Align pitch crossings to the nearest grid point - #164
Conversation
There was a problem hiding this comment.
Review verdict: Request changes
Review: PR #164 — Align pitch crossings to the nearest grid point
Summary: This PR refactors the fix_phiplacement_problem logic in the QL ripple solver test files by extracting the "nearest grid point" selection into a new pure function nearest_phi_crossing_offset in phi_crossing_alignment_mod.f90. The refactor fixes latent out-of-bounds array accesses at the ibeg/iend boundaries, makes the alignment always apply (including at the center point, which the old code skipped), and adds a unit test. Only the NEO-2-QL test variants are changed; the COMMON/plagrange_mod.f90 and NEO-2-PAR copies of this routine are untouched.
Findings:
-
[major]
COMMON/plagrange_mod.f90:1127/NEO-2-PAR/ripple_solver.f90:2989— The samefix_phiplacement_problemroutine exists in the PAR variant (via COMMON) and is not updated. The old code there still accessesbhat_mfl(istep-1)andbhat_mfl(extracted)without bounds checks. Sinceicross_lcan containistep=extractedandicross_rcan containistep=extracted, this means out-of-bounds reads onbhat_mfl(ibeg-1)andbhat_mfl(extracted)are still present in the PAR path. If the intent is to fix the boundary access bug, the COMMON/PAR copy should be updated too (or a note added that this is intentionally QL-only). If left as-is, the PAR variant retains the latent bug. -
[minor]
NEO-2-QL/ripple_solver_ArnoldiOrder2_test.f90:5352/ripple_solver_axi_test.f90:3810— Behavioral change: the old code only appliedbhat_mfl(aligned) = 0/etaand wrote to the log when a neighbor (offset ±0) was closer than the center. The new code always applies the fix and writes the log, including when offset=0 (the center point is closest). This meansbhat_mfl(istep)is now unconditionally set to0/etaeven when it's already the nearest, and the log file receives entries that the old code would have omitted. This is likely intentional (more consistent alignment), but it will change numerical output and may require golden record updates — CI should catch this. -
[minor]
TEST/test_phi_crossing_alignment.f90— The test covers four scenarios (center closest, preceding closest, following closest, nearest-neighbor edge case) and uses hardcodedreal64constants. The test is adequate for thepurefunction. One nit:error stop 'FAIL: ...'messages will be caught by theFAIL_REGULAR_EXPRESSION "FAIL"in CMakeLists, which is correct, but the test would also benefit from printing the actual vs expected offset for debugging when it fails.
Verdict: Comment — The refactoring is clean and the pure function is well-designed with good test coverage. The major concern is the inconsistency: the same fix is not applied to the COMMON/PAR copy of this routine, leaving a latent out-of-bounds bug in the PAR path. The behavioral change (always applying the fix) is reasonable but should be confirmed against golden records. If the PR scope is intentionally QL-only, that should be stated; otherwise, the COMMON copy should be updated in the same PR.
There was a problem hiding this comment.
Review verdict: Approve
Review: PR #164 — Align pitch crossings to the nearest grid point
Summary: This PR extracts the phi-crossing alignment logic (formerly inline if/elseif blocks in fix_phiplacement_problem) into a new phi_crossing_alignment_mod containing the pure function nearest_phi_crossing_offset. The function picks which of {istep-1, istep, istep+1 has bhat*eta closest to 1 and returns the offset. The QL test files (ripple_solver_ArnoldiOrder2_test.f90, ripple_solver_axi_test.f90) now call this function, and a new unit test exercises it across four scenarios. The commit message ("Align pitch crossings to the nearest grid point") confirms this is a deliberate behavioral fix, not just a refactor.
Findings:
-
[minor]
COMMON/plagrange_mod.f90:1207,1218,1286,1295— The PAR variant'sfix_phiplacement_problem(inCOMMON/plagrange_mod.f90, compiled into thecommonlibrary and called fromNEO-2-PAR/ripple_solver.f90:2989) still uses the old inlineif/elseiflogic and1was not updated. This means QL and PAR now diverge in phi-placement behavior: QL always setsbhat_mfl(aligned_step) = 1/eta` (including when the crossing point itself is best, which the old code silently skipped), while PAR only updates a neighbor when it beats the center. If intentional (QL-only scope), consider a follow-up to bring PAR in line; if unintentional, the PAR golden-record test in CI may diverge. -
[minor] Behavioral change — always-modify semantics: In the original code, when neither neighbor had a smaller residual than
bhat_mfl(istep), no modification or file write occurred. The new code always writes tophi_placement_problem.datand always setsbhat_mfl(aligned_step), even whenaligned_step == istep(offset 0). This produces more diagnostic entries and overwrites the crossing point itself with0/etawhen it is the best match. This appears to be the intended fix (the old code left the crossing misaligned when it was already closest), but it does change runtime output. The CI golden-record tests usecontinue-on-error: true, so a behavioral drift would be reported as a test failure — worth confirming the golden records were regenerated or are expected to pass with the new behavior. -
[minor] Test coverage: The unit test covers offset 0, −0, +0, and a mixed nearest-neighbor case. Good coverage for a pure function. One gap: there's no explicit test for the boundary-safe fallback behavior (when
istep == ibegoristep == iend, the function should never return an offset that goes out of thebhat_mfl(ibeg:iend)array). The caller code handles this by conditionally settingbhat_crossing(±1)8only when in bounds, but a test that verifies the function behaves correctly withbhat(-1) == bhat(0)(boundary fallback) would strengthen confidence.
Verdict: Approve — The refactoring is clean, the new pure function is correct, and the behavioral change is a deliberate fix confirmed by the commit message. The COMMON/PAR inconsistency is a scope decision worth tracking in a follow-up but does not block this QL-focused change. The unit test and CMake registration are correct.
Summary
Complete the existing pitch-crossing placement rule by aligning the nearest of
the preceding, current, and following spatial samples to each exact
trapped-passing boundary. The previous rule considered both neighbours but did
nothing when the crossing sample itself was nearest. It could also prefer the
preceding sample without comparing it with a still-closer following sample.
For the band-integrated unknown
[
Y_k=\int_{\eta_{k-1}}^{\eta_k} f,d\eta,
]
a constant distribution is represented by the band width. In the pinned
480-step, 30-band case, propagator 192 changed from 27 to 26 passing bands
between steps 679 and 680. The old placement rule left step 680 unaligned even
though it was the nearest sample. The counter-passing stencil therefore coupled
a width of
3.8755753453293407e-2to3.8419219196663246e-2. Their difference, multiplied by the streamingcoefficient, produced the complete
5.1395606791432513e-2constant-state rowresidual. The collision terms already cancelled.
The shared selector now performs a true three-point minimum. Both QL solver
variants apply the selected offset, including offset zero. Endpoint crossings
duplicate the current sample for an unavailable neighbour, so the new selector
does not introduce an out-of-bounds access.
This PR is stacked on #163. It uses the opt-in stage trace from that PR to prove
the correction before the diagnostic stack is removed or consolidated.
Preserved invariants
step count, and pitch grid are unchanged.
fis restored as the discrete right null of each single-ripplesparse operator.
periodic boundary conditions are unchanged.
propagator layout are unchanged.
bhat_mflvalue per crossing to1/eta_cross; the fix includes the currentsample and selects the genuinely nearest sample.
The intended sample alignment changes the two reference qflux responses by
-2.22575e-4and-1.53849e-4relative. Their point-sum closure remains atroundoff.
Verification
Test fails on the stacked base
At
8bf3c29, the pinned full reconstruction exposes the unresolved constantstate:
The focused selector regression also fails with the old current-point omission:
Test passes after fix
At
f9b0062, the same pinned reconstruction completes with eight adaptivesolver retries, the same count as the stacked base:
A separate retained stage-0 map trace at the same head verifies the extracted
boundary map after the direct sparse solve:
The retries keep the failure-free resolution campaign open; they do not affect
the before/after localization of the constant-state defect.
Post-fix convergence status
The localized nullspace fix is necessary but does not establish convergence of
the physical response. A pinned downstream campaign at this head completed at
1920, 3840, and 7680 spatial steps without adaptive recoveries. The local
sparse and solved constant-state residuals, right and left transport, projector
intertwining, qflux reconstruction, and periodic rows remain at floating-point
scale through 7680. Nevertheless, the two harmonic-response components change
by 0.0768% and 0.0612% at the first doubling, then by 4.05% and 3.07% at the
second. An
epserr_iterscan from1e-4through1e-6is bitwise identical,so iteration tolerance does not explain the change.
A point-resolved 3840--7680 comparison also rules out one isolated propagator.
For each response component, 127 of 199 propagators are required to accumulate
90% of the change, no propagator supplies more than 1%, and the signed change
agrees with the sum of absolute changes to better than
3e-10. Theper-propagator pitch-topology signatures and the crossing-boundary
etasequence are unchanged.
The remaining leading risk is the pre-existing placement operation, not the
three-point selector introduced here.
fix_phiplacement_problem_oldreplacesonly
bhat_mfl(aligned_step)by1/eta_cross; it leavesphi_mfl,geodcu_mfl,h_phi_mfl, anddlogbdphi_mflsampled at the original point.The selected point moves under spatial refinement. This identifies a
geometry-consistency risk but is not yet a causal proof. A follow-up must derive
either a root-aligned grid or a consistent resampling rule and preserve the
physical field, pitch-band measure, constant-distribution right null, particle
left null, source quadrature, mirror and periodic boundaries, units, ABI, and
serialized layout.
Accordingly, this PR remains the focused correction for the current-point
omission and true nearest-sample selection. It is stacked on #163 and should
remain unmerged while the geometry-preserving follow-up is derived and the
three-level spatial and pitch screens are repeated. No physical current profile
is accepted from the present campaign.
Field-preserving follow-up contract
A subsequent 30/60/120-band scan rejects the physical response even though the corrected local algebra remains close to roundoff. From 60 to 120 bands, the two response components change by
3.34556e-3and3.84088e-3, above the1e-3gate. The 60-band run needs four adaptive recoveries, and the 120-band solved-constant residual is2.22873e-11, above its1e-11tolerance. No response profile is accepted from this PR.An independent symbolic derivation now fixes the contract for the follow-up. For each pitch boundary, solve the simple bracketed root
1 - eta B(phi*) = 0and move an interior spatial node tophi*; do not overwrite onlyBat a fixed point. Evaluate all primitive field, metric, drift, and derivative data at the same root, then recompute every dependent coefficient and nonuniform cell factor. The pitch-band measure must remain unchanged. A lost or non-simple root, or two crossings competing for one node, requires refinement or rejection. A fixed-node-count move preserves the current ABI and serialized propagator layout; insertion requires an explicit format migration.The derivation verifies the crossing and derivative identities, vanishing mirror factor, unchanged pitch measure, conservative nonuniform flux telescoping, constant-source quadrature, periodic endpoints, unit scaling, and crossing-conflict rejection. Implementation still must re-establish the constant-distribution right null, particle left null, mirror and periodic rows, source quadrature, qflux, and three-level spatial and pitch convergence. This PR remains a narrowly scoped nearest-sample correction and must not be treated as the geometry-preserving follow-up.