EQUIL + GPEC - Two-pass measured-curvature auto psi grid with rational-surface knots - #312
Conversation
…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>
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
…performance/ntv-psi-quadrature-paneling
…-crossing quadrature panels, shared sign-change root scan, psilims floor above axis Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
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 |
…scription and future settings docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 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 <noreply@anthropic.com>
"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 |
…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>
|
@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. |
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>
…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>
…grid-sensitive diagnostic Empirical re-measurement on the current branch head does not support the grid-robust ladder claims. The reported h0=5e-5 ringing does not reproduce (dpm[1,1] holds at 8.3-8.8); across psi_accuracy 2e-3->2.5e-4 the ladder swings dpm[1,1] ~21% (mean ~9.7, above the ldp-512 reference 8.48, not the claimed ~4%); and column-equilibrating the sing_get_ca LU solve is bit-identical, so the solve is not the source of the sensitivity. The ideal-MHD Δ' diagonal is intrinsically grid-uncertain (~30-50% across the accuracy knob); et[1], NTV torque, and ||resonant flux|| stay grid-robust to <1%, confirming the sensitivity is local singular-layer matching, not the global response. Pin the bare rational surfaces (rational_psi_nodes) as mandatory knots and delete rational_psi_ladder with its four one-example-tuned constants. Re-pin the delta_prime_matrix regression diagonals honestly as a grid-sensitive diagnostic, not a converged value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
…GE_ATOL) The 1e-8 panel-boundary merge/bound tolerance was repeated four times in integrate_psi_quadgk. Hoist it to a documented PANEL_MERGE_ATOL constant and factor the duplicated in-span predicate into a local helper. Behavior-preserving (kinetic testset 277/277). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
…mple The psi-quadrature paneling in this PR is the deferred follow-up the interim atol_psi=1e-2 / rtol_psi=1e-2 lines pointed to, so drop them and inherit the new rtol-primary defaults (atol_psi=0, rtol_psi=1e-2). Numerically inert here: |T|~83 N·m gives rtol*|T|~0.83 >> atol=1e-2, so rtol already bound termination. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
…ature-paneling Sync #312's latest (sing_pack_coef exposure, density-model docstring, decomposition figure) ahead of consolidating both PRs into one package. Resolved the GridRefinement.jl SING_PACK conflict in favor of #312's exposed sing_pack_coef parameter; this branch had only reworded the adjacent comment (ladder references already removed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
…e, docstring/const accuracy) From a 3-agent review of the consolidated grid+NTV package (physics review passed with no defects; perf/clean-code minor). Removed a dead per-ψ wtw_l zeroing in psi_batch! (redundant with the per-bounce-harmonic reset). Documented the panel_psis/resonance_psis return fields; synced the _knot_density docstring signature with its sing_pack_coef kwarg; named the core/edge model-region splits CORE_MODEL_PSI_MAX/EDGE_MODEL_PSI_MIN (were bare literals duplicated in prose). Behavior-preserving: grid-refinement 41/41, kinetic 277/277. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
…ighted energy x=2.5 The kinetic-resonance ψ-panel locator evaluated Ω_ℓ at thermal energy x=1, but the Maxwellian-weighted NTV drive x^2.5·e^-x peaks at x=2.5 — where the resonance overlaps the most particles and the torque density actually spikes. Parameterize the evaluation energy (xeval) and default it to 2.5. On the DIII-D case the located nodes sit ~3x closer to the measured dT/dψ spikes (mean 3.97e-3 vs 1.17e-2 in ψ, same 6-node count). Panel placement only — the converged torque is unchanged within rtol_psi. The node-scan unit test is pinned to xeval=1.0 to preserve its analytic closed-form roots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
The 1e-12 value-scale floor (guards divide-by-zero on an all-zero profile/geometry channel) was repeated three times in _knot_density; hoist to a named const. From the fresh-eyes clean-code review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v
…orces conflicts) Brings in develop's #310 (kinetic dW/PE displacement; NTV torque/energy stored as complex T with real δW_k=Im(T)/2n; bounce quadrature via precomputed weights; theta_bmax held at the nodal knot per Fortran) and #314. Resolved two KineticForces conflicts: (1) KineticForcesStructs tolerances — kept this branch's rtol-primary redesign (atol_psi=0, maxevals_psi) over develop's atol_psi=1e-2, which would have duplicated the atol_psi field; (2) Torque _setup_surface_state dB/dθ extrema — applied develop's theta_bmax-stays-at-knot logic through this branch's find_sign_change_roots idiom, matching the auto-merged tpsi! loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HQTbqjiubWtS915nQeN96v


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