Skip to content

CIP: a test suite for the intrinsic prior densities (the missing half of the 0.0.17.13 eccentricity-prior fix) - #174

Open
oshaughnessy-junior wants to merge 1 commit into
oshaughn:rift_O4cfrom
oshaughnessy-junior:rift_O4c_ecc_prior_fix
Open

CIP: a test suite for the intrinsic prior densities (the missing half of the 0.0.17.13 eccentricity-prior fix)#174
oshaughnessy-junior wants to merge 1 commit into
oshaughn:rift_O4cfrom
oshaughnessy-junior:rift_O4c_ecc_prior_fix

Conversation

@oshaughnessy-junior

@oshaughnessy-junior oshaughnessy-junior commented Aug 13, 2026

Copy link
Copy Markdown

Rebased onto 0.0.17.13. This PR originally carried a one-line fix to log_eccentricity_prior plus a test. 0.0.17.13 independently made the byte-identical fix (np.log(ECC_MAX/ECC_MIN)). That fix is upstream's and is left entirely alone — this branch no longer modifies CIP at all. What remains is the half that did not land: the test.

What was wrong, and why it reached a release

--eccentricity-prior log_uniform, added in 0.0.17.12, could never have run:

AttributeError: module 'numpy' has no attribute 'ln'

Independently of the crash, the normalization was the uniform prior's. For a density uniform in ln(e) on [ECC_MIN, ECC_MAX] the constant is ln(e_max/e_min), not ln(e_max − e_min) — which for the shipped 0.001/0.4 defaults is ln(0.399) = −0.918, negative, so the density would have been negative everywhere had it evaluated at all.

Both are fixed in 0.0.17.13. But the fix went in without a test, so nothing stops the next edit to these functions from reintroducing either defect. That is what this PR adds.

The root cause is structural: CIP is a script, not an importable module — importing it parses argv, reads the input grid and runs several thousand lines of module-level setup. So none of its 38 prior densities has ever had a test, and a function that could not execute at all shipped in a release.

The test

test_cip_priors.py deliberately does not transcribe the priors: a copied reference implementation drifts from the shipped code and then tests nothing. Instead it extracts the real def blocks from the CIP source with ast and execs them in a namespace holding numpy and the handful of module-level constants they close over. The functions under test are byte-identical to the ones CIP runs, and no argparse or I/O executes.

Two layers, covering multiple priors rather than only the one that broke:

test scope catches
test_prior_evaluates all 38 extracted priors a name that does not exist (np.ln), non-finite or negative densities, non-broadcastable returns — for any prior, including ones added later, with no new test to write
test_prior_is_normalized the 19 that claim a normalized density a wrong normalization constant, which raises nothing and silently reweights the posterior

The integration measure is spelled out per prior (dx, d(ln x), d(x²)) rather than inferred, since choosing it wrongly is precisely the bug class under test. Priors documented in-source as unnormalized (M_prior, q_prior, unnormalized_*, the tapered magnitudes, …) are listed in UNNORMALIZED and excluded on purpose, not by oversight.

Plus two shape tests specific to this regression — that e·p(e) is constant (so p really is log-uniform, independently of the constant), and that the flat and log-uniform eccentricity priors are not the same function, since if a refactor collapsed one onto the other --eccentricity-prior would silently stop doing anything. And a test_priors_were_actually_extracted guard: if CIP is refactored so the priors are no longer top-level, every parametrized test would collect zero cases and the suite would go green while testing nothing. It fails loudly instead.

Verified as a regression test

CIP source result
0.0.17.12 (pre-fix) 4 failed, 56 passed — all four naming log_eccentricity_prior
0.0.17.13 (this base) 60 passed

Runs in about a second and needs only numpy + scipy. No new dependency, no CI wiring change.

Two things found while writing it

  1. Selection is on 'prior' appearing anywhere in the name, not a _prior suffix. The obvious suffix rule silently skips s_component_zprior, s_component_zprior_positive and both *volumetricprior densities — most of the spin sector. Worth knowing if anyone extends this.

  2. The test does not import lalsimutils (it parses p_R out of the source instead). Importing it pulls in LAL, whose default error handler calls abort(), which turns any numerical complaint raised inside scipy.integrate.quad into a process core dump instead of a test failure. I hit exactly that while developing this. These priors are pure numpy, so the test stays free of that stack.

Flagged, not fixed

  • s_component_volumetricprior integrates to R/9 over [0,R], not 1. It is excluded from the normalization test as an unnormalized density. I have not verified whether that factor is intended (a volumetric spin-magnitude prior is usually 3a²), so I have not touched it.
  • util_ConstructIntrinsicPosterior_GenericCoordinates.py:1007 reads prior_map["s1z_bar"] = s_compone+nt_zprior_positive — a typo that raises NameError whenever --aligned-prior alignedspin-zprior-positive is used. Present on both O4c and O4d. Out of scope here; happy to fix separately.

The same test is carried on the O4d consolidation branch (#173 / oshaughnessy-junior#91).

🤖 Generated with Claude Code

Rebased onto 0.0.17.13, which independently made the same one-line fix this
branch previously carried (np.log(ECC_MAX/ECC_MIN) in log_eccentricity_prior).
That fix is upstream's and is left untouched; the CIP source is not modified
here at all.  What remains is the half that did not land: the test.

CIP is a script, not an importable module -- importing it parses argv, reads
the input grid and runs several thousand lines of module-level setup -- so none
of its 38 prior densities has ever had a test.  That is how a function calling
np.ln, a name that does not exist in numpy, reached a release: --eccentricity-
prior log_uniform could never have run, and its normalization was independently
the uniform prior's (ln(ECC_MAX-ECC_MIN) = ln(0.399) = -0.918 for the shipped
defaults, i.e. negative).  Nothing currently stops the next edit to these
functions from reintroducing either defect.

test_cip_priors.py does not transcribe the priors -- a copied reference
implementation drifts from the shipped code and then tests nothing -- but
extracts the real def blocks from the CIP source with ast and execs them in a
namespace holding numpy and the handful of module-level constants they close
over.  The functions under test are byte-identical to the ones CIP runs, and no
argparse or I/O executes.

Two layers:

  - test_prior_evaluates, over all 38 extracted priors: each must evaluate on
    its support and return finite, non-negative, broadcastable densities.  This
    is the generic guard that catches an np.ln (a name that does not exist) in
    any prior, including ones added later, with no new test to write.

  - test_prior_is_normalized, over the 19 that claim a normalized density: each
    is integrated numerically over its stated support and must come to 1.  This
    is what catches a wrong normalization constant, which raises nothing and
    merely reweights the posterior.  The measure is spelled out per prior ('x',
    d(ln x), d(x^2)) rather than inferred, since choosing it wrongly is the bug
    class under test.  Priors documented in-source as unnormalized are listed
    and excluded on purpose.

Plus two shape tests specific to the regression -- e*p(e) constant (so p really
is log-uniform, independently of the constant), and that the flat and
log-uniform eccentricity priors are not the same function, since if a refactor
collapsed one onto the other --eccentricity-prior would silently stop doing
anything -- and a guard that fails loudly if the extraction ever finds nothing,
so a CIP refactor cannot leave the suite green while testing zero cases.

Verified as a regression test: 4 failed / 56 passed against the 0.0.17.12
source, all four naming log_eccentricity_prior; 60 passed against 0.0.17.13.
Runs in about a second and needs only numpy and scipy.

Two notes for review, deliberately NOT changed here:
  - selection is on 'prior' appearing anywhere in the function name, not a
    '_prior' suffix.  The suffix rule silently skips s_component_zprior,
    s_component_zprior_positive and both *volumetricprior densities.
  - the test does not import lalsimutils (it parses p_R out of the source):
    importing it pulls in LAL, whose default error handler calls abort(), which
    turns any numerical complaint inside scipy.integrate.quad into a core dump
    instead of a test failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oshaughnessy-junior
oshaughnessy-junior had a problem deploying to private-review-dispatch-rift-upstream August 14, 2026 15:50 — with GitHub Actions Failure
@oshaughnessy-junior oshaughnessy-junior changed the title CIP: fix the log-uniform eccentricity prior (np.ln, wrong normalization), and give the CIP priors a test CIP: a test suite for the intrinsic prior densities (the missing half of the 0.0.17.13 eccentricity-prior fix) Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant