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
Conversation
oshaughnessy-junior
had a problem deploying
to
private-review-dispatch-rift-upstream
August 13, 2026 17:20 — with
GitHub Actions
Failure
oshaughnessy-junior
force-pushed
the
rift_O4c_ecc_prior_fix
branch
from
August 13, 2026 17:20
5a3c7a1 to
e1f49ae
Compare
oshaughnessy-junior
had a problem deploying
to
private-review-dispatch-rift-upstream
August 13, 2026 17:21 — with
GitHub Actions
Failure
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
force-pushed
the
rift_O4c_ecc_prior_fix
branch
from
August 14, 2026 15:50
e1f49ae to
5391300
Compare
oshaughnessy-junior
had a problem deploying
to
private-review-dispatch-rift-upstream
August 14, 2026 15:50 — with
GitHub Actions
Failure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong, and why it reached a release
--eccentricity-prior log_uniform, added in 0.0.17.12, could never have run: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 isln(e_max/e_min), notln(e_max − e_min)— which for the shipped 0.001/0.4 defaults isln(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.pydeliberately does not transcribe the priors: a copied reference implementation drifts from the shipped code and then tests nothing. Instead it extracts the realdefblocks from the CIP source withastand 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_prior_evaluatesnp.ln), non-finite or negative densities, non-broadcastable returns — for any prior, including ones added later, with no new test to writetest_prior_is_normalizedThe 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 inUNNORMALIZEDand excluded on purpose, not by oversight.Plus two shape tests specific to this regression — that
e·p(e)is constant (sopreally 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-priorwould silently stop doing anything. And atest_priors_were_actually_extractedguard: 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
log_eccentricity_priorRuns in about a second and needs only numpy + scipy. No new dependency, no CI wiring change.
Two things found while writing it
Selection is on
'prior'appearing anywhere in the name, not a_priorsuffix. The obvious suffix rule silently skipss_component_zprior,s_component_zprior_positiveand both*volumetricpriordensities — most of the spin sector. Worth knowing if anyone extends this.The test does not import
lalsimutils(it parsesp_Rout of the source instead). Importing it pulls in LAL, whose default error handler callsabort(), which turns any numerical complaint raised insidescipy.integrate.quadinto 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_volumetricpriorintegrates toR/9over[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 usually3a²), so I have not touched it.util_ConstructIntrinsicPosterior_GenericCoordinates.py:1007readsprior_map["s1z_bar"] = s_compone+nt_zprior_positive— a typo that raisesNameErrorwhenever--aligned-prior alignedspin-zprior-positiveis 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