Skip to content

Move bounce_integral event search onto fortnum and drop vode#47

Draft
krystophny wants to merge 10 commits into
mainfrom
migrate/fortnum-ode-events-drop-vode
Draft

Move bounce_integral event search onto fortnum and drop vode#47
krystophny wants to merge 10 commits into
mainfrom
migrate/fortnum-ode-events-drop-vode

Conversation

@krystophny

@krystophny krystophny commented Jun 14, 2026

Copy link
Copy Markdown
Member

Stacked on #46. Replace the bounce_integral event search (DVODE_F90 with
bounceroots, NEVENTS=2) with the fortnum vode Adams integrator monitoring the
same two event functions, and drop the now-unused vode dependency.

Call site

  • src/orbit.f90 bounce_integral: vode_integrate_to with two event
    functions (event + event2),
    • root_theta: g1 = theta - th0 (trapped return to th0),
    • root_turn: g2 = 2pi - (theta - th0) (passing full +2pi turn),
      advancing in dt-sized windows, continuing the integration across rejected
      turns (DVODE istate=2), so taub is the integrator-located root. Acceptance
      mirrors the DVODE filter: accept when passing, or when the previous window
      endpoint had theta below th0.
  • CMakeLists.txt: drop find_or_fetch(vode) and the vode link/include; the
    fortnum pin from Move non-event orbit integrations onto the fortnum ODE solver #46 (main 974dcf1) carries over.

Golden record regenerated and RK4-validated

The golden was regenerated from this branch and the old DVODE golden retired,
after an independent reference confirmed the DVODE values were wrong on the
event path. Details in test/golden_record/REGENERATION.md.

Root cause: NEO-RT builds the canonical poloidal-frequency spline in
init_canon_freq_trapped_spline by calling bounce_time on a 100-point eta
grid at v = vth. At the nodes nearest the separatrix the DVODE chunked event
search, seeded by the taub_estimate chained from the previous node, stepped
past the first theta=th0 return and accepted the second or third full-period
crossing. The stored DVODE taub at those nodes is an integer multiple of the
fundamental bounce period. fortnum returns the fundamental.

Validation: a fixed-step RK4 of the poloidal-motion ODE, ~2e6 substeps per
fundamental period, the bounce event bracketed at every theta=th0 crossing and
root-polished by RK4 sub-bisection. Converged at 2e6, 16e6, 64e6 substeps. At
every node where DVODE and fortnum disagree above 1%, fortnum/fundamental is
0.99 to 1.00 and DVODE/fundamental is 2.00 (5.00 at 0p900 k=99):

case trapped DVODE multiple-period nodes (k: DVODE/fundamental)
0p100 84: 2.00x
0p200 69: 2.00x
0p300 none
0p400 43: 2.00x, 91: 2.00x, 99: 2.00x
0p500 none
0p600 57: 2.00x, 99: 2.00x
0p700 8: 2.00x, 99: 2.00x
0p800 76: 2.00x, 79: 2.00x, 80: 2.00x, 98: 2.00x
0p900 92: 2.00x, 95: 2.00x, 97: 2.00x, 99: 5.00x

Passing orbits and non-separatrix trapped orbits match DVODE to ~1e-6 and both
match RK4. No case showed fortnum as the outlier, so none was held back.

Per-case transport, DVODE golden vs fortnum (magfie bit-identical; the shift is
the trapped diffusion channel that carries the multiple-period nodes):

case D11 DVODE D11 fortnum d% D12 DVODE D12 fortnum d%
0p100 4.66479e-03 4.63838e-03 -0.57 1.39282e-02 1.38751e-02 -0.38
0p200 1.00903e-03 1.05268e-03 +4.33 1.33994e-03 1.43353e-03 +6.98
0p300 4.05668e-04 3.99458e-04 -1.53 5.34070e-04 5.23870e-04 -1.91
0p400 2.40201e-03 2.38122e-03 -0.87 8.41916e-03 8.39316e-03 -0.31
0p500 6.39491e-04 6.39403e-04 -0.01 2.21908e-03 2.21904e-03 -0.00
0p600 3.22354e-03 3.22456e-03 +0.03 1.25500e-02 1.25507e-02 +0.01
0p700 2.42497e-02 2.25794e-02 -6.89 5.77027e-02 5.31353e-02 -7.92
0p800 4.03095e-03 3.96635e-03 -1.60 1.10415e-02 1.07758e-02 -2.41
0p900 3.04954e-03 2.94211e-03 -3.52 1.05836e-02 1.02717e-02 -2.95

Verification

golden.h5 is now a committed artifact built CONFIG=Fast from this branch.
ensure_golden.py uses a committed golden as-is and regenerates from main only
when none is present. The comparison logic and tolerances in
test_golden_record.py are unchanged.

test/golden_record/test_golden_record.py, CONFIG=Fast, bar rtol=1e-8
atol=1e-15:

9 passed

Before this commit, against the old DVODE golden: 9 failed, output rel up to
~53%, integral rel up to ~4.0.

Note: fortnum pin updated to current main (92de6e9) after a fortnum history rewrite; old shas no longer resolve.

Wire the fortnum numerical-core library via CMake FetchContent
(git@github.com:lazy-fortran/fortnum.git, pinned to a7faa3c) guarded by
if(NOT TARGET fortnum) so a parent build that already provides the target
is reused. Link the static library into neo_rt; its module directory is
exported PUBLIC, so neo_rt and the diagnostics library that links neo_rt
both see the fortnum modules.

This commit adds the dependency without changing any solver call site; the
existing DVODE path is untouched and still in use. Subsequent commits move
the orbit integrations onto the fortnum ODE solver and then drop DVODE.
@krystophny krystophny force-pushed the migrate/fortnum-ode-nonevent branch from 14ac98c to 4c1cf84 Compare June 14, 2026 09:32
@krystophny krystophny force-pushed the migrate/fortnum-ode-events-drop-vode branch 2 times, most recently from 6904b2a to 93343cb Compare June 14, 2026 09:56
@krystophny krystophny force-pushed the migrate/fortnum-ode-nonevent branch from 4421b9c to 08acde3 Compare June 14, 2026 10:17
@krystophny krystophny force-pushed the migrate/fortnum-ode-events-drop-vode branch from 93343cb to 21becd7 Compare June 14, 2026 10:20
Replace the DVODE calls in the two fixed-interval, no-event integrations
with the fortnum DOP853 solver:

- orbit.f90 bounce_fast: ode_solve_dop from 0 to taub, take the final
  recorded state. The fortnum status maps onto the legacy istate the
  transport caller branches on (2 success, -1 step budget exhausted, 0
  other), so dvode_error_context still fires on the exhausted-budget case
  and the public bounce_fast signature is unchanged.
- diag/diag_bounce_debug.f90 probe_bounce: ode_integrate_dop, then read
  solution%nsteps, solution%nrejected and the last solution%h into the
  diagnostic columns the scan previously filled from get_stats.
- freq.f90: drop the vode_thread_init shim. The fortnum integrator keeps
  no module-level state, so freq_thread_init has nothing VODE-specific to
  initialise.

The event-based bounce_integral and the vode dependency itself stay until
the next commit so this change is reviewable on its own.
Two defects surfaced once bounce_fast ran on fortnum DOP853 instead of
DVODE, both caught only by the CI Debug build (gfortran 13,
-ffpe-trap=zero,overflow,invalid,underflow):

- timestep_transport left ydot(7), the unused abs(B) slot, uninitialised.
  DVODE ignored the stray derivative, but DOP853 carries every component
  through its stage combinations, so the leftover denormal tripped the
  underflow trap inside dop853_step. Zero the trailing integrands the
  routine does not compute, matching what timestep already does.

- The bounceavg(3:4) perturbed-Hamiltonian integrands oscillate at the
  resonant harmonic but are driven, not fed back into the orbit, so the
  rtol error estimate barely constrains them. The unconstrained adaptive
  step grew too coarse and the oscillatory integral came out wrong (D11
  off by an order of magnitude). Drive bounce_fast through
  ode_integrate_dop with hmax = taub/200 to keep the step fine enough;
  this recovers the DVODE result to four significant figures.
@krystophny krystophny force-pushed the migrate/fortnum-ode-nonevent branch from 08acde3 to b9718a3 Compare June 14, 2026 14:31
@krystophny krystophny force-pushed the migrate/fortnum-ode-events-drop-vode branch from ead488c to 26ee48f Compare June 14, 2026 14:31
@krystophny krystophny changed the base branch from migrate/fortnum-ode-nonevent to main June 14, 2026 18:26
@krystophny krystophny closed this Jun 14, 2026
@krystophny krystophny reopened this Jun 14, 2026
Replace the DOP853 substitution in bounce_fast with fortnum vode
(DVODE MF=10 nonstiff Adams), relative tolerance 1e-9 and a per-component
absolute tolerance 1e-10 (DVODE ITOL=2), over the single bounce span. This
restores the variable-order method NEO-RT drove through DVODE before the
migration, so the oscillatory bounce-average integrands no longer need the
artificial DOP853 step cap. Re-pin fortnum to the commit that adds the
two-event root support the bounce_integral event path needs.
Replace the DVODE_F90 bounce_integral event search with the fortnum
nonstiff Adams integrator monitoring two event functions (NEVENTS=2):
theta returning to th0 and theta advancing by 2*pi. The integrator
locates the earliest root on its Nordsieck interpolant, so taub is the
located root at relerr 1e-9 and per-component abserr 1e-10. Reproduce
the DVODE acceptance filter: keep a root when passing or when theta
entered from below th0. Drop the now-unused vode dependency.
@krystophny krystophny force-pushed the migrate/fortnum-ode-events-drop-vode branch from 26ee48f to 781143d Compare June 15, 2026 15:25
The DVODE golden returned an integer multiple of the fundamental bounce
period at near-separatrix trapped spline nodes. Building the canonical
poloidal-frequency spline calls bounce_time on a 100-point eta grid; at
the nodes nearest the separatrix the DVODE chunked event search, seeded
from the previous node, latched onto the second or third theta=th0
return instead of the first. fortnum returns the fundamental period.

A 2e6-substep fixed-step RK4 of the poloidal-motion ODE (converged at
2e6, 16e6, 64e6 substeps) confirms the fundamental at every mismatch
node: fortnum/fundamental is 0.99 to 1.00, DVODE/fundamental is 2.00
(5.00 at 0p900 k=99). Passing orbits and non-separatrix trapped orbits
match DVODE to ~1e-6. fortnum passes RK4 validation for all 9 cases.

golden.h5 is now a committed artifact built CONFIG=Fast from the fortnum
branch. ensure_golden uses a committed golden as-is and regenerates from
main only when none is present. The comparison logic and tolerances in
test_golden_record.py are unchanged; it passes for all 9 cases at
rtol=1e-8 atol=1e-15.

REGENERATION.md records the per-case OLD-vs-NEW-vs-RK4 evidence.
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.

1 participant