EQUIL + GPEC - Two-pass measured-curvature auto psi grid with rational-surface knots#312
EQUIL + GPEC - Two-pass measured-curvature auto psi grid with rational-surface knots#312logan-nc wants to merge 16 commits into
Conversation
…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 <[email protected]>
…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 <[email protected]>
… 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 <[email protected]>
…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 <[email protected]>
…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 <[email protected]>
…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 <[email protected]>
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 <[email protected]>
…md, example annotations) Co-Authored-By: Claude Fable 5 <[email protected]>
…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 <[email protected]>
… equidistribution, IMAS rerun note Co-Authored-By: Claude Fable 5 <[email protected]>
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 <[email protected]>
…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 <[email protected]>
…) and generator script Co-Authored-By: Claude Fable 5 <[email protected]>
Knot placement: auto two-pass vs ldp (for the record)Top: cumulative knot fraction (index/mpsi) vs ψ_N — where each grid spends its budget. Bottom: local spacing Δψ_N (log scale). Dotted verticals are the n=1 rational surfaces. In the top panel the two ldp curves overlap exactly (normalized sin² packing is N-independent). Regenerate with Reading the figure: the auto grid (blue, 199 intervals) coasts across the smooth mid-radius at ~10× coarser spacing than ldp, dives at every rational surface (the spacing notches), and packs the core, pedestal, and edge — which is exactly where ldp either wastes knots (mid) or runs out of them (near-separatrix rationals). The grid logic, in plain bullets
Rename:
|
| grid | knots | et[end] | Δ′(q=2) | Δ′(q=3) | Δ′(q=4) | Δ′(q=5) | Δ′(q=6) |
|---|---|---|---|---|---|---|---|
| auto, τ=1e-3 (default) | 200 | 0.078112 | 8.47 | −4.03 | −10.23 | 12.35 | −144.9 |
| auto, τ=1e-4 | 454 | 0.078086 | 8.98 | −3.86 | −10.23 | 12.35 | −217.5 |
| ldp, mpsi=256 | 256 | 0.077800 | 8.79 | −3.88 | −10.21 | 16.62 | +205.2 |
| ldp, mpsi=512 (reference) | 512 | 0.078104 | 8.90 | −3.46 | −10.16 | 12.23 | −146.1 |
- The default auto grid matches ldp-512 on the eigenvalue to 0.01% and on Δ′ to ~5% on the inner surfaces — at 200 knots vs 512. On the near-separatrix surfaces it agrees with ldp-512 where ldp-256 is 35% off (q=5) or wrong-signed (q=6). Tightening τ converges Δ′ further (τ=1e-4 column).
- Test pinning is now on the auto grid (commit 42d9cf4): the DIIID parallel-FM and STRIDE-BVP testsets build the two-pass grid exactly as the driver does (
rational_psi_nodes→refined_psi_grid→ ingest re-form) and pin its values — the auto grid is the production default, so that is what regression must guard. The table above is the recorded proof of ldp-512 equivalence; the near-separatrix Δ′ entries are asserted finite-only since they are not grid-converged in any grid family at these counts.
🤖 Generated with Claude Code
Threads the rational-surface packing coefficient through refined_psi_grid and _knot_density as a keyword (default unchanged, scan-calibrated const). A (psi_accuracy, sing_pack_coef) scan on the DIII-D example showed the default grid's residual delta-prime spread at q=2,3 tracks the edge-truncation branch (correlated with the q=6 bistability), not knot count — denser grids up to 574 knots do not reduce it — so the lean default stands. Co-Authored-By: Claude Fable 5 <[email protected]>
…th three curvature sources One sizing rule (cubic derivative error h^3|f''''|/24 <= tau*scale) fed by measured divided differences (mid + edge), the separatrix log model (edge floor, normally inactive), or the power-law axis model (core, replacing the noise-dominated smallest-surface measurements). Adds the psi->0 explanation: the bicubic channels' psi-space blow-up is a coordinate artifact handled by measuring in rho = sqrt(psi) and by the geometric core packing. Co-Authored-By: Claude Fable 5 <[email protected]>
Follow-up: default-convergence scan, core ψ→0 handling, and edge-logic assessmentShould the default converge everything? Scan says: no default change is justifiedFull-pipeline scan over (
What the table shows:
How does the auto grid treat the bicubic splines blowing up near ψ→0?As a coordinate artifact, which is what it is: the rzphi channels behave as ψ^(k/2) near the axis (R−R₀ ~ √ψ), so their ψ-derivatives diverge under refinement while their ρ=√ψ derivatives converge. The grid (a) measures curvature in ρ, (b) converts density with the dψ/dρ = 2√ψ Jacobian (√ψ-tight packing toward the axis by itself), and (c) below ψ=0.03 switches to pure geometric-in-log(ψ) packing — constant-ratio spacing ∝ ψ down to Is the different edge treatment needed? Honest assessmentMostly no — one consistent logic already governs, and the docs now say so (commit 053526a). The measured ρ-space density supplies ~3× more edge knots than the edge model demands on this example (84 vs 29 in ψ>0.95), because the pass-1 layout is already log-packed at the edge — so the edge "floor" is inactive insurance, not a distinct treatment. The honest framing, now in the module docstring and 🤖 Generated with Claude Code |
… measured, not floor-driven Per-source decomposition on the DIII-D example shows the pedestal band is packed by measured curvature — P, q, and dV/dpsi contribute comparably and the rzphi geometry channels (the GS response to the same pedestal p') dominate at every node in psi 0.8-0.98, all above the edge floor. Co-Authored-By: Claude Fable 5 <[email protected]>
"Curvature of what?" — pedestal-packing attribution, with dataDefinition: the density measures |d⁴f/dρ⁴| (ρ=√ψ_N) — the quantity in the cubic-spline derivative error bound err(f′) ≈ h³|f''''|/24 ≤ τ·scale — for each of: the 1D profiles F = R·B_φ, μ₀·P, dV/dψ, q; the four rzphi bicubic channels (r², η-offset, ν, Jacobian) along every 8th θ-line (worst case over θ); and the kinetic profiles (nᵢ, nₑ, Tᵢ, Tₑ, ω_E) when loaded. The final density is the max over all of them. Note the develop example loads no kinetic profiles (no Is the pressure pedestal gradient appropriately included? Yes — verified by per-source decomposition rather than asserted: Band integrals (knots contributed per band if that source acted alone), DIII-D example, τ=1e-3:
Reading:
Figure + one-paragraph summary committed to the docs (a145a47). 🤖 Generated with Claude Code |
|
@d-burg it seems to me that all the auto grid logic checks out. Default settings give ~200 points for DIIID-like examples. I adde dsub issues to yours to make sure the logic captures the pedestal region well for ballooning and kinetic resonance regions well for kinetic terms. The logic also now explicitly knots on rationals, which I think should be a big win for all applications but I hope for you in particular. Please test on the cases that prompted #302 and confirm all is good. If yes, then merge away. |


Closes #302, closes #308. Implements the rational-surface half of #309 (kinetic-resonance knots remain a follow-up; the mandatory-node hook is in place).
Problem
The one-pass auto grid (
log_asymptotic,mpsi=0; the auto grid_type is renamed"auto"in this PR, with"log_asymptotic"kept as a working alias) sized knots from a-priori models blind to the formed solution: ~5 edge knots at the defaultpsi_accuracy=1e-3(spurious dW/Δ′, #302), while tightening the tolerance over-packed the mid-region 4× because N_mid ∝ τ^(−1/2) vs N_edge ∝ τ^(−1/4).Approach
Two-pass measured-curvature refinement (
src/Equilibrium/GridRefinement.jl+ driver orchestration):ForceFreeStates.rational_psi_nodes, extracted fromsing_find!), each with local geometric packing; a δ_min snap guard prevents near-duplicate nodes (Kinetic-aware equilibrium grid packing: place knots on rational and kinetic-resonance surfaces #309).override_psi_nodeskeyword through all solver paths) — no file re-read.Key modeling decisions (each validated on the DIII-D example):
err(f′) ≈ h³|f''''|/24: the stability physics consumes spline derivatives (q′ at rational surfaces, p′/V′ in the EL and ballooning coefficients). Every region scales as τ^(−1/3), fixing the auto mpsi under-resolves the edge, giving spurious dW and Delta-prime #302 imbalance.Also included:
benchmarks/benchmark_q_vs_iota_edge.jl): splining ι=1/q gives a ~1.2–2× constant-factor gain but the same convergence order, documenting that wholesale ι replacement is not warranted for grids ending inside the separatrix (PR EQUIL + ForceFreeStates - Extend equilibrium splines to separatrix for diverted plasmas #179 lineage owns separatrix-extension ι work).Validation (DIII-D-like example, n=1, defaults)
fortran-physics-revieweraudit of the density mathematics: PASS (divided-difference table, sharp Hall–Meyer h³/24 constant, ρ-Jacobian, edge-floor algebra dlog=(4τ)^(1/3), equidistribution, merge semantics, rerun dispatch); its two MINOR robustness suggestions are applied.test/runtests_grid_refinement.jl(38 assertions: merge invariants, equidistribution, quartic-exact d4, τ^(−1/3) scaling, Solovev two-pass round-trip).solovev_n1andsolovev_multi_nbitwise-unchanged (fixed-grid path untouched).diiid_n1moves as expected — equilibrium scalars at spline-resolution level (≤1e-5 relative); grid-sensitive rows (energies, Δ′, island widths, resonant field, PE torque) shift because the develop baseline was computed on the defective under-resolved grid; the new values match the dense references above. The diiid_n1 baseline should be re-pinned on merge.Notes for reviewers
ldp+mpsi=256workaround can flip back tompsi=0auto after this merges (and re-pin its regression).setup_equilibriumcallers withmpsi=0now get the coarse pass-1 grid unless they refine viarefined_psi_grid+override_psi_nodes(documented in the docstrings anddocs/src/equilibrium.md).merge_mandatory_nodes/refined_psi_grid(...; mandatory=...)without further Equilibrium changes.🤖 Generated with Claude Code