O4d: prepare 0.0.18.0rc4 release - #191
Merged
Merged
Conversation
The gate owns the filename pattern test/test_noloop_*.py, so the new file matched its manifest scope by NAME and went red as "neither registered nor explicitly excluded" -- which is the gate working: a new file in that namespace is meant to force a decision rather than be silently unrun. The decision is exclusion. It matches by name but not by subject: it pins NoLoop's rho_sq and kappa_sq ACCUMULATOR shapes against a reference, and its time-integral test is about the quadrature RULE, not about sub-sample interpolation of Q_lm. It is already registered with core-unit-check, whose FILES manifest carries it and whose floors count it, and there is no dual-registration precedent -- every marker-carrying file belongs to this gate alone and no core-unit file carries the marker. The two gates partition. Listed in EXCLUDED with a stated reason rather than renamed out of scope. The gate's own comment notes that a file under another prefix escapes the manifest entirely; renaming to dodge it is how these gates quietly stop covering things, and the reason belongs where the next such file will hit it. No floor changes here: exclusion adds no tests to this job. Verified locally through the gate's scope-pattern, exclusion and manifest phases, which are the three my change affects; the run then stops on an unrelated ImportError (no module named glue) in test_batchmode_stencil_default, an artifact of this environment rather than of the change. CI has the full install. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…verify # Conflicts: # .travis/test-core-units.sh
Conflict was in .travis/test-core-units.sh: both sides moved the same floors. rift_O4d added
test_vectorized_lal_tools_split.py (296/284 -> 299/287); this branch added
test_teobresums_compat.py and test_integrator_studies.py (-> 316/304).
Resolved by keeping BOTH manifest entries and RE-MEASURING rather than adding the deltas: the
floors are now 319/307 from a full run, and the merged comment records both histories plus the
reason the arithmetic is not the method. (It happens to equal 316+3, which is exactly the kind
of agreement that would have made a computed number look safe.)
Two things the resolution itself broke or exposed, both caught before pushing:
* my conflict edit DELETED the EXPECTED_PASSED assignment along with the two conflicting
versions of it. `set -u` turned that into "EXPECTED_PASSED: unbound variable" and the gate
exited 1 rather than silently skipping the outcome check -- the failure direction that
matters. Restored with its comment.
* ci.yml claimed timeout-minutes was "an order of magnitude above the 49 s measured on CIT".
Measured now: 350 s, of which only ~100 s is the pytest run; the rest is the per-file
collection loop, one RIFT-importing interpreter per manifest entry, which grows with the
manifest. 20 minutes is ~3.4x, not 20x. Comment corrected in both files, because a margin
claim that has quietly become false is how a gate ends up timing out.
Reassessed on the merged tree: ci-roster-check PASS (208 files, 157 reachable, 51 rostered --
the five test files rift_O4d brought are all already registered by its own authors),
roster-verify-check PASS (50 predicates), core-unit-check PASS (319/307/12).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…aude/ci-roster-verify
…ocation was sized wrong An independent review ran a 37-configuration differential matrix over both PRs (phase and distance marginalization, n_cal>1 in loop and fused, the cal self-term fix, return_lnLt / return_cal_components / return_time_draw, explicit_time_values, all three stencils, all three quadratures, 1-3 detectors, heterogeneous mode lists, CPU and GPU). It found the CODE sound -- #255 bitwise across all 37, the whole of #256 within 1.8e-15 nats -- and the EVIDENCE unsound. This fixes the evidence and one real defect it surfaced. 1. THE ACCUMULATOR TEST NEVER EXERCISED kappa_sq. epoch was a whole second before tref, putting ifirst at ~3979-4152 against an n_time of 512, so every Q window was zero-extended and the data term was identically zero in all six parametrized cases. The file was named for the kappa_sq change and validated only rho_sq; the reviewer demonstrated it passing with the CPU Q producer aliased across detectors, which is precisely the hazard kappa_sq = Q_prod_result introduces. Fixed by placing the window inside the buffer, and pinned by a new test that asserts lnL(t) actually varies in time. Verified by sabotage: dropping either accumulator now fails 4 of 9, and the clean tree passes 9. 2. THE SPLIT TEST WAS TAUTOLOGICAL. After the split ComputeDetAMResponse IS SourcePolarizationBasis composed with ComputeDetAMResponsePrecomputed, so comparing them cannot fail. It passed with a sign flipped in the source-only half, the response matrix doubled in the per-detector half, and the speed of light wrong by 0.1%. Rewritten against a FROZEN copy of the pre-split bodies; all three sabotages now fail. The claim in DESIGN_noloop_per_detector_glue.md is corrected rather than deleted, because the general lesson is worth keeping: when a refactor splits a function, the halves are not an independent check on each other. 3. THE READ-ONLY TEST TESTED NUMPY, NOT THIS CODE, and its rationale was false. It called np.broadcast_to directly and passed on any tree; its docstring claimed cupy also returns a read-only broadcast, which is measurably wrong -- cupy's is writable and writes through to the base. Replaced with a test of what _dense_rho_sq actually returns, and the false claim removed. 4. _simps_weights BUILT AN (npts, npts) IDENTITY ON THE DEFAULT HOT PATH. npts is 2*window*srate and the driver's DEFAULT srate is 16384, so npts is 2457 in the default configuration, not the 614 of the srate-4096 runs everything here was measured at: a 48 MB identity and ~97 MB held in cupy's pool, inside the first likelihood call of every --vectorized --gpu run, in a function whose n_chunk is already bounded by device memory. Now built a block of rows at a time, capping it at 5 MB; verified bitwise against the whole-identity result at npts 614, 1000 and 2457. Also keyed the cache on the quadrature function, not just the backend: the GPU and CPU simps differ by 0.405 nats on an under-resolved peak, and a future caller passing a different rule at the same shape would silently be served the other one's weights. 5. THE loglikelihood= CALLBACK CONTRACT NARROWED SILENTLY. rho_sq now reaches the callback as a stride-0 view, so a callback writing in place raises on CPU and RACES on GPU -- cupy's broadcast is writable and every column aliases one address. No in-tree callback writes (_factored_lnL_helper and the driver's distmarg_loglikelihood both allocate), so nothing is broken; densifying at the callback boundary would undo the optimization, so this is documented in the NoLoop docstring instead. core-unit-check floors raised to 312/300: baseline 299/287, +4 as the split test goes 3 -> 7, +9 for the accumulator test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lidate
Three findings from the automated review, all real holes I left, merged from the branch:
* my LEGACY predicate was FAIL-OPEN. It errored only when the file COLLECTED tests, so a file
that imports cleanly and finds none satisfied "cannot be imported" -- when a clean import is
exactly what falsifies that status. Now only a collection/import ERROR counts.
* my EXPENSIVE predicate asked for "passed none", which a suite that FAILS or ERRORS also
satisfies. A broken suite is not a guarded one. Now: collects, and SKIPS in a run that
exits cleanly.
* my collected-count used len(re.findall("::")), which double-counts class-based node ids.
AND THE ONE THAT MATTERS MOST. test_integrator_studies.py invoked the five studies WITHOUT
--as-test. All five keep their scientific comparisons and their SystemExit(1) behind
`if args.as_test` (verified in each: lines 144/70/179/230/126), so the wrapper I shipped as "the
AV bias gate now has CI behind it" detected only CRASHES and gated none of the five criteria it
named. The evidence I offered for it -- three consecutive runs exiting 0 -- could not have come
out any other way. That is the inert-guard class this whole effort exists to catch, authored by
me, and review caught it rather than I.
With the flag the gates actually run and all five pass, printing their real criteria ("all warm
starts no more biased than cold", "oracle improved sampling and stayed unbiased"). They cost
8/4/19/7/5 s instead of 5/2/14/4/4 -- larger precisely because the work now happens. The roster
timings said the old numbers and are corrected, with the reason recorded beside them.
Re-measured on the merged tree: core-unit-check 319/307/12 PASS (315 s), roster-verify-check 50
predicates PASS (340 s), ci-roster-check PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-broadcast NoLoop: accumulator shapes and the time integral (-44% cumulative with #255)
… on a runner roster-verify-check went red on the runner, and was right. The entry was OPTDEP needs:glue,htcondor; with htcondor absent there it still collected 15 and passed 15. Confirmed locally with BOTH blocked via a sys.meta_path finder: 15/15. Its `import htcondor` and `from glue import pipeline` are capability probes inside the tests, not requirements. CIT has both packages, which is why CIT could not see this and a runner could -- the same asymmetry that produced the rimsky mistake, this time caught by the check instead of by a red core-unit-check. That is the predicate earning its place: it is the third false reason it has found, and the first that only a runner could expose. Moved into core-unit-check's manifest; floors re-measured in the follow-up commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…'s own floors
SECOND merge conflict on the same floors: rift_O4d added test_noloop_accumulator_shapes.py
(+8/+8, 299/287 -> 307/295) while this branch was at 319/307 and adding
test_backends_lowlevel.py. Resolved the same way -- keep every manifest entry from both sides
and RE-MEASURE, never add the deltas. Measured: 347 collected, 335 passed, 12 skipped, 363 s.
The history block now records that arithmetic has been the wrong method twice.
THE RESOLUTION EXPOSED A FAIL-OPEN IN THIS SCRIPT. With the MEASURED_* placeholders still in
place the gate reported
== outcomes: 347 collected, 335 passed, 12 skipped, 0 failed, 0 errored ==
line 197: [: MEASURED_TESTS: integer expression expected
rc=0
`[ 347 -lt MEASURED_TESTS ]` does not fail the build: bash prints to stderr, returns 2, and the
`if` is simply FALSE. A malformed floor therefore DISABLES the comparison it appears to
perform, and the gate goes green having checked nothing. `set -u` catches a MISSING floor --
it did, earlier in this branch -- and nothing caught a malformed one.
The three floors are now validated as non-negative integers, and that check sits ABOVE the
per-file collection loop so it reports in 1 s rather than after 363 s; a developer-error guard
that costs six minutes to fire is one people route around. Checked both directions: the
placeholders are rejected, AND a valid-but-too-high floor (999) still fails, so the comparison
really runs rather than merely being reachable.
Also in this merge: test_backends_lowlevel.py moves from the roster into the manifest.
roster-verify-check found its OPTDEP reason false ON THE RUNNER -- htcondor absent there, and it
still collected 15 and passed 15. Confirmed locally with glue AND htcondor both blocked via a
sys.meta_path finder: 15/15. Those imports are capability probes inside the tests.
Every runner-vs-CIT disagreement this round ran the same way: CIT is richer, and that richness
hid a false claim (asimov for rimsky, htcondor here, jax for jax_gp). Two of the three were
caught by CI rather than by a local run that preceded it.
Merged tree: ci-roster-check PASS (209 files, 159 reachable, 50 rostered), core-unit-check
347/335/12 PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y there PR #256 rewrote test_vectorized_lal_tools_split.py (3 -> 7 tests) and added a ninth test to test_noloop_accumulator_shapes.py (8 -> 9), but the floor update never reached the commit: the edit was made on one host and committed over ssh from another before NFS made it visible, so git add -A staged the previous content. Nothing failed, because the floors are MINIMUMS and 312 collected comfortably clears 307 -- which is exactly the silent under-coverage this gate was built to prevent, and its own comment warns about: "a floor left at the old value passes while covering less". Baseline is unambiguous. At ebb4b58 the floors were 299/287 with the split file contributing 3 tests, and the ONLY change to the FILES manifest between that commit and rift_O4d HEAD is test_noloop_accumulator_shapes.py. So 299 + 4 (split 3 -> 7) + 9 (accumulator) = 312 collected, and 287 + 13 = 300 passed. MAX_SKIPPED is unchanged: neither file skips or xfails. The comment block records why the split file grew, since the reason is the useful part: it compared the combined wrapper against the composition of its own two halves, which after the split IS the wrapper, so it could not fail. It is now checked against a frozen copy of the pre-split bodies and parametrized per detector. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The runner reports 350 collected / 338 passed for this manifest; CIT reports 347 / 335. The results are IDENTICAL -- both "335 passed, 11 skipped, 1 xfailed" -- and per-FILE collection is 347 on both, file by file. The gap is pytest-subtests: present in the runner's dependency closure, absent from CIT's IGWN environment, and it counts the three `subTest` blocks in test/backends/test_backends_lowlevel.py as separate cases in the junit XML this gate parses. Floors stay at the PLUGIN-FREE 347/335, which is robust in the direction that matters: the runner's 350/338 clears them today, and if pytest-subtests ever leaves that closure the counts fall back to 347/335 and still clear. Raising them to 350 would convert an unrelated dependency change into a red gate -- and the note is here because "the runner measured more, so raise the floor" is the obvious wrong move for the next person, including me. Found by diffing per-file collection against the combined junit rather than accepting that a higher number on the runner was self-explanatory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
247 rewrote the opening paragraph to describe both phi paths and left the SCOPE block forty lines below it saying "Localizing phi as well ... is not attempted here". Same docstring, opposite claims, and the second one is the stale one -- the profile F(phi) and its envelope derivatives it says would be needed ARE u_profile, and phi_local_lnI is built on them. This is the defect the integration review predicted for this file and it was only half fixed: the header was reconciled during the 250 merge and this block was missed, so main now carries a docstring that argues with itself. Rewritten as two rules rather than one, which is what the file actually contains: joint_lnL_phi_dense scans phi densely and is the production path; phi_local_lnI localizes phi too, declines rather than returning an unbounded number, and is not wired into production. The retraction is left visible rather than silently overwritten, because "true when written" is the reason this kind of claim goes stale. Documentation only; no executable change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ors-after-review core-unit-check: raise the floors to match the tests that are actually there
…egrid ILE: add accurate reflected Q time pregrid
rift_O4d raised core-unit-check's floors 307 -> 312 in #258. That is a legitimate raise and a good catch on their side, but merging it changes THIS manifest's numbers by ZERO: measured before and after, 347 collected / 335 passed / 12 skipped. The reason is the whole argument for re-measuring. #258's growth -- test_vectorized_lal_tools_ split.py 3 -> 7 and test_noloop_accumulator_shapes.py 8 -> 9 -- had ALREADY landed in the files via eedf7a8; only rift_O4d's floors lagged its own contents, so 312 collected was clearing a 307 floor. #258 reconstructed 312 arithmetically (299 + 4 + 9); this branch had measured 347 with those same files already grown, because it never adds. An arithmetic floor can sit one commit behind reality without anything failing; a measured one cannot. Conflict shape: git saw delete-vs-modify, because this branch had relocated the floors block above the per-file collection loop, so their side arrived whole and ours empty. Dropped the conflicted region and folded their per-file rationale into the history here -- the reasons those two files grew are the useful part, and both are inert-test findings worth keeping: a comparison that pitted the combined wrapper against the composition of its own halves (so it could not fail), and synthetic inputs that left kappa_sq identically zero (so an assertion held for the wrong reason). Dropped one line from their side: "CONFIRMED ON A RUNNER ... reported the same 278/266/12". It is no longer true. The runner reports 350/338 against CIT's 347/335 -- identical results, plus three subTest cases that pytest-subtests counts and CIT's environment cannot -- and that is documented immediately above. A "confirmed identical" claim beside a known divergence is worse than no claim. Measured with deliberately impossible floors (999999) so the run had to fail and report real counts, rather than relying on noticing a leftover placeholder. All 35 manifest entries from both sides survive. ci-roster-check PASS (159 reachable, 50 rostered); core-unit-check 347/335/12 PASS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cted twice Two hunks, both in .travis/test-jax.sh. The FILES array takes the UNION of both sides (PR #250's two files and this branch's one). The floor is re-derived from a collection RUN over the merged tree: 451/452 collected (1 deselected) from 31 files. Worth recording that this branch has now hit this conflict TWICE. 339 was itself a merged collection read off a real run, and it was stale inside a day -- so the constant does not go stale through carelessness, it goes stale because rift_O4d moves faster than any one branch. And BOTH times the arithmetic would have landed on the right answer (312+24+3 = 339, 424+27 = 451), which is exactly what makes it an unreliable shortcut: it is usually right, so the one time it is wrong there is no habit of checking left to catch it. rift_O4d rewrote 389 lines of samplers.py and 82 of the driver under this branch. Git merged them without conflict, which is not the same as correct, so the #227 work was re-checked against the merged tree rather than assumed: all eight regularize_cov call sites survive, the AST class gate finds no new cholesky-with-inline-identity, and all 27 pins pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion Fix JAX drop-in handling for Q pregrid option
…epted
The phi-local rule declined nearly every row that realized its own cost win.
Measured before this change, sweeping KP x amplitude, exactly ONE case in 36
both localized (area_outside > 0) and accepted -- at amplitude 100. Everything
above that localized into 4-14 regions and was refused.
THE OMITTED MASS WAS NEVER THE PROBLEM. At amplitude 3e4 the TRUE margin is
about -66 nats against a -23 threshold: the row deserved acceptance by a wide
margin. The computed margin was +2.7e5. The entire gap was one term.
M2F = M20 + M10^2 bounds Var(d_phi g) by its worst case -- as if d_phi g spread
over its whole range under exp(g) du. That measure CONCENTRATES as amplitude
rises, so the bound is loosest exactly where the physics is tightest, and the
Taylor lift M2F delta^2 / 2 sat five orders of magnitude above the integral.
Refining the grid cannot rescue it: n_bound * u_nodes both grow with amplitude,
and at 3e4 that is 8192 * 4 * 7302 evaluations -- the process is killed.
So the bound stops going through the profile at all:
F(phi) = log int exp(g) du <= log(2 pi) + max_u g(phi, u)
max_u g is exact -- four quartic roots u_stationary_roots already returns -- and
its Lipschitz constant is M10 by the envelope inequality, not M10^2, so
refinement is LINEAR. sup_g_bound costs no quadrature and is flat in amplitude.
required_bound_grid sizes the grid from M10 the way required_u_nodes sizes u.
VERIFIED SOUND rather than assumed: bound - F >= +1.15 nats over KP x amplitude
x phi, slack 2.8-5.5, which is the Laplace width log(sqrt(2 pi)/sigma_u) it
throws away. A test checks it directly against the profile.
Measured after, with both knobs sized:
KP=3 amp=1e3 5 regions margin -33.9 ACCEPTED err 1.1e-13
KP=3 amp=3e3 4 regions margin -58.7 ACCEPTED err 0.0
KP=5 amp=3e3 8 regions margin -24.9 ACCEPTED err 1.7e-11
KP=9 amp=1e3 8 regions margin -48.3 ACCEPTED err 1.1e-13
Localized, accepted, and right to machine precision -- the regime the cost
argument was always about, and which was unreachable before.
TWO REVIEW FINDINGS RETIRE BY CONSTRUCTION. The lift could be applied to a
profile the u fallback had underestimated; there is no profile on that grid now,
and a test asserts sup_outside is BIT-IDENTICAL across an 8x range of u_nodes.
And u_sizing_ok moves to the quadrature grid, where it belongs -- it reports
whether the integration that produced `value` was sampled adequately, not the
grid that bounds F from outside. It is non-vacuous there: it fires on a fixture
whose margin (-29.3) and resolution both pass at 48 u nodes.
The nested-grid test drops from four profile grids to THREE.
31 tests pass. Gate floor re-collected: 424 -> 426.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…had not done All three review findings reproduced before fixing, and each matched the report exactly. [P2] REQUIRED DEPENDENCIES COULD BE LABELLED OPTIONAL. _in_requirements compared the IMPORT name against requirements.txt's DISTRIBUTION names, so `needs:sklearn` sailed past a line saying `scikit-learn`, and `needs:lal` past `lalsuite` -- both installed by CI, both therefore not optional, both accepted. Reproduced for sklearn, lal and lalsimulation. The import name is now resolved to the distributions providing it, cheapest first: the metadata index, then a scan of each distribution's files for a top-level `<mod>/`, then a short alias table. The table is not laziness: on CIT `lalsuite` is a conda metapackage whose dist-info declares NO top-level modules (files() shows only __pycache__ and the dist-info), so nothing can resolve `lal` there, while a pip wheel does declare them. That limit is written down. Checked that no real roster entry changes verdict: jax, hydra, omegaconf, EOBRun_module, asimov, liquid, nflows and vegas all still report correctly absent from requirements. [P2] TIMEOUTS PASSED SILENTLY. The OPTDEP branch discarded both the collection and the run timeout and still counted the entry as checked. With TIMEOUT dropped to 1 s on an OPTDEP-only roster -- every subprocess timing out -- the old code reported "OPTDEP 8 checked ... PASS". It verified nothing and said so affirmatively, which is precisely the failure this file exists to remove. Both paths now error, saying that a timeout is not a pass. [P2] THE EXPENSIVE CHECK INHERITED THE OPT-IN FLAG. _pytest copied os.environ, so the run billed as "without RIFT_RUN_EXPENSIVE" kept it when the caller had it exported. That INVERTS the predicate: measured with the variable set, a correct guard gave rc=0 skipped=1 passed=3 and would have been reported broken, while an inverted guard would skip and be reported fine. _pytest now removes a variable when its value is None, and both EXPENSIVE subprocesses -- the run and the collection, since a module-level skip can change what is collected -- drop it. Same file now gives skipped=4 passed=0. Mutations, each broken and seen to fail: OPTDEP needs:lal -> FAIL "which requirements.txt DOES install" (passed before) OPTDEP needs:sklearn -> FAIL, same (passed before) OPTDEP collection timing out -> FAIL "a timeout is not a pass" (PASS before) And the direction that must NOT fail: the full roster passes both in a clean environment and with RIFT_RUN_EXPENSIVE=1 exported, which before this fix would have reported a false failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI: check that roster reasons are true, not just present
The guard compared the adapted estimate against the pilot's RAW estimate. The pilot is prior Monte Carlo -- unbiased, but with a heavy right tail once the target is a tiny fraction of the prior: for a mode of prior mass m one lucky draw gives ~L_max/n_pilot against a truth of ~L_max*m, overshooting by 1/(n_pilot*m). That same draw is what _moment_match centres the adapted proposal on, so an inflated reference and a well-seeded proposal arrive together, and a correct answer is converted to nan. MEASURED, not conceded on argument. At a synthetic width of 0.05 rad n_pilot*m = 1.6e-3 and the pilot ran up to +5.46 nats above the truth, P = 1.1e-3 over 900 seeds; +6.31 nats at 0.03 rad. The mechanism is real. The fix is prior_pilot_floor(): a Markov lower confidence bound. Markov needs only non-negativity and unbiasedness, both of which hold, so ln Zhat + ln rate is a floor at level 1 - rate. The threshold becomes a CHOSEN false-positive rate instead of a tuned constant, and it is distribution-free -- it does not assume the pilot resolved anything. That assumption would have been false: pilot ESS is ~1 (median 1.0-1.3) in every regime where the guard does any work, so "rests on one draw" is the pilot's normal state, not its failure state. rate = exp(-8), read off a measured operating curve over 5400 runs rather than asserted: against exp(-5) it costs 4.5 points of power (0.651 -> 0.606) and buys a 20x smaller worst-case false-positive rate. A false positive fails the event and writes no row, so it is worth paying for. Two alternatives were measured and rejected with numbers, not opinion. A bootstrap lower confidence bound loses too much power (misses 244 vs 148 inaccurate runs at 0.06 rad) -- publishing a wrong number is the failure this PR exists to stop. Gating on pilot ESS is worse than useless: every firing observed had pilot ESS < 5, so the gate would disable the guard exactly where it works. The test-design half of the finding was right without qualification. The narrow cases asserted isnan on EVERY seed, so a recovered, accurate answer would have been recorded as a regression -- a test that can only pass while the code fails cannot witness the guard being too aggressive, which is the risk under review. They now assert the property that matters: no inaccurate number is ever published, and an accurate one is explicitly allowed through. Added the regression case asked for, found by sweeping 5400 runs for the shape: sig=0.15 seed=885, pilot +1.35 nats above truth, adapted right to 0.001 nats at neff 1.4e4. Honest limit, stated in the test: over that sweep the largest pilot-minus-adapted gap on an accurate run was +1.654 nats, so no false positive was ever observed and this case does not reach the shipped threshold. It pins the margin; the Markov bound, not the sweep, is what the guarantee rests on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two P1s, one of which invalidates the certificate this PR exists to fix. SUP_G_BOUND RETURNED A LOWER BOUND WHEN THE U QUARTIC DEGENERATES. It took max over u_stationary_roots as if that set contained the maximizer. A max over candidates is a LOWER bound unless it provably does, and u_stationary_roots builds a companion matrix for c2 z^4 + ... and substitutes lead = 1 when c2 = 0 -- solving a DIFFERENT polynomial -- so for a table with no q = +-2 content its angles need not contain the maximizer. Measured over four such draws: 0.024 to 0.092 nats BELOW log(2 pi) + max_u g. Not a loose bound, an invalid one, and every margin in this PR rests on that inequality. a + |c1| + |c2| >= max_u g holds for every table and needs no roots, and in the degenerate regime it is also TIGHT because c2 -> 0 makes max_u g -> a + |c1|. It is used wherever the quartic cannot be trusted -- |c2| <= 1e-8|c1|, or an argmax that fails a stationarity test against the axis's exact derivative bound. Re-measured: +0.00000 on all four. Every table elsewhere in the suite carries full mode content, so nothing could see this. The regression test is the degenerate table itself. EMPTY MERGED-REGION SLOTS VOTED ON u_sizing_ok. Found by this session's review and by external review independently, and the external description was sharper: an empty slot is neutralized for the VALUE by zeroing its position and masking its WEIGHT, but its nodes are still evaluated at the artificial point phi = 0 and their fallback counts were summed with the rest. A risky cell there could decline a row whose every contributing node was adequate; the reported n_u_*_quad counters were contaminated the same way. The tell was that the counts tracked the SLOT ALLOCATION and not the regions: 5 risky at n_slots=2 rising to 176 at n_slots=8 while the region count only went 2 -> 4. So the test pins that the counters do not move once slots exceed regions, which no real structure could cause. Also: the docstring claimed this bound is "strictly better" than the profile route while disclosing eight lines later that it discards the Laplace width and sits ~6 nats high. Both cannot hold -- the profile bound converges to F under refinement and this one does not. Reworded, and the reason no affordable grid reaches the crossover is stated. seg_lo/seg_width are now exported, because the soundness check for this bound must compare it to the set it is a bound ON. Comparing to the GLOBAL sup of h reads w_sigma^2/2 = 72 nats low and condemns correct code, which is what my first attempt did. 33 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`multipeak_planner.multipeak_local_marginalize` was callable only from inside its own module: ANGLE_MARG_CHOICES did not list it and wrapper.py contained no reference, so no driver path could select the controller that was validated on the ladder-2 injection. This wires it as --angle-marg-scheme multipeak. Operating point is the measured one (tier0 (2,3,24), tier1 (3,5,48), tol 1e-3, cell_sigma 5, quadrature_order 7, refine_iterations 18), with log_measure derived by policy_log_normalization rather than chosen. Guard comes from the caller; it defaults to 16, the value the ladder measurement used, and the driver's production default is larger. The scheme OWNS the time integral, so it refuses return_lnLt instead of returning a wrong-shaped array, and no time_quadrature reaches it. The reserve is a PEAK-LOCAL time rule, not a refined whole window: nodes sit only in +-12 sigma_t windows about the envelope peaks at spacing sigma_t/8, so window and spacing scale together and the node count does not grow with rho. A refined whole-window reserve needed 4905 nodes for a peak 0.06 native samples wide at rho 163 and failed its own half-refined warrant at rho 40.77; this rule reproduced it to 0.0 on every row with 193 nodes. Defaults unchanged: multipeak is reachable by name only and choose_angle_marg_ scheme never returns it. Tests (7, wired into .travis/test-jax.sh in this commit, floor 754 -> 761): membership and export, absence from auto, the return_lnLt refusal, shape and finiteness, provenance, optparse choice validation through a subprocess, and a seam test that calls the REAL coefficient_table_distphipsimarg_laplace rather than a stand-in -- a fake there passes while the shipped call raises. Regression: 112 passed across the peak-local, default, buffer-cap and gh-selection wiring suites on /scratch/$USER/envs/jaxci-py311 (jax 0.10.2, cupy absent, CPU backend), ldas-pcdev13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…earch-projects-RIT into claude/angle-marg-multipeak
…ve-kernel-kwargs jax_ile: psi-Laplace reserve kernel called with the exact kernel's keywords
… EXPECTED_TESTS=762 measured Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ltipeak jax_ile: reach the four-axis multipeak controller from the driver
…ossterm-batching ILE precompute: batch the mode cross terms (26x on the cross terms, 3.6x ILE wall)
At rung 652 the peak-local reserve refused 3 of 8 rows on the focus certificate: the locator's search grid maximizes the angles on a 64-node phi lattice whose ripple, about rho^2 (pi/n_phi)^2 = 1000 nat at rho 652, exceeds the 25-nat change of the profile per search cell, so the search maximum landed up to 0.4 samples off, beyond the polish's reach; refining the time search did not help, and the (phi, u) Newton polish clipped at 0.1 rad x 3 steps sat 500 nat low. New PolicyConfig fields (static shapes, in the ledger): reserve_peaklocal_search_phi_nodes = 4096 (ripple under 1 nat to rho 1300), reserve_peaklocal_newton_steps = 8, reserve_peaklocal_newton_step_max = 1.0; locate_time_maxima takes newton_steps/newton_step_max. Test: a carrier at rho 632, tau 8, is located within tau/rho at the profile's true maximum. Measured (likedata_snr640.pkl, S=8 draw, guard 128, GH 16, decline forced): 8/8 rows warranted at the first tier, 357 nodes, 28 s/row; the refused rows are located (row 0 centre 307.108 vs the dense-lattice maximum 307.109). Rung 163: 8/8 unchanged to all printed digits at 9 s/row. DESIGN carries the sweep table and the before/after rows. EXPECTED_TESTS collapsed to the single measured value 763 (the #312/#313 merges had left three assignments). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ator-sizing jax_ile: size the peak-local locator's angular search from the amplitude (rung 652 usable)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conflict: .travis/test-slowrot.sh EXPECTED_TESTS. #307 raised 43 -> 47 (its test_slowrot_rotating_freqresponse.py), this branch raised 43 -> 46 (three block-form tests in test_slowrot_freqresponse.py). Neither number is the merged floor; re-derived by running the gate: 50. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…se-vectorize likelihood: build the finite-size response coefficients per block (tau_it,like 1707 -> 37 us)
JAX ILE: add value-only AV and portfolio sampling
oshaughnessy-junior
deployed
to
private-review-dispatch-rift-upstream
September 11, 2026 00:05 — with
GitHub Actions
Active
Resolve O4d rc3/rc4 CHANGES.rst conflict
oshaughnessy-junior
deployed
to
private-review-dispatch-rift-upstream
September 11, 2026 00:12 — with
GitHub Actions
Active
oshaughn
deployed
to
private-review-dispatch-rift-upstream
September 11, 2026 00:13 — with
GitHub Actions
Active
mebiri
pushed a commit
to mebiri/research-projects-RIT
that referenced
this pull request
Sep 15, 2026
mebiri
pushed a commit
to mebiri/research-projects-RIT
that referenced
this pull request
Sep 15, 2026
mebiri
pushed a commit
to mebiri/research-projects-RIT
that referenced
this pull request
Sep 15, 2026
…p-kernel-bounds fit_gp: make the kernel bounds settable, and report when a fit is saturated
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.
Summary
Bring
oshaughn/rift_O4dat the0.0.18.0rc3release mark up to the accumulated work onoshaughnessy-junior/rift_O4d. The rc4 version bump is deliberately deferred until this upstream PR has merged.oshaughn/rift_O4d@a93e41f7oshaughnessy-junior/rift_O4d@5e55d030+37,905 / -1,157); the fork is one release-note/version commit behind the base.Delivery history
This is the next catch-up after #170 (the rc2 integrator/calmarg/convergence payload), #173 (O4c/master consolidation), #178 (the post-consolidation fork sync), and #188 (the rc3 sync). The fork merge log shows this payload begins with the post-#188 JAX/CI work at #245--#248 and continues through #319; those earlier deliveries are already represented by the rc0--rc3 entries below and are not re-released here.
Payload by subsystem
New JAX sampling and direct-marginalization capabilities are opt-in. The payload does change failure behavior intentionally: an integration whose evidence is non-finite or fails the guard is not written as a normal successful result.
CHANGES.rst conflict
The only merge conflict is
CHANGES.rst: the base supplies the concise rc0--rc3 release history while the fork retains earlier detailed development notes. The resolution keeps the base history, adds a compactrc4 pendingsummary for this payload, and keepssetup.pyat0.0.18.0rc3.oshaughnessy-junior/rift_O4dis protected, so the resolution is supplied as fork PR #320. Merge #320 first; it updates the head branch and clears this PR conflict without bypassing branch protection.Release follow-up
After this PR merges, update
CHANGES.rstandsetup.pytogether from rc3 to0.0.18.0rc4, then run the selected release validation.Verification
git diff --check.