Skip to content

Propagate tidal parameters through GWSignal - #180

Open
oshaughnessy-junior wants to merge 1 commit into
oshaughn:rift_O4cfrom
oshaughnessy-junior:codex/seobnrv5thm-tidal-gwsignal
Open

oshaughnessy-junior wants to merge 1 commit into
oshaughn:rift_O4cfrom
oshaughnessy-junior:codex/seobnrv5thm-tidal-gwsignal

Conversation

@oshaughnessy-junior

Copy link
Copy Markdown

Summary

  • pass P.lambda1 and P.lambda2 into all RIFT GWSignal generation paths
  • centralize construction of the GWSignal parameter dictionary
  • add a public tidal-waveform regression test and a native-mode generator sanity test

This intentionally does not special-case string approximants or impose a mode ceiling; those behaviors remain with GWSignal and downstream RIFT handling.

Validation

  • pytest -q MonteCarloMarginalizeCode/Code/test/waveform/test_gwsignal_tides.py
  • result in the available environment: 2 passed, 1 skipped
  • the optional native-mode generator test skipped because that generator is not registered in the installed environment
  • public TaylorT4 tidal-vs-zero-tide generation passed

@oshaughnessy-junior
oshaughnessy-junior deployed to private-review-dispatch-rift-upstream August 20, 2026 17:49 — with GitHub Actions Active
@oshaughnessy-junior
oshaughnessy-junior marked this pull request as ready for review August 24, 2026 19:46
@oshaughnessy-junior
oshaughnessy-junior deployed to private-review-dispatch-rift-upstream August 24, 2026 19:46 — with GitHub Actions Active

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent automated review completed at the recorded exact commit. Detailed findings were withheld from public output by the private-context egress policy and require private human declassification.

oshaughn pushed a commit that referenced this pull request Sep 4, 2026
…rom the SNR

RO'S (2026-08-23): the non-JAX helper picks this exponent with intelligent
historical choices; the JAX protocol has no such intelligence and should have
something.  This is that something -- but keyed on a different variable, because
measuring first showed the historical rule does not transfer.

WHAT THE HISTORICAL RULE IS.  helper_LDG_Events.py:1472-1481 sets
beta = 0.1 for SNR <= 22.5 and 0.1*(22.5/SNR)^2 above; equivalently
beta = min(0.1, 25.31/lnLmax), i.e. it holds beta*lnLmax fixed at ~25.3 nats.
util_RIFT_pseudo_pipe.py does not set it; the helper is the only chooser.

WHY IT DOES NOT TRANSFER.  In every non-JAX consumer the exponent shapes only
the adaptive sampling PRIOR -- mcsamplerGPU keeps
log_integrand = lnL + ln p - ln p_s while tempering only log_weights -- so the
estimator is unbiased at any beta and the exported sample count is untouched.
On the JAX flowMC path beta = inv_T is the exponent of the target the MCMC
SAMPLES, the draws are the deliverable, and the export must be reweighted by
L^(1-beta) (PR #180).  That reweight costs

    ESS/N = Z_1^2/(Z_beta Z_{2-beta}) = [beta (2-beta)]^(dim/2)

which is set by the SAMPLED DIMENSION and carries no lnLmax term at all.

MEASURED, two independent routes (DESIGN_jax_tempering.md):
  * exact offline sweep on the real BNS likelihood (SNR 23.8, 4-D), defensive-IS
    reference at ESS 5235 with every exponent in [0,2] resolved: the law holds to
    0.79-1.00 over beta in [0.05,1].
  * the driver's own reported export ESS, which shares no machinery with the
    above: predicted 157 vs measured 185 at beta=0.0951, and predicted 4320 vs
    measured 4203 at beta=0.7735.
  * SNR ladder at fixed beta (injected distance): beta=0.5 gives ESS/N 0.620 at
    SNR~15 and 0.517 at SNR~33 -- a 17% drift where the historical rule would
    have demanded beta fall 4.7x.

CONSEQUENCE.  Porting the historical exponent literally costs 17x in exported
rows (4800 -> 278) and 6.6x in evidence precision on the study event, and the
driver already prints "NOT a usable posterior sample" over the result.

WHAT THIS ADDS.
  * samplers.export_ess_fraction / beta_for_export_ess -- the law and its
    inverse.  The signature takes no SNR, deliberately; a test pins that.
  * --auto-adapt-weight-exponent + --target-export-ess-frac (default 0.9): the
    smallest beta meeting the export budget, keyed on dim (3 -> 0.740,
    4 -> 0.773, 5 -> 0.797).
  * a guard that REFUSES any beta whose predicted export ESS is below the
    driver's own usability floor, naming the historical rule as the trap.
    --allow-degenerate-tempering overrides.  That floor is now one constant
    (_USABLE_EXPORT_ESS) shared with the warning fairdraw_indices already
    printed, so the two cannot drift.
  * conflicts raise rather than silently win: --auto with an explicit exponent,
    and --auto with --adapt-adapt.
  * --interp cubic is selectable (the gatherer is implemented and registered in
    _GATHERERS; the CLI could not reach it).  Needed to measure the
    configuration the reference run actually used.

DELIBERATELY NOT DONE: porting the SNR rule to the static exponent, and turning
--adapt-adapt on by default.  --adapt-adapt is an annealing SCHEDULE that always
terminates at inv_T = 1, so it delivers the historical rule's benefit at zero
reweight cost -- which makes it the right answer to the high-SNR problem and the
wrong thing to call an exponent chooser.  Whether it should be the default needs
the high-SNR bake-off in PR #183.

Stacked on claude/jax-fairdraw-extrinsic (PR #180): without that fair draw a
non-unit exponent is not correctly handled at export, so this must not land
first.

Limitations, including the axes NOT swept, are in the DESIGN doc rather than
implied by silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
…f them blocking

Found by running the repo's own RIFT/integrators/REVIEW_CHECKLIST.md against the
change.  None was visible to the test suite or the CI gate, which were green over
all of them.

1. BLOCKING -- --auto killed every multi-event batch.  resolve_tempering_exponent
   wrote its answer back to opts.  opts is per-RUN; analyze_one is per-EVENT.  On
   event 1 the chooser read its own event-0 output as a user-supplied exponent and
   aborted with SystemExit.  ILE_extr.sub runs batches, so every real multi-event
   --auto run would have died.  NO SINGLE-EVENT TEST CAN SEE THIS and every
   measurement backing this PR is single-event; the checklist names the shape
   ("Is per-point state cleared on ENTRY").  Reproduced with
   --n-events-to-analyze 3.  The chooser is now PURE -- it returns the exponent
   and analyze_one keeps it in a local.

2. The guard falsely REFUSED --smc-puffball.  That flag routes these modes to
   samplers.smc_puffball_sample, which swallows `temper` in **_ignore and returns
   post_weight uniform with temper=1.0 -- the exponent has no effect there, so
   refusing a run over it is a false alarm.  The path now returns beta=1 and
   REPORTS the no-op rather than leaving it silent.

3. --fisher-is-samples: a SUCCESSFUL Fisher-IS pass replaces the cloud with an
   already-fair-drawn uniform-weight set, so the reweight cost never materialises
   -- but it falls back to the tempered draws when it fails.  The guard still
   refuses (fail-closed); that is now stated in the message and the limitations
   instead of being a surprise.

4. .github/workflows/ci.yml still documented EXPECTED_TESTS=27 and quoted its cost
   and trim guidance against 27 tests.  The count went 27 -> 48 (#180) -> 79 here.
   Updated, with the runner-vs-local ratio spelled out so the 60-minute timeout is
   justified rather than assumed.

Also: TWO OF MY OWN TESTS PINNED THE BUGGY CONTRACT -- they asserted the chooser
must ASSIGN opts.adapt_weight_exponent.  Retargeted to the return-value contract
rather than deleted, so what they were actually protecting (that the chooser is
not a dead knob) survives.

Three regression tests added, each mutation-tested by re-introducing the exact
defect: writing opts again (lethal, 4 tests), the call site ignoring the return
value (lethal, 2 tests), and the smc-puffball path.

31 tests (from 28); EXPECTED_TESTS 79.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
Review finding on #180.  fairdraw_size clamped the requested export count by
1.5*res["neff"].  On every flowMC mode `theta` is the production posterior /
tempered chain, while res["neff"] comes from a SEPARATE estimator: the
moment-matched Gaussian importance cloud built for the evidence, or -- under
--adapt-adapt -- the annealing ladder's minimum inter-stage ESS.  Neither
describes the exported rows.  A valid equal-weight chain was therefore truncated
whenever that unrelated proposal happened to have low ESS.

Demonstrated on identical input (5000 equal-weight rows,
--n-fairdraw-extrinsic-samples 1000):

    evidence neff = 2.0     ->    3 rows exported
    evidence neff = 40.0    ->   60 rows
    evidence neff = 5861.9  -> 1000 rows
    after this change       -> 1000 rows, always

Real runs of this branch report evidence neff of 5861.9 (beta=1), 163.8
(beta=0.0951) and as low as 4.1 for a poorly converged evidence proposal, so the
truncating regime is reachable, and --fairdraw-extrinsic-output is in
ILE_extr.sub.

WHY THE ESS TERM IS REMOVED RATHER THAN REPOINTED.  ILE's own clamp
(mcsampler.integrate:802, n_extr = min(n_extr, 1.5*eff_samp, 1.5*neff)) is
self-consistent because eff_samp, neff and the fair draw are all properties of
the SAME weight vector `wt` the draw samples from.  The port lost that.  The ESS
that does bound this export is the one fairdraw_indices computes from the EXPORT
weights -- and it has already applied it, as n_out = min(ceil(1.5*ESS), n_in).
So `n_have` already carries the 1.5*ESS cap wherever the weights were
non-uniform, and where they were uniform there is no computed chain ESS to clamp
by (an autocorrelation-based one would be a different, unimplemented quantity).
A second clamp is a no-op where it would be right and wrong where it binds.

The `neff` parameter is removed from write_samples and fairdraw_size entirely,
so the evidence number has no path into the export count and the defect cannot
be re-wired.

WHY NOTHING CAUGHT IT: all 20 write_samples call sites in the test file passed
neff=inf or neff=nan, so the clamp was never once exercised.  Two tests added --
one behavioural (ask for 1000 equal-weight rows, require 1000), one structural
(the parameter is gone from both signatures and from the analyze_one call site).

Also corrects a pre-existing off-by-one in the gate's bookkeeping, found while
updating it: test_jax_fairdraw_export.py collected 30, not the 29 the manifest
documented, and the suite collected 57 against EXPECTED_TESTS=56.  The floor is a
>= test so it passed; the documented counts were simply wrong.  Now 32 and 59,
both verified by collection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
…extrinsic

jax ILE: make --save-samples a fair draw, not the raw sampler cloud
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
…ubic)

#180 has landed, so this PR now targets rift_O4d directly rather than stacking.

Three conflicts:

  * --interp cubic.  #190 shipped the same fix independently, with a help string
    explaining that linear undershoots the rholm peak and biases the recovered
    arrival time.  Took THEIRS wholesale -- it is a strict superset of the bare
    `choices` widening this branch carried -- and dropped mine.
  * The gate's per-file ledger.  Both entries are needed (this branch's chooser
    file, #190's test_interp_choices.py); kept both.
  * EXPECTED_TESTS.  Resolved to 95 by RUNNING collection on the merged FILES
    list.  64 (rift_O4d) + 31 (chooser) = 95 confirms nothing else shifted.

TWO STALE CLAIMS OF MY OWN, found by reading what landed rather than only the
conflict hunks:

  * The gate ledger entry for test_jax_tempering_chooser.py still said the
    chooser is "actually assigned to opts.adapt_weight_exponent" -- the contract
    REMOVED in cf02b9d when that write turned out to make event 1 of a batch
    read event 0's choice.  Corrected to the return-value contract.
  * The --smc-puffball branch comment said smc_puffball_sample "returns
    post_weight uniform with temper=1.0".  #180 changed that: it now reports the
    inv_T its ladder actually reached and a matching L^(1-inv_T) weight, and its
    export weights are uniform only because analyze_one REFUSES the event unless
    the ladder finishes at inv_T=1.  Skipping the guard is still correct -- the
    signature still swallows `temper` in **_ignore, so the exponent is genuinely
    inert -- but the comment now says why, and warns against re-deriving "its
    weights are always uniform" from that branch.

Checked the driver's automerge kept both sides rather than trusting it: #180's
2-arg fairdraw_size and both length-mismatch guards are present alongside this
branch's resolve_tempering_exponent, the resolved_beta wiring at both sampler
call sites, and a single _USABLE_EXPORT_ESS shared with fairdraw_indices.

65 tests pass across the two interacting files (31 chooser + 34 export).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
Caught reading PR #186's own diff after the rift_O4d merge.  I wrote "79" when
79 was current; merging #180 (64) plus this branch's chooser took the floor to
95 and left my own note stale -- the same failure the note itself documents,
one revision later.

Also records where the count came from (27 -> 48 -> 64 via #180 -> 95 here,
with #190's test_interp_choices.py along the way) and re-measures the wall time
on the host it was actually run on (859 s, ldas-pcdev11, 16 cores), so the
60-minute timeout is justified against a current number rather than a stale one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oshaughn pushed a commit that referenced this pull request Sep 4, 2026
…han assumed)

No conflicts this time.  A clean automerge is not evidence the semantics
survived, though -- both sides had touched .travis/test-jax.sh and ci.yml -- so
checked explicitly:

  * this branch: resolve_tempering_exponent, the resolved_beta wiring at both
    sampler call sites, and the --smc-puffball no-op notice are all present.
  * upstream: #180's 2-arg fairdraw_size and length-mismatch guard, and
    TIME_INTERP_CHOICES from the stencil-validation work, are all present.
  * manifest: 12 gated files + 2 explicitly excluded, every test_*.py in
    test/jax accounted for, and collection returns exactly 95 -- so
    EXPECTED_TESTS=95 and the ci.yml cost note both remain correct rather than
    silently becoming a floor that no longer describes the suite.

Nothing to change; recording the checks because "the merge was clean" has not
been a sufficient answer at any point in this branch's history.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants