ForceFreeStates - BUGFIX! - Use a per-column absolute tolerance in the Riccati shooting integration - #413
Conversation
… integration The shooting solves batched all columns under one uniform absolute tolerance (the ODE library default, 1e-6), so the resonant big-solution column set the error floor of the small-solution column and the extracted Δ' inherited an absolute error that scaled with the matching radius (dpsi^~2α). Scale abstol per column to the column's own magnitude, mirroring the reference implementation's per-column atol policy. Δ' becomes matching-radius independent: on the TJ circular benchmark the 3/1 diagonal moves 0.909 → 1.054 at the default singfac_min=1e-4, in agreement with the Galerkin path (1.057), Fortran STRIDE (1.05), and TJ (1.067); the 2/1 and all shaped-case surfaces move ≤0.1%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scan-scale validation: full TJ β + ε scans on this branch, both grids — 196/196 runs, zero failuresA/B against the frozen develop scans (same equilibria, same settings;
Exactly the bug's fingerprint: the 2/1 (|Δ'| ~ 7–22) is untouched at the 0.1 % level across all 196 points, and the movement is confined to the small ε-scan 3/1 elements (|Δ'| ~ 0.9–1.5) where the absolute error floor lived. Closure against the independent reference: re-cutting the K^(2μ)-corrected comparison with this branch's data, the ε-scan 3/1 median |difference from TJ| drops 5.7 % → 2.3 % (spread [−14.6 %, +0.4 %] → [−6.3 %, +5.4 %]) — the previously "unexplained low-ε 3/1 drift" is now confirmed to have been this bug. The 2/1 panels are unchanged (median 1.4 %). Scan runner + data: 🤖 Generated with Claude Code |
|
Forced-rerun verification of the regression stamp — CORRECTED. An earlier version of this comment claimed the gamma_Hz residual of 1.623e-01 Hz (0.02%) "reproduces exactly, four orders of magnitude above the measured sample-noise ceiling — it is mechanism." That claim was wrong and is withdrawn. The ceiling it relied on (1.5e-05 Hz) came from two forced samples that happened to land in the same solver state. The actual same-source reproducibility floor, measured by comparing a single commit against itself across forced re-runs (layer-overlap investigation, tracked in #418), is a discrete per-surface step of ~0.08–0.15 Hz in gamma_Hz — the threaded AMR root search lands in one of two nearby states per surface. The 0.16 Hz residual on this PR is the same magnitude as that step, so the measurement cannot distinguish the Delta-prime mechanism from the floor; "reproduces to four digits across two pairs" is consistent with landing in the same state four times. What still stands: the structural single-channel argument (the entire source diff is The cached-vs-forced ω residual (4.3e-03 → 1.8e-03 Hz) is likewise at or below floor. All layer inputs remain bit-identical. The stamped table stands as a record of what the harness reported; its gamma line should be read as "at the per-surface jitter floor." Follow-ups: the root-search nondeterminism itself is tracked in #420 (seeded/serial candidate ordering + an in-process run-twice reproducibility test); until it lands, the |
|
This pull request is missing a reviewer. If you are not ready to name them, mark this pull request as a draft. |


Release note
singfac_minmove at the 0.00–0.01 % level (float-level tightening); at smallersingfac_minthe previous results were biased low (3/1 Δ' on the DIII-D-like case: 0.909 at 1e-4 rising to a 1.055 plateau by 1e-6 after the fix, 4-way consistent with Galerkin, Fortran and TJ). SLAYER growth rates at defaults change by 0.02 %, which is at the per-surface reproducibility floor of the threaded AMR root search (~0.1 Hz discrete steps, see Regression - BUGFIX - Fix two silent failure modes: PATH julia resolution and single-sample result caching #418); attribution of that residual to the Δ' shift is plausible but not resolved by the harness measurement. (harness @ 362662b)singfac_min(below 1e-4) should be regenerated; values at the default move within 0.01 %.The Riccati shooting batches were solved with a relative tolerance only, so DifferentialEquations' uniform default absolute tolerance (1e-6) let the large column of each batch set the error floor of the small column, biasing Δ' low by an amount that grew as the singular-surface exclusion radius shrank. Each column now carries its own absolute tolerance scaled to its initial magnitude, making Δ' independent of
singfac_minto 0.07 % per decade. This is a tightening of the integration tolerance, not a relaxation.Regression report
regress --cases diiid_n1_riccati,diiid_slayer_n1 --refs c42d558e,362662b9(branch head 362662b is the develop-merge carrying #339/#399; c42d558 is its develop side, so the diff isolates the Riccati.jl fix. Same pinned manifest, julia 1.11.6):Energies, singular-surface locations and ODE step counts are bit-identical: the fix changes only the per-column absolute tolerance of the shooting batches (a tightening; no tolerance was loosened). The only flagged SLAYER quantity is γ_Hz at 0.02 %, which sits at the root search's own per-surface jitter floor (~0.1 Hz) and should not be read as a resolved mechanism; every layer input is bit-identical and the ψ_N-referenced Δ' moves 0.00–0.01 % on deterministic BVP quantities.
Fixes #412.
Root cause
integrate_fm_with_ua_icsolved each shooting batch withreltolonly, leaving the ODE library's uniform defaultabstol = 1e-6in force across columns spanning ~10⁻³ (resonant small solution) to ~10³ (resonant big solution). The batch's largest column therefore set the absolute error floor of its smallest, and the extracted Δ' inherited an absolute error proportional to the big solution's magnitude at the matching radius — decaying only assingfac_minshrinks (measured ≈ dpsi^1.2, close to the Frobenius exponent gap 2α = 1.0), and invisible to grid-resolution convergence scans. The reference implementation avoids this by construction: it rebuilds a per-columnatolfrom each column's own magnitude every step, so its Δ' shows only −1.4 % sensitivity over the same singfac decade where this code moved −13 %.Fix
11 lines: build a per-column
abstolarray scaled to each column's own magnitude (max|column| × reltol) for both shooting batches, mirroring the reference implementation's per-column policy. This tightens error control (replacing an implicit uniform 1e-6 floor); no criterion is relaxed. A structural alternative (confining the ua IC to the surface-adjacent chunk, composing the rest from identity-IC propagators) was implemented and tested first: it only half-fixed the benchmark and regressed the small-radius plateau, so it was reverted — error control, not integration structure, was the load-bearing difference.Evidence (TJ circular benchmark, ε-scan point, ldp mpsi=256, n=1)
Δ' is now matching-radius independent (0.07 %/decade, matching the reference implementation's behavior). The full diagnostic chain (Galerkin cross-check, Fortran/TJ corroboration, per-surface singfac scans, sing_order irrelevance proof, line-by-line series/BVP audits) is in #412.
Validation
diiid_slayer_n115/15 unchanged;diiid_n1_riccatiΔ' diagonal/raw/coil move 0.00–0.01 % (float-level tightening shifts);gal_resistive_diiid0.01 %. Branch since rebased onto develop (InnerLayer.SLAYER - BUGFIX! - Use the physical toroidal field, not the b0exp normalization #399); the fix touches onlyRiccati.jl, which InnerLayer.SLAYER - BUGFIX! - Use the physical toroidal field, not the b0exp normalization #399 does not.What moves downstream
Small Δ' matrix elements (|Δ'| ≲ 1–2) computed at the default
singfac_minwere biased low by up to ~0.15 absolute; they now agree with the Galerkin path and external codes. Large elements move ≤ 0.1 %. Cases whose tearing analysis hinges on small/near-marginal Δ' should be re-examined after merge.Important
This changes the effective error-control semantics of the Δ' shooting integration (tightening an implicit library default). Do not merge without human review.
🤖 Generated with Claude Code