You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#302 showed the auto grid (log_asymptotic, mpsi=0) places knots by a curvature/separatrix heuristic that is blind to where the solution is actually sharp — it under-resolves the edge and, to converge, over-resolves the mid-region (~4× the knots ldp needs). The right fix is to place knots where the physics has sharp features rather than globally tightening a tolerance.
With #112 (KineticForces/NTV) merged, the grid must also serve kinetic calculations, which add new sharp radial features the current heuristic ignores:
Rational surfaces (q = m/n). Orbit-pitch / bounce-harmonic resonances near rationals produce large, sharp kinetic features. The grid should land points on the rationals. (This also directly addresses the auto mpsi under-resolves the edge, giving spurious dW and Delta-prime #302 near-separatrix-rational symptom, independent of kinetics.)
Kinetic resonance surfaces. The superbanana-plateau resonance near ω_E ≈ 0 and other bounce/precession resonances (Logan 2013, §IV–V) are sharp and localized. Their ψ-locations are cheaply estimable from cylindrical approximations of the resonance operator evaluated at thermal energy x = 1.
Proposal: a two-pass "form → refine grid → reform" workflow
The equilibrium↔kinetic ordering is physical: resonance frequencies (ω_b, ω_d) need the formed geometry, and rational surfaces need the q-extrema that only exist after the first build. The solver also builds psi_nodes before the field-line integration that produces the geometry, so a true single pass is impossible. Equilibrium formation is fast and the existing rerun path re-forms from the same in-memory input (no file re-read), so a second pass is cheap.
Driver flow:
Form the equilibrium once on the default/coarse grid.
refined_psi_nodes(equil, base_nodes; kinetic_profiles, n, m-range, …) → augmented ψ vector: union of (a) rationals q=m/n (q forced positive, via the existing sing_find! bisection on the q-spline + q-extrema) and (b) kinetic-resonance ψ where Ω(x=1; ψ) = ℓ_eff·ω_b·√x + n·(ω_E + ω_d·x) changes sign (cylindrical ω_b/ω_d from Logan 2013), each locally packed with a few knots.
Re-form on the augmented grid via the existing in-memory rerun path; discard the first pass.
Merge must guard against near-duplicate nodes. A mandatory node landing a hair from an existing one (existing […0.5, 0.6…], rational at 0.5+1e-15) creates a near-zero interval that pollutes the bicubic spline reconstruction of those surfaces with ringing. A plain unique! does not catch this. Enforce a minimum spacing δ_min (a fraction of local base spacing, not float epsilon): either snap — drop the redundant existing node, keep the mandatory one ([…0.5+1e-15, 0.6…], default) — or displace — nudge neighbors outward to open room while preserving local psi_accuracy ([…0.45, 0.5+1e-15, 0.6…]). Post-merge grid must be strictly monotone with every interval ≥ δ_min.
Minimal code surface
Equilibrium: add optional override_psi_nodes kwarg to equilibrium_solver / setup_equilibrium; _build_psi_grid branches untouched. Only a Vector{Float64} crosses the module boundary — no kinetic physics enters the Equilibrium foundation module.
ForceFreeStates:rational_psi_nodes(equil; n, m-range) reusing the sing_find! root-find.
Kinetic-resonance packing: auto-enabled whenever kinetic profiles are loaded.
Acceptance criteria
Auto grid lands knots on (or symmetrically straddling) every q=m/n rational in range, including near-separatrix ones, with a few packing knots each.
When kinetic profiles are present, knots are packed near the ω_E≈0 surface and the dominant bounce/precession resonance surfaces predicted by the x=1 cylindrical estimate.
A regression case (kinetic NTV torque on an example deck) shows the refined grid reaches the converged torque at far fewer knots than uniform tightening of psi_accuracy.
No near-duplicate nodes survive the merge: every post-merge interval ≥ δ_min, and a mandatory node within δ_min of an existing node is resolved by snap-or-displace (no 0.5 / 0.5+1e-15 pairs that ring the reconstructed surfaces).
No dependency from Equilibrium onto KineticForces.
References
Logan et al., Neoclassical toroidal viscosity in perturbed equilibria with general tokamak geometry, Phys. Plasmas 20, 122507 (2013), §IV (pitch-angle resonance) and §V (superbanana plateau) — docs/resources/2013-Logan-...pdf.
Sub-issue of #302.
Motivation
#302 showed the auto grid (
log_asymptotic,mpsi=0) places knots by a curvature/separatrix heuristic that is blind to where the solution is actually sharp — it under-resolves the edge and, to converge, over-resolves the mid-region (~4× the knotsldpneeds). The right fix is to place knots where the physics has sharp features rather than globally tightening a tolerance.With #112 (KineticForces/NTV) merged, the grid must also serve kinetic calculations, which add new sharp radial features the current heuristic ignores:
Proposal: a two-pass "form → refine grid → reform" workflow
The equilibrium↔kinetic ordering is physical: resonance frequencies (ω_b, ω_d) need the formed geometry, and rational surfaces need the q-extrema that only exist after the first build. The solver also builds
psi_nodesbefore the field-line integration that produces the geometry, so a true single pass is impossible. Equilibrium formation is fast and the existing rerun path re-forms from the same in-memory input (no file re-read), so a second pass is cheap.Driver flow:
refined_psi_nodes(equil, base_nodes; kinetic_profiles, n, m-range, …)→ augmented ψ vector: union of (a) rationals q=m/n (q forced positive, via the existingsing_find!bisection on the q-spline + q-extrema) and (b) kinetic-resonance ψ where Ω(x=1; ψ) = ℓ_eff·ω_b·√x + n·(ω_E + ω_d·x) changes sign (cylindrical ω_b/ω_d from Logan 2013), each locally packed with a few knots.Merge must guard against near-duplicate nodes. A mandatory node landing a hair from an existing one (existing
[…0.5, 0.6…], rational at0.5+1e-15) creates a near-zero interval that pollutes the bicubic spline reconstruction of those surfaces with ringing. A plainunique!does not catch this. Enforce a minimum spacingδ_min(a fraction of local base spacing, not float epsilon): either snap — drop the redundant existing node, keep the mandatory one ([…0.5+1e-15, 0.6…], default) — or displace — nudge neighbors outward to open room while preserving localpsi_accuracy([…0.45, 0.5+1e-15, 0.6…]). Post-merge grid must be strictly monotone with every interval ≥δ_min.Minimal code surface
override_psi_nodeskwarg toequilibrium_solver/setup_equilibrium;_build_psi_gridbranches untouched. Only aVector{Float64}crosses the module boundary — no kinetic physics enters the Equilibrium foundation module.rational_psi_nodes(equil; n, m-range)reusing thesing_find!root-find.refined_psi_nodes(...)(resonance locator + merge/pack), reusingBounceAveraging.jl/EnergyIntegration.find_resonance_energies.Activation
Acceptance criteria
et[1] ≈ 0.80) converges at a total knot count comparable toldp, mpsi=256, not the ~1012 the current auto heuristic needs.psi_accuracy.δ_min, and a mandatory node withinδ_minof an existing node is resolved by snap-or-displace (no0.5 / 0.5+1e-15pairs that ring the reconstructed surfaces).References
docs/resources/2013-Logan-...pdf.