Repository navigation
Conversation
d433522 to
1197cfb
Compare
Review packageToroidal Critical-Δ Review Package — a single self-contained page with the derivation (including the two notation readings that had to be checked against Eq. 61), the implementation data path showing the one place the coordinate transformation enters, the symbolic (SymPy, 12/12) and numerical verification ladder, the radial-label sensitivity tables and the TJ ε scan, with all figures embedded. Decision recorded in the PR body and the 🤖 Generated with Claude Code |
…ic factor in the r_s reference
The Connor et al. 2015 (PPCF 57 065001) Eq. 59 geometric factor of the toroidal
critical-Δ (dc_type=:toroidal) is now derived per surface from the equilibrium
instead of requiring a user-supplied dgeo_val. `toroidal_dgeo` evaluates
V_s·(α²Λ²/(⟨B²⟩⟨|∇V|²⟩))^{1/4} with Λ = ψ_t'² ι'/2π and converts it from the paper's
Y = (V−V_s)/V_s reference to the x̂ = (r−r_s)/r_s reference shared by the slab layer,
the rfitzp critical-Δ and the reference-length-converted Δ'; the factor r_s(dV/dr)/V_s
= k_ref·v1/V_s cancels V_s. ResistGeometry gains the ⟨|∇ψ_N|²⟩ average it needs.
At large aspect ratio the factor reduces to √(n s r_s/R₀), so :toroidal coincides
with :rfitzp there; the factor is dimensionless and scales with the radial label
exactly as the converted Δ' does. The PerSurface/D_geo dataset now carries derived
values instead of zeros.
Tests: derivation wiring and label invariance (LayerInputs), large-aspect-ratio
limit and scale invariance (TJ analytic). Benchmarks regenerate the convergence,
verification, label-sensitivity and TJ ε-scan figures.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ision for the toroidal critical-Δ Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…M-processing They are one-off physics studies, archived with their figures under CTM-processing/julia_deltaprime_harness/figures/delta_crit_toroidal/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the dgeo_val reference, and tighten its guards - Cite Connor, Ham, Hastie & Liu 2015 (PPCF 57 065001) everywhere the source was credited to "Connor-Hastie-Helander". - A prescribed dgeo_val must be in the r_s reference (Eq. 59's value times k_ref·v1/V_s). - toroidal_dgeo requires dV/dψ_N > 0 and a non-zero chi1; the q guard could not fire. - Name the Mercier exponent α_M so it no longer collides with Connor's α, and drop the equilibrium-specific percentages from the docstring. - The k_ref fallback warning names the critical-Δ too; ResistGeometry docs name ψ_N and the eight integrands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nst rfitzp and the stable-D_R sign dc_tmp(:toroidal)/dc_tmp(:rfitzp) must equal dgeo/√(n|s|r_s/R₀) exactly (up to the W_d iteration's convergence), which checks the shared χ-matching prefactor end to end; a stable D_R < 0 surface must get a positive offset. The direct-evaluation and label checks are labelled as the wiring checks they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… case Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dc653ed to
c0dba9f
Compare
…-ratio residual by local ε and require it to halve The old rtol = 1e-2 sat inside the measured spread of a genuine O(ε) toroidal correction and passed only because the two sampled surfaces fell under it. The residual is now bounded by the local r_s/R₀ (measured coefficient 0.15–0.18 at ε = 0.05) and must halve from ε = 0.05 to 0.025 within 10 % (measured 0.491–0.495), which a wrong O(1) factor cannot satisfy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te GGJ 1975 The bare | in |∇ψ|² split the Markdown table cells, truncating avg_bsq_over_dpsisq and leaving avg_dpsisq undocumented. Use ‖∇ψ_N‖, state the normalized-average convention, list the eighth integrand in the file header, and replace the Fortran routine citation in the edited resist_geometry docstring with Glasser, Greene & Johnson 1975. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te the D_geo override An explicit dgeo_val = 0.0 only disables the offset for dc_type = :toroidal; dr_val = 0.0 disables it for every dc_type. The D_geo dataset holds the prescribed value when dgeo_val is overridden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D_geo is derived independently of dc_type, so the toroidal variant of the same deck tracked an identical vector twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e toroidal critical-Δ parallel-conduction closure dc_type = "toroidal" took Connor, Ham, Hastie & Liu 2015 (PPCF 57 065001) Eq. 59's toroidal geometry only in its final prefactor; the self-consistent W_d loop and the free-streaming χ∥ inside it still used the cylindrical field-line pitch n·s/R₀ (Fitzpatrick 1995). Both now come from the same Eq. 20 transport balance, χ⊥⟨|∇V|²⟩∂²_x = χ∥(α²Λ²/⟨B²⟩)x²: W_d = √8·(χ⊥/χ∥)^¼ / D_geo χ∥,fs = 2·v_te / (√π·K∥·W_d), K∥ = |αΛ|·r_s(dV/dr)/√⟨B²⟩ (new toroidal_kpar) K∥ is not D_geo²/r_s outside a cylinder (they differ by r_s√⟨|∇ψ_N|²⟩/k_ref, 0.57–0.76 on the DIII-D-like deck), so it is derived separately. Both reduce to the cylindrical forms at large aspect ratio (K∥ at O(ε²), D_geo at O(ε)), and the whole toroidal critical-Δ is now invariant under the choice of radial label up to k_ref. :lar and :rfitzp are algebraically unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der the toroidal critical-Δ model With k_ref = 1 the toroidal critical Δ stayed in the ψ_N reference while Δ' was reported per r_s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tity Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elta-crit-geometry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce length for every critical-Δ model Erroring under the toroidal model only was inconsistent: with k_ref = 1 the derived toroidal critical Δ stays consistent with Δ', and the mismatch against the layer response exists under every model. This restores the behaviour the branch had before ff50706. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cites them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The new toroidal closure API has inconsistent K∥ defaults and lacks validation for invalid geometry values.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Derives toroidal critical-Δ geometry from equilibrium data and propagates it through SLAYER’s thermal-conduction closure.
Changes:
- Adds
D_geo,K∥, and ⟨|∇ψ_N|²⟩ calculations. - Integrates derived geometry into SLAYER and HDF5 output.
- Adds analytic, unit, and regression coverage.
THIRD-PARTY HUMAN REVIEW IS REQUIRED BEFORE MERGE.
| File | Description |
|---|---|
src/ForceFreeStates/Surfaces/ResistEval.jl |
Adds the gradient flux-surface average. |
src/InnerLayer/SLAYER/LayerInputs.jl |
Derives toroidal geometry per surface. |
src/InnerLayer/SLAYER/LayerParameters.jl |
Applies geometry to the closure. |
src/InnerLayer/SLAYER/SLAYER.jl |
Exports geometry helpers. |
src/InnerLayer/InnerLayer.jl |
Re-exports geometry helpers. |
src/Tearing/Runner/Control.jl |
Documents control semantics. |
src/Tearing/Runner/HDF5Output.jl |
Updates critical-Δ metadata. |
test/runtests_tj_analytic.jl |
Tests asymptotic and scale limits. |
test/runtests_slayer_params.jl |
Tests closure algebra. |
test/runtests_slayer_inputs.jl |
Tests geometry derivation and wiring. |
regression-harness/cases/diiid_slayer_n1.toml |
Tracks derived D_geo. |
regression-harness/cases/diiid_slayer_n1_toroidal.toml |
Adds toroidal regression coverage. |
docs/development/references.md |
Adds the Connor et al. reference. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| sval_r::Float64 | ||
| dr_val::Float64 = 0.0 | ||
| dgeo_val::Float64 = 0.0 | ||
| kpar_val::Float64 = 0.0 |
| g_w, kpar = if dc_type === :toroidal | ||
| (dgeo_val, kpar_val === nothing ? kpar_cyl : kpar_val) | ||
| else | ||
| (sqrt((rs / R0) * abs(sval_r) * n_tor), kpar_cyl) | ||
| end |
…elta-crit-geometry

Release note
dc_type = "none"); withdc_type = "toroidal"the critical-Δ offset is now computed from the equilibrium rather than from a user-supplieddgeo_val, and its parallel-conduction closure uses the same toroidal geometry (Δ_crit +3.7 to +5.9 % on the DIII-D-like example). The HDF5 datasetTearing/PerSurface/D_geocarries derived values instead of zeros. (harness @ 90c3201)dc_type = "none"are unaffected. Underdc_type = "toroidal", dropdgeo_valto take the derived geometric factor;dc_type = "toroidal"no longer errors when it is absent. A prescribeddgeo_valnow means the factor in the r_s reference, i.e. Eq. 59's own value ×k_ref·v1/V_s(≈ 2 at large aspect ratio). A value that was passed as Eq. 59's own factor must be converted, or it gives about half the intended Δ_crit. Anything readingTearing/PerSurface/D_geomust now expect derived values where it previously read zeros.:larand:rfitzpare unchanged.The toroidal critical-Δ of Connor, Ham, Hastie & Liu 2015 (PPCF 57 065001, Eq. 59) can now be used on any equilibrium: its geometric factor is derived per rational surface from the flux-surface averages GPEC already computes, expressed in the same r_s reference length as the rest of the SLAYER layer stack.
Left: the critical Δ at each rational surface of the DIII-D-like example under the cylindrical
rfitzpmodel, under the toroidal geometric factor with a cylindrical parallel-conduction closure, and under the full toroidal model. The toroidal model is 22 to 60 % aboverfitzp; the closure accounts for 3.7 to 5.9 % of that. Right: SLAYER growth rates with no critical Δ (the default) and under each model. The 2/1 goes from growing (192 s⁻¹) to damped: −153 s⁻¹ withrfitzp, −230 s⁻¹ with the toroidal model.Regression report
regress --cases diiid_slayer_n1,diiid_slayer_n1_toroidal --refs 6fc89d021,90c320185 --forceon feynman (SLURM, 8 threads; current develop vs this branch with develop merged; both fresh, manifest pinned).D_geo: changes from zeros to the derived factor, which is the intended change.e84e1afa7), after merging develop6fc89d021: in both cases every layer input,D_geo,D_Rand Δ_crit are bit-identical. The γ values differ by at most 0.06 %, which is the same triangulation noise.docs/development/references.mdandclasslines in the case files. An error on an unusable reference length was added and then taken back out, so that function is unchanged; the hazard is tracked in InnerLayer.SLAYER - BUGFIX - Fail clearly when the minor-radius derivative at a rational surface is unusable #505. None of this changes a computed value:regress --cases diiid_slayer_n1_toroidal --refs d008c30e1,e84e1afa7 --forceon feynman gives 5 unchanged (D_R, Δ_crit, Q_root, γ, no-root flags).diiid_slayer_n1_toroidal: has no develop baseline, because develop errors ondc_type = "toroidal"without adgeo_val. The χ∥-closure commit is therefore compared against the previous head,regress --refs d0e8a0261,276288065(same job):The per-surface numbers are in the closure section below.
D_Rand every layer input are identical.Review package
A self-contained page with the derivation, the implementation data path (where the coordinate transformation enters), the symbolic and numerical verification ladder, and all figures embedded: Toroidal Critical-Δ Review Package.
Base branch
Originally based on and targeted at
bugfix/slayer-dprime-reference-length(#403), which defines the r_s reference length K = r_s·dψ_N/dr used below. #403 has since merged intodevelop(f36ecef86), so this branch was rebased onto #403's final head12f64da8cand the PR now targetsdevelopwith a single commit.Derivation
Connor's Eq. 59, in Hamada coordinates with ψ(V) the toroidal flux, χ(V) the poloidal flux, primes d/dV, ι/2π = χ'/ψ' = 1/q, Λ = ψ'² ι'/2π, α = 2πn/χ':
In GPEC quantities (ψ_N normalized poloidal flux,
chi1 = 2π·psio= dχ/dψ_N with χ the full poloidal flux,v1 = dV/dψ_N,q1 = dq/dψ_N):Two points where the paper's notation has to be read carefully, both verified against the paper's own large-aspect-ratio limit (Eq. 61):
jac/V'weighting above. A literal 1/2π leaves a (2π)^{1/2} mismatch with Eq. 61.rfitzpcritical-Δ, and the Tearing - BUGFIX! - Convert Δ' to the r_s reference length before slab-layer matching #403-converted Δ' are all referenced to x̂ = (r−r_s)/r_s. Because Eq. 59 is derived at H = 0 (Frobenius exponent μ = 1/2), the conversion is linear: multiply by r_s(dV/dr)/V_s = K·v1/V_s. V_s cancels, so no volume integral is needed. Without this conversion Eq. 59 gives exactly half ofrfitzpat large aspect ratio (V ∝ r²).With the conversion the geometric factor reduces at large aspect ratio to √(n s r_s/R₀) with s = (r_s/q)dq/dr, which is exactly the factor inside
rfitzp, so the two branches coincide there and the paper's Eq. 61 is recovered after dividing by r_s.Implementation
ResistGeometry(src/ForceFreeStates/Surfaces/ResistEval.jl) gainsavg_dpsisq= ⟨|∇ψ_N|²⟩, an eighth column of the existing θ-loop.toroidal_dgeo(src/InnerLayer/SLAYER/LayerInputs.jl, exported) evaluateswith α and Λ as above.
k_ref · v1is the Y → x̂ reference conversion; this is the only place the coordinate transformation enters, andk_refis the same K thatdelta_prime_to_rs_referenceapplies to Δ'.build_slayer_inputsderivesdgeo_valwhenever the surface carries aResistGeometry(sing.restype, populated byresist_eval_all!), for everydc_type; only:toroidalconsumes it, via_solve_dc_tmp's0.5·(−D_R)·π^{3/2}·(χ∥/χ⊥)^{1/4}·dgeo, whose χ∥ closure now uses the same toroidal geometry (next section). It throws only ifdc_type=:toroidalis requested on a surface without aResistGeometry. An explicitdgeo_valstill overrides.Toroidal χ∥ closure
Eq. 59 needs χ∥/χ⊥. GPEC gets χ∥ by harmonically combining a collisional (Spitzer–Härm) value with a free-streaming value. That combination is solved self-consistently with the critical width W_d.
Until
276288065,:toroidalused Eq. 59's toroidal geometry only in the final prefactor. The W_d loop and the free-streaming χ∥ inside it still used the cylindrical field-line pitch n·s/R₀ (Fitzpatrick 1995, Phys. Plasmas 2 825, Eq. 132 and Sec. VII). Both now come from the balance that produces Eq. 59.The balance. Connor's Eq. 18 with the Eq. 20 coefficients, written in x = V − V_s, reads
That is χ⊥k⊥² = χ∥k∥² with k∥(x) = |αΛ|·x/√⟨B²⟩ [1/m].
Critical width. Fitzpatrick's w_D (Connor Eq. 65) is 2√2 times the cylindrical x_T. In the x̂ reference, with the same conversion K·v1 = r_s·dV/dr as D_geo:
The second relation is the same identity that links
:larand:rfitzp.Free-streaming χ∥. The form and the 2/√π flux-limit factor are unchanged; only k∥ changes, to its value from the balance at x = W_d·K·v1:
K∥ is computed by the new
toroidal_kpar.In a circular cylinder, αΛ = −n s B₀/(4π²R₀²r²) and K·v1 = 4π²R₀r², so K∥ = n·s/R₀. That is exactly the previous code.
K∥ is not D_geo²/r_s. Algebraically, K∥ = (D_geo²/r_s)·g, with g = r_s√⟨|∇ψ_N|²⟩/K. The metric factor g is 1 in a cylinder, but 0.57–0.76 on the DIII-D-like surfaces. Taking K∥ from D_geo would put it 24–43 % off and make χ∥ depend on the radial label. So K∥ is computed separately.
Limits.
That factor bounds the change on the DIII-D-like equilibrium (midplane label):
Measured with the
diiid_slayer_n1_toroidalharness case, which uses the same equilibrium with psihigh = 0.9995 and so also reaches the 7/1. It comparesd0e8a0261(before) with276288065(after):Every surface stays inside its free-streaming bound, and close to it: at these temperatures χ∥ is mostly free-streaming. D_R, D_geo and every layer input are bit-identical. The three tracked modes were already stable and become 3.0–7.3 % more stable.
Checks. All but the last are in the unit tests:
runtests_tj_analytic.jl): on TJ circular equilibria at ε = 0.1, 0.05 and 0.025, K∥/(n s/R₀) − 1 = (0.37–0.47)·ε². That is second order; the test requires |dev| ≤ ε² and a quartering from ε = 0.05 to 0.025. D_geo's first-order deviation is entirely the metric factor, g − 1 ≈ −2·(D_geo/D_cyl − 1), which checks the K∥–D_geo identity numerically.runtests_slayer_params.jl, Test 1e)::toroidalreproduces:larto 1e-12 and:rfitzpto 1e-8.dgeo_val = 0still gives no offset.runtests_slayer_inputs.jl)::midplane,:fluxand:volumeto 1e-8. The previous cylindrical closure does not have this property, because its r-based shear and r_s change with the label.dgeo_valkeeps the derived K∥, and without aResistGeometrythe closure falls back to the cylindrical K∥ with a warning.v1_local,avg_bsqandavg_dpsisqare the normalized-average quantities the formulas need.Still a modelling choice (also in the code comments and docstrings):
:larand:rfitzpkeep the cylindrical geometry by definition.Verification
rfitzpformula through Connor's Eq. 65; the factor is invariant under B → λB and lengths → μ·lengths, while the Fortran one-power form scales as (λ/μ)^{-1/2}. The consequences that can be checked numerically are pinned by this PR's unit tests rather than by the script: the large-aspect-ratio limit and its linear ε-scaling (runtests_tj_analytic.jl), scale invariance, which the one-power form fails by √2 (runtests_tj_analytic.jl), and the exact:toroidal/:rfitzpprefactor identity (runtests_slayer_inputs.jl).rfitzpcode formula: all consistent, pinning the prefactor.slayer_growthratebranch of the Fortran GPEC, which carries one power of ψ_t' in Λ instead of two, fails this by √2 and is off by ψ_t'^{-1/2} (2.3× at ε = 0.1, 3.2× at ε = 0.05). That branch also feeds D_I rather than D_R and writes log(q) asshear; none of that is used here.ResistGeometryto 1e-7–1e-4 on interior surfaces of the circular and DIII-D-like cases.:toroidalreproduces:larto 1e-12 and:rfitzpto 1e-8 (unit test). This checks the shared χ-matching prefactor. With toroidal geometry the closure checks are listed in the closure section above.toroidal_dgeowith the surface's inputs, and the radial label reaches it only throughk_ref.276288065:runtests_slayer_params.jl81/81,runtests_slayer_inputs.jl84/84,runtests_tj_analytic.jl28/28,runtests_slayer_runner.jl79/79.Radial-label sensitivity
The numbers below come from one-off runs, not from code in this PR, so this is how to regenerate them from the public API. On
examples/DIIID-like_SLAYER_example, form the equilibrium and a fresh Riccati Δ′, then callbuild_slayer_inputs(equil, sings, profiles; rs_method=lab, dc_type=:toroidal)and the same withdc_type=:rfitzp, forlabin(:midplane, :flux, :volume). The ratio of the twodc_tmpvalues, andΔ′_rs/dc_tmp, give the rows. The TJ ε scan isruntests_tj_analytic.jl's_lar_equilibrium(ε)swept over ε.DIII-D-like SLAYER example, n = 1 (Δ' diagonal reproduces #403's table: 9.097, −6.196, −16.374):
:midplane(current default):flux:volumerfitzptoroidalBecause dgeo ∝ K exactly and the converted Δ' ∝ K^{2μ} with μ ≈ 1/2, the toroidal threshold margin is nearly label-invariant; the cylindrical
rfitzpformula (∝ r_s/√(da/dψ)) is not. The residual toroidal spread in this table is ~1 % from K^{2μ−1} and ~6 % from χ∥ through the W_d loop. The table predates276288065, which makes that loop toroidal and removes the second source exactly: Δ_crit/K is now identical across labels to 1e-8 (unit test). With either branch the 2/1 sits at about half its threshold under every label, whereas withdc_type = "none"it is unstable.TJ circular ε scan (fresh Δ' per point, surfaces 2/1 and 3/1): the label spread of the margin and the toroidal/rfitzp deviation both vanish roughly linearly in ε (rfitzp spread 1.3–1.7 % at ε = 0.05, toroidal 0.2 %; toroidal/rfitzp 1.006–1.012 midplane, 0.995–0.998 flux). No residual at small ε, so no implementation artefact; finite-ε differences are label physics.
Notes for reviewers
Reference-conversion exponent — decision: linear. The critical-Δ is converted from Connor's Y = (V−V_s)/V_s reference to x̂ = (r−r_s)/r_s with the linear rule (Frobenius exponent ½, exact in the paper's H = 0 ordering). The alternative would be this PR's base's K^(2α) rule with the equilibrium's Mercier exponent α = √(−D_I). Linear is the defensible choice: Δ_crit is a layer-side quantity like the slab Δ̂(Q), W_d and
rfitzp, all defined with the slab exponents ½ ± ½; only the outer Δ' genuinely carries ½ ± α, which is why Tearing - BUGFIX! - Convert Δ' to the r_s reference length before slab-layer matching #403 converts it with K^(2α). Applying c^(2α) to the offset would put an outer-region exponent on a quantity derived without one; the consistent generalization would be a finite-D_I layer theory with thermal conduction, which changes the structure of the D_R term and does not exist. Recorded in thetoroidal_dgeodocstring. Cost of the alternative, c^(2α−1) on Δ_crit (always stabilizing), and the residual label-covariance mismatch of the threshold under the linear rule, K^(2α−1), on the DIII-D-like equilibrium (midplane label):At the 2/1 and 3/1, where accurate γ matters most, the ambiguity is 5–7 % on Δ_crit and the label mismatch is ≤ 1 %. The 10 % mismatch at the 3/2 comes from the midplane label deep in the Shafranov-shifted core (K = 0.52), an argument for the flux label (InnerLayer.SLAYER - API - Choose one default radial label for the Fitzpatrick layer formalism #417), not for changing the exponent. The 5/1 (α = 0.83, D_R = −0.67) is outside the small-D_R ordering under either rule.
Default radial label. This PR inherits
:midplanefrom Tearing - BUGFIX! - Convert Δ' to the r_s reference length before slab-layer matching #403. The evidence in InnerLayer.SLAYER - API - Choose one default radial label for the Fitzpatrick layer formalism #417 and the tables above point to:flux(Fitzpatrick's toroidal-flux label, the one the layer formulas are derived in) as the default for shaped plasmas;:volumeonly coincides withrfitzpby accident of near-circularity. Flipping the default moves rs, S, shear and every SLAYER result and is deliberately left to InnerLayer.SLAYER - API - Choose one default radial label for the Fitzpatrick layer formalism #417 as its own!-tagged PR.D_geois written for everydc_typeonce aResistGeometryexists; it is a diagnostic and does not feed the dispersion unlessdc_type = "toroidal".🤖 Generated with Claude Code