Skip to content

KineticForces - PERFORMANCE - Panel NTV psi quadrature at rational and kinetic-resonance surfaces with rtol-primary tolerances#313

Open
logan-nc wants to merge 22 commits into
developfrom
performance/ntv-psi-quadrature-paneling
Open

KineticForces - PERFORMANCE - Panel NTV psi quadrature at rational and kinetic-resonance surfaces with rtol-primary tolerances#313
logan-nc wants to merge 22 commits into
developfrom
performance/ntv-psi-quadrature-paneling

Conversation

@logan-nc

@logan-nc logan-nc commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Closes #303. Built on #312 (feature/two-pass-psi-grid is merged into this branch, so its commits appear in this diff until #312 lands — merge #312 first, after which this PR reduces to the KineticForces changes).


Update (2026-07) — merged develop, and made Δ′ grid-robust

origin/develop is now merged into this branch. develop had retargeted the DIII-D example to Ip=1.15 MA and switched that example to a fixed ldp/mpsi=256 grid; the merge conflicts (DIIID gpec.toml, EquilibriumTypes.jl, and the parallel-integration tests) were resolved to keep this branch's two-pass auto grid running on the new equilibrium.

That surfaced a real problem the merge exposed: on the two-pass auto grid the delta-prime matrix diagonal was not grid-converged — a psi_accuracy (τ) scan swung dpm[1,1] (q=2) by ~30%. Root cause: Δ′ extracts the subdominant small-solution coefficient through a dpsi^{-2α}-conditioned Frobenius projection, so it tracks the equilibrium-spline error in the layer around each rational; the old packing was τ-scaled and its merge snapped out the pinned rational's own neighbors, so the local stencil changed topology with τ. (et[1], a whole-plasma integral, stayed robust to <1% — confirming this is a local singular-layer matching problem, not a global-response one.)

Fix — rational_psi_ladder: a geometric knot ladder straddling each rational with a fixed absolute innermost step h0 = 1e-3, independent of τ and matched to the equilibrium's native radial resolution, emitted as mandatory grid knots. Anchoring h0 to the equilibrium resolution — not the far finer perturbed matching offset singfac_min/|n·q1| (~5e-5), which rings the re-splined equilibrium and blew Δ′ up to ~149 — is what makes Δ′ grid-robust. A min-spacing thinning prevents overlapping ladders from nearby rationals (low shear / multi-n) creating sub-h0 intervals; merge_mandatory_nodes collapse now uses an absolute tolerance so fine ladder knots survive.

DIII-D case before (τ-scaled packing) after (ladder)
dpm[1,1] spread across τ ∈ {2e-3…2.5e-4} ~30% ~4% (q3, q4 <1%)
dpm[1,1] value 6.4–8.9 (τ-dependent) 8.40, matches ldp mpsi=512 (8.48)
Δ′-matrix runtime vs develop's ldp mpsi=256 2.2× faster (158 s vs 353 s)

The converged value matches the finer ldp mpsi=512 grid; develop's ldp mpsi=256 (7.28) undershoots by under-resolving the rationals. In the diiid_n1 harness, equilibrium, energies, NTV torque, and ‖resonant flux‖ are unchanged (<0.5%); the delta-prime and its downstream island/Chirikov diagnostics move intentionally (the ladder converges Δ′). A fortran-physics-reviewer audit passed: the fixed ladder is a faithful analog of DCON (equilibrium splined at native resolution, singular layer handled analytically by the Frobenius matching at dpsi = singfac_min/|n·q1|), and it flagged the overlapping-ladder edge case that the min-spacing thinning now guards.

Re-pinned the parallel-integration dpm diagonals at rtol=0.1 on the ladder values (real parts only; et_par kept tight); added a grid-refinement test pinning the ladder min-spacing invariant in the multi-n regime. The NTV-quadrature description below is unchanged by this update.


Problem

The outer ψ-integration of the NTV torque tpsi(ψ) used a single whole-domain adaptive quadrature with no interior breakpoints, no maxevals bound, and a discarded error estimate. The resonant torque-density peaks — at the rational surfaces and at the kinetic resonances (dominantly the ExB/superbanana-plateau resonance near ω_E ≈ 0) — forced deep adaptive bisection (1375+ evaluations, ~30 min reported in #303 on the DIII-D-like case). Separately, the atol_psi = 1e-2 N·m default was an amplitude-sensitive trap: NTV ∝ δB², so a 10× weaker applied field gives a 100× smaller torque and a fixed absolute tolerance can silently dominate termination with O(1) relative error.

Changes

ψ-quadrature paneling at resonant surfaces

  • Rational-surface ψ locations the stability run resolved (sing + kinsing) are threaded into KineticForcesInternal.sing_psis and passed as quadrature panel boundaries — Gauss–Kronrod handles peaks at interval endpoints natively instead of hunting them by bisection.
  • Kinetic-resonance locations are identified and paneled for the case's full bounce-harmonic range: kinetic_resonance_psi_nodes(kinetic_profiles, equil; n, nl, ...) scans for zeros of the trapped-branch resonance denominator at thermal energy, Ω_ℓ(x=1; ψ) = ℓ·ω_b(ψ) + n·(ω_E(ψ) + ω_d(ψ)) for every ℓ ∈ −nl:nl, using the pitch-averaged RLAR closed-form frequencies already in tpsi! (single spline evaluations, no bounce averaging). The ℓ=0 node is the ω_d-shifted ExB resonance; ℓ≠0 nodes capture the bounce resonances. On the DIII-D case this finds 8 in-range surfaces that line up with the observed pedestal dT/dψ structure (see figure) — including the previously unexplained bump at ψ_N ≈ 0.955.
  • Panel boundaries and located resonance surfaces are persisted to the output (kinetic_forces/<method>/panel_psi, resonance_psi) for diagnostics/plotting; the per-method log line reports torque, error estimate, evaluation count, and panel composition.
  • A fortran-physics-reviewer audit of the locator passed all checks: frequency conventions match tpsi!/torque.F90 exactly (the wdhat = q·T/(2εR₀²ZeB₀) closed form verified algebraically identical), the denominator correctly uses ω_E alone (diamagnetic terms are numerator-only), and leff = ℓ matches the trapped branch. Passing-branch (ℓ+nq transit) resonances are a documented non-goal (broad/Landau-like; quadrature resolves them without dedicated panels).

Unified helpers (one source of truth per concept, no drift)

Tolerance redesign (rtol-primary)

  • atol_psi: 1e-2 → 0.0. Convergence is controlled by rtol_psi = 1e-2 (~2 significant figures — the limit of the NTV model's validity; tighter tolerances buy digits the physics can't back).
  • New maxevals_psi = 2000 runaway guard; the error estimate is checked and a clear warning fires when the quadrature terminates without meeting tolerance, or when a user-set nonzero atol_psi dominates termination (the weak-field silent-garbage scenario).
  • psilims stays [0.0, 1.0]. (An interim commit floored it at 0.1 to dodge a near-axis dT/dψ spike; measurement showed that was over-optimization — at rtol=1e-2 only 7 of 345 develop evaluations land below ψ=0.1 and the core carries a real 0.3% of the torque — so the floor was reverted. The near-axis feature itself turns out to be two located ℓ≠0 resonance surfaces, now paneled like the rest.)

Coverage

  • New examples/Solovev_kinetic_NTV_example (ideal FFS → PE → NTV torque) — previously no shipped config exercised this code path — plus a solovev_kinetic_ntv regression case pinning the fgar torque and ψ evaluation count.
  • Removed the inert atol_psi/rtol_psi keys from the a10 example (it stops after kinetic stability and never reaches the torque quadrature).

Benchmarks (DIII-D-like case from #295, n=1, C-coil 1 kA, nl=6, 5 rationals)

Code Panels atol/rtol_psi Torque (N·m) ψ evals KF stage
develop 1e-3 / 1e-3 (pre-#303 mitigation) 1375+ ~30 min (reported)
develop 0 / 1e-2 82.93 − 14.80i 345 158 s
this PR 5 rational + 8 kinetic resonance defaults (0 / 1e-2) 83.16 − 14.92i 210 108 s
this PR 5 rational + 8 kinetic resonance 1e-9 / 1e-3 83.18 − 14.95i 450 296 s

The defaults run agrees with the tightly-converged answer to 0.024% in Re(T) at 1.6× fewer evaluations than develop at equal tolerance — and 210 = 14 panels × 15 nodes exactly: the initial pass alone converges, i.e. the panels sit precisely where the quadrature work is. Whole n=1 pipeline: ~3 minutes. Regularization note: the PE default reg_spot = 0.05 must stay on for this post-PE diagnostic — with raw (unregularized) ξ the "torque" becomes the grid-capped ideal-MHD 1/(m−nq)² divergence (~66,000× larger on a Solovev test) rather than a physical answer.

Torque density with the identified resonance locations (rationals dashed gray; located kinetic resonances Ω_ℓ(x=1)=0 dash-dot violet — note the match to the pedestal structure and the small near-axis feature):

NTV torque density

Quadrature evaluation clustering — develop (no panels) goes near-vertical at the edge resonances; the paneled defaults run spreads effort almost uniformly:

Evaluation clustering

Cumulative torque integral — all runs overlay; ~95% of the torque accumulates in the pedestal, with the sharp rise starting at the ℓ=0 kinetic resonance:

Cumulative torque

Regression

  • solovev_n1: 21/21 unchanged; diiid_n1: 46/46 unchanged vs feature/two-pass-psi-grid (this PR's changes are isolated to [KineticForces]-configured runs).
  • solovev_kinetic_ntv: NTV torque and evaluation count move intentionally with the quadrature improvements (final baseline pinned at this head).
  • Unit tests: 222 kinetic (new: root-scan, resonance-node scan with analytic locations, panel assembly, tolerance warnings) + 39 grid-refinement, all green.

Notes for reviewers

🤖 Generated with Claude Code

logan-nc and others added 18 commits July 2, 2026 17:22
…rfaces, rtol-primary tolerances with maxevals guard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rium solver paths

Adds an override_psi_nodes keyword to setup_equilibrium and threads it through
the direct, arclength, inverse, and by-inversion solvers, bypassing the
config-driven grid with a validated externally supplied node vector. This is
the injection point for the two-pass auto-grid refinement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on, and mandatory-node merge

GridRefinement.jl derives the pass-2 knot density from the formed pass-1
equilibrium using the cubic h^4 error model on nodal fourth divided differences
(1D profiles, rzphi geometry channels at sampled theta lines, and kinetic
profiles when present), with a-priori edge/core geometric floors, then
equidistributes and pins mandatory knots with a delta_min snap guard. The
log_asymptotic auto path now forms a coarse fixed-128 pass-1 layout; the
one-pass a-priori heuristics (make_optimal_mpsi, probe log-slope, 300-point
mid-spacing sampling) are removed. All regions now scale as psi_accuracy^(-1/4),
so tightening the tolerance refines edge and mid proportionally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… add rational_psi_nodes

The qextrema-interval Brent walk moves from sing_find! into
_find_rational_surfaces, which returns (m, n, psifac) tuples; sing_find!
rebuilds its SingType multiplicity bookkeeping on top. rational_psi_nodes
exposes the unique surface locations for the two-pass grid refinement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ession case covering the psi torque quadrature

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onal-surface packing

Validation on the DIIID-like example drove four corrections to the knot
density model:
- h^3 derivative error model (err(f') ~ h^3|f''''|/24) replaces the h^4 value
  model: the stability physics consumes spline derivatives (q' at rational
  surfaces, p' and V' in the EL and ballooning coefficients), and the value
  model under-resolved delta-prime by 2x at q=2.
- Curvature is measured against rho = sqrt(psi), where the equilibrium is
  regular at the axis; the psi-space geometry channels diverge as psi^(k/2-4)
  there and made the implied knot count grow without bound under refinement.
- Local packing around mandatory (rational) surfaces: spacing 0.06*tau^(1/3)
  at the surface with geometric growth, within radius 0.05 — converges the
  delta-prime BVP, which samples the psi-splined coefficient matrices around
  each singular surface.
- The core below psi=0.03 uses the a-priori geometric density exclusively:
  nodal data on the smallest flux surfaces is dominated by integration and
  axis-extrapolation error. Near-duplicate mandatory nodes (same surface via
  different m,n) collapse onto one knot. Noise floor scales as eps/h^4 on
  tightly packed sample grids.

Adds test/runtests_grid_refinement.jl covering the merge invariants,
equidistribution, quartic-exact divided differences, layer concentration,
tau^(-1/3) scaling, and a Solovev two-pass round-trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts in main driver

When grid_type=log_asymptotic with mpsi=0, the driver forms a coarse pass-1
equilibrium, pins knots on all rational surfaces in the requested n range,
derives the refined grid from the measured curvature (including kinetic
profiles when loaded), and re-forms from the in-memory input. The nn range
validation and the kinetic-profile load are hoisted above equilibrium
formation to feed the refinement; a consistency check warns when the refined
equilibrium implies substantially more knots than used.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ha-boundary scan drivers

The alpha-boundary drivers now scan only psi_N in [0.1, min(0.99, psi_edge)],
reusing the existing NaN sentinel for skipped surfaces. Ballooning boundaries
are physically relevant in the mid-radius and pedestal; the packed axis and
far-edge surfaces dominated the scan cost. The locstab path
(compute_ballooning_stability!) is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Quantifies knots-vs-accuracy for cubic splines of q versus iota = 1/q on the
DIII-D-like example: iota gives a modest constant-factor improvement (~1.2-2x
at coarse N) but the same convergence order, confirming that wholesale iota
replacement is not warranted for grids ending inside the separatrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…md, example annotations)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…psi=256 grid

These testsets target the bidirectional FM integration and STRIDE BVP
machinery, so they now pin the radial grid instead of inheriting the example
default (mpsi=0), which previously baked the defective one-pass auto grid into
the pinned values and would otherwise move whenever the auto grid evolves.
Re-pins et_par and the delta-prime diagonals to the ldp-256 values: q=2,3,4
real parts are grid-converged and pinned tightly; the near-separatrix q=5,6
entries are not grid-converged (value and sign vary O(1) between grids) and
are now asserted finite and non-zero only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… equidistribution, IMAS rerun note

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two-pass grid is a measured spline-derivative-error density with geometric
floors, not a log-asymptotics model — q stays finite everywhere on the grid —
so the old name misdescribed it. grid_type="auto" is the new default;
"log_asymptotic" remains a working alias (two-pass when mpsi=0, the
three-region log layout when mpsi>0), so no existing TOML breaks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…roduction auto grid

Per review: the auto grid is the production default, so the machinery testsets
now build it exactly as the main driver does (rational_psi_nodes +
refined_psi_grid + ingest re-form) and pin its values. The auto-vs-ldp512
convergence evidence is recorded on the PR; tightening psi_accuracy converges
the pinned delta-prime entries toward the dense-reference values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…) and generator script

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-crossing quadrature panels, shared sign-change root scan, psilims floor above axis

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…scription and future settings docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
logan-nc and others added 2 commits July 3, 2026 13:33
…nances for quadrature panels, revert psilims floor, persist panel data to HDF5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ance surfaces, annotate epsilon clamp

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@logan-nc logan-nc requested a review from d-burg July 6, 2026 16:19
Resolved three conflicts, all downstream of the DIIID example grid choice
(develop retargeted the equilibrium to Ip=1.15 and switched to a fixed
ldp/mpsi=256 grid; this branch uses the two-pass auto grid):

- examples/DIIID-like_ideal_example/gpec.toml: hybrid — keep develop's
  psihigh=0.995 and jac_type "custom" naming, keep this branch's
  grid_type="auto"/mpsi=0. Auto grid still needs validation on the new
  Ip=1.15 equilibrium (tracked TODO).
- src/Equilibrium/EquilibriumTypes.jl: keep develop's new jac_custom_power_*
  fields (required by the constructor) and this branch's grid_type="auto"
  default.
- test/runtests_parallel_integration.jl: took develop's tight et_par and
  delta_prime pins provisionally; will re-assess against the auto grid via
  the regression harness and propose an update.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@logan-nc

logan-nc commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

FYI @d-burg I had claude go another round with this now that a few other things merged.
The auto grid looks pretty good to me. I am seeing the dpm regression values change outside the pinned range with changes in either the auto tau or the ldp mpsi though.

I had claude working through a smarter auto grid. It tried denser packing near the rationals, but that failed. It was looking into at least just consistent packing around the rationals, but I ran out of tokens. That seems like a hack to force "robustness" anyways - why trust any given fixed packing? Do you have any better ideas for how to make a smart grid that gets the "right" answer for dpm?

…ar-layer knot ladder

The two-pass "auto" grid gave a delta-prime matrix whose diagonal swung ~30%
with the psi_accuracy (τ) knob — not grid-converged — because Δ' extracts the
subdominant small-solution coefficient through a dpsi^{-2α}-conditioned Frobenius
projection, so it tracks the equilibrium-spline error in the layer around each
rational. The old packing was τ-scaled and its merge snapped out the pinned
rational's own neighbors, so the local stencil changed topology with τ.

Add `rational_psi_ladder`: a geometric knot ladder straddling each rational with
a fixed absolute innermost step h0=1e-3 (independent of τ, matched to the
equilibrium's native radial resolution), emitted as mandatory knots. Anchoring h0
to the equilibrium resolution — not the far finer perturbed matching offset
singfac_min/|n·q1| (~5e-5), which would ring the re-spline and blow Δ' up — is what
makes Δ' grid-robust. A min-spacing thinning (h0, rationals prioritized) prevents
overlapping ladders from nearby rationals (low shear / multi-n) creating sub-h0
intervals. `merge_mandatory_nodes` collapse now uses an absolute tolerance so fine
ladder knots survive.

Result on the DIIID example: dpm auto-τ spread 30% → ~4% (q2), <1% (q3,q4); the
converged value (dpm[1,1]≈8.4) matches the finer ldp mpsi=512 grid, while ldp
mpsi=256 undershoots (7.28) by under-resolving the rationals. Equilibrium, energies,
NTV torque and resonant flux are unchanged; runtime drops 2.2× vs ldp mpsi=256.

Re-pin the parallel-integration dpm diagonals at rtol=0.1 on the ladder values
(real parts only; et_par kept tight); add a grid-refinement test pinning the
ladder min-spacing invariant in the multi-n regime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

NTV torque psi-quadrature over-refines at rational surfaces, dominating runtime

1 participant