Skip to content

Use implicit midpoint for SPECTRE field lines#483

Merged
krystophny merged 1 commit into
mainfrom
perf/spectre-fieldline-midpoint
Jul 13, 2026
Merged

Use implicit midpoint for SPECTRE field lines#483
krystophny merged 1 commit into
mainfrom
perf/spectre-fieldline-midpoint

Conversation

@krystophny

@krystophny krystophny commented Jul 13, 2026

Copy link
Copy Markdown
Member

Risk tier

  • T0: docs, comments, small build metadata
  • T1: pure refactor, no behavior change intended
  • T2: local numerical logic
  • T3: physics, output behavior, coordinate convention
  • T4: higher-risk build or runtime logic

Correctness contract

Intended behavior change

Advance the SPECTRE canonical field-line pair with the second-order implicit midpoint rule. Use libneo's first-derivative-only vector-potential evaluation in the midpoint residual and endpoint-radius solve. The Poincare app builds a cubic hot-loop spline for one volume at a time and can read the same explicit seed file as the native SPECTRE tracer.

Behavior that must not change

Field lines remain confined to their SPECTRE volume. Section orientation, rotational-transform sign, vector-potential representation, and the psi = A_theta canonical momentum definition remain unchanged.

Coordinate / unit conventions

The map still uses local SPECTRE s in [-1,1], poloidal angle theta, and toroidal angle zeta; angles are dimensionless radians. The spline is periodic over 2 pi in theta and 2 pi/Nfp in zeta. The implicit solve advances (theta, psi) with zeta as time and recovers endpoint s from A_theta(s, theta, zeta) = psi.

Numerical invariants

Implicit midpoint preserves the canonical symplectic form and is self-adjoint. A forward step followed by the same negative step returns (s, theta, zeta, psi) within 2e-11. The midpoint residual retains the 1e-13 tolerance. Long traces solve for a local angle increment to avoid subtracting large unwrapped angles.

The hot-loop spline represents A_theta and A_zeta; all derivatives in the midpoint residual come from those same interpolants. The map is therefore symplectic for one internally consistent approximate Hamiltonian. The spline is constructed before a volume trace, read only while tracing, and destroyed before the next volume.

Tests added

  • unit: forward/backward midpoint reversibility on the committed SPECTRE fixture
  • integration: second-order step halving, explicit seed preservation, confinement, section geometry, rotational-transform bounds, and three consecutive per-volume spline lifetimes
  • system: complete fo pipeline
  • golden record: existing SIMPLE orbit records are outside this app-only field-line path

Golden-record impact

  • unchanged
  • changed

Failure modes considered

The midpoint solve rejects a singular numerical Jacobian or failure to meet its residual tolerance. The endpoint-radius Newton solve remains separately checked. The angle-increment formulation avoids loss of residual precision after hundreds of unwrapped turns. Spline grid dimensions must exceed the cubic order.

Manual validation

A Profile build on tok_island attributed 98.3% of the post-midpoint run to direct vector-potential evaluation, which motivated the per-volume spline. The profile used 45 seeds, 50 toroidal transits, and 32 steps per field period.

On the same 45 seeds and 500 transits, the final 32-step midpoint+spline run completed with zero terminations in 11.19 s and 57 MiB peak RSS. SPECTRE's native CyRK tracer took 11.35 s on the same host. Relative to the native section, the outer-volume errors were median 5.90e-4 m, 95th percentile 6.80e-3 m, and maximum 1.70e-1 m. The stored 512-step first-order run took 324 s and has median 3.94e-4 m, 95th percentile 3.15e-2 m, and maximum 4.39e-1 m.

Before the spline, the 32-step midpoint run took 50.0 s. Its outer-volume errors were median 4.83e-4 m, 95th percentile 3.36e-3 m, and maximum 2.92e-2 m.

Verification

Failing before, with the second-order convergence test run against main:

BDD4: shift 256->512 = 1.212e-04, 512->1024 = 6.021e-05
FAILURES:
  BDD4: shift not converging 6.021e-05 vs 1.212e-04

Passing after:

BDD4: shift 256->512 = 4.248e-07, 512->1024 = 1.062e-07
BDD5: explicit field-line seeds preserved
test_field_line_midpoint Passed
Static: OK (175 modules, 175 changed, 175 affected)
Build: OK
Tests: OK
Lint: OK
Fmt: WARN (two touched Fortran files)
All stages passed

The dependency-pin failure was also reproduced before the final push:

Error: Keyword argument ‘second_derivatives’ is not in the procedure

After advancing GOLDEN_LIBNEO_REF to merged libneo commit 18b91aabffcc3c8833648f01a057e278809c15f2, the exact pinned deterministic configuration built all 1,153 targets successfully. The final bare fo gate then passed 175-module static analysis, build, tests, and lint.

@krystophny krystophny enabled auto-merge (squash) July 13, 2026 09:50
@krystophny krystophny force-pushed the perf/spectre-fieldline-midpoint branch from 5e96c1e to 3a2aeed Compare July 13, 2026 10:07
@krystophny krystophny force-pushed the perf/spectre-fieldline-midpoint branch from 3a2aeed to bf29923 Compare July 13, 2026 11:43
@krystophny krystophny merged commit fc20806 into main Jul 13, 2026
7 checks passed
@krystophny krystophny deleted the perf/spectre-fieldline-midpoint branch July 13, 2026 12:29
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