Skip to content

Tearing - FEATURE - Add the modified Rutherford equation with saturated-island roots, applied-field forcing and a Callen preset - #665

Draft
logan-nc wants to merge 27 commits into
developfrom
feature/mre-islands
Draft

logan-nc wants to merge 27 commits into
developfrom
feature/mre-islands

Conversation

@logan-nc

@logan-nc logan-nc commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

Release note

  • Audience: users
  • Numerical impact: none (harness @ 862b2f6)
  • Migration: none

GPEC can now solve the steady modified Rutherford equation (MRE) per rational surface, with every term switchable: classical Δ′(w), finite-width saturation, bootstrap, curvature, ion polarization and the applied-field forcing of a coil-driven perturbed equilibrium. It reports seed thresholds, every saturated island width with the seed that reaches it, the resonant field equivalent to each seed island, and the critical applied fields at which a forced island opens and at which it triggers growth past the seed threshold. Callen's reduced threshold for coil-driven islands is a preset of the MRE.

What is added

  • Tearing.ModifiedRutherford ([Tearing.ModifiedRutherford], solve(ModifiedRutherfordProblem(ffs; tearing, ...))).
    • Terms: F(w) on a log-spaced grid of full widths, built from the surfaces' Δ′, ResistGeometry (D_I, D_R, H) and the kinetic profiles.
    • Curvature: curvature_model = "lutjens" (default) matches SLAYER's toroidal critical offset D_c at small w; "benjamin" gives the Benjamin form.
    • Saturation: the finite-width term of Fitzpatrick 2023 Eq. 9.15 (off by default), with the current profile from Grad–Shafranov.
    • Gate: the SLAYER TearingProblem supplies γ, the layer width δ_r that starts the root search, and D_c.
    • Roots:
      • the seed threshold w_m and the first saturated width w_sat;
      • every saturated branch w_sat_all, with the seed that reaches it w_seed_all, and w_seed_unsaturated;
      • the resonant field of the vacuum island as wide as each seed (b_m, b_seed_all, b_seed_unsaturated, b_m_star, via the new public vacuum_island_field);
      • validity flags: unsaturated, multiple_saturated, outside_plasma, gate_disagrees, saturation_unconverged, among others.
    • Forcing: with pe (a coil-driven perturbed equilibrium), the term Δ_RMP (dρ/dψ_N)(w_v/w)² of Callen's Eq. 17 enters F, so the roots and branches include the applied field (forcing = false switches it off). The critical fields b_crit (a forced window opens below the unforced seed threshold) and b_trigger (the window reaches that threshold) follow from the unforced F, since the forcing scales with the vacuum resonant field.
    • Callen preset: preset = "callen" (CALLEN_PRESET) reduces F to Callen's steady state in his units. Its roots and b_crit equal the positive roots of Callen's cubic and his closed-form critical field (API test against the closed form evaluated outside the MRE, 10⁻⁸). The critical vacuum island width is w_v (b_crit/B_vac)^½.
    • Output: Tearing/ModifiedRutherford/{PerSurface,Terms} in gpec.h5, written from one annotation table.
  • Tearing.ResonantForcing: the resonant forcing the MRE uses (vacuum and total resonant fields, Δ_RMP, vacuum island width on the toroidal-flux radius ρ) and forcing_thresholds. When the force-free-states solve has no Δ′ (the perturbed equilibrium needs a forward solve), main adds one Riccati solve for the MRE, timed as tearing_delta_prime.
  • InnerLayer.layer_plasma: one public view of the kinetic plasma (profiles, Z_eff, ion mass and charge, resistivity model). The SLAYER tearing solve, the layer-overlap scan, and the MRE now use it in place of private helpers. Values are unchanged.
  • Docs: the MRE section of docs/src/inner_layer.md, with the forcing and the Callen preset; its group in docs/development/hdf5-conventions.md.
  • Harness cases diiid_slayer_n1_mre (a base = variant of diiid_slayer_n1_toroidal) and diiid_n1_callen (the MRE Callen preset on the coil forcing).

Part of #630 and #629. Still open after this PR:

  • #630 (general MRE): a flux-surface-averaged bootstrap current ⟨j_bs·B⟩ (Sauter/Redl), so the default can use one normalization family for the bootstrap and curvature terms.
  • #629 (Callen island features): pressure flattening across saturated islands.

Regression report

All 29 cases, origin/develop (a1149c5) against this branch at 862b2f6:

tj_n1 | 25 unchanged
diiid_slayer_n1_toroidal | 5 unchanged
gal_resistive_diiid_galinner | 11 unchanged
ggj_ray_q500i | 4 unchanged
solovev_kinetic_nuzero | 14 unchanged
diiid_error_field | 10 unchanged
diiid_slayer_n1 | 22 unchanged
diiid_truncation_n1 | 12 unchanged
diiid_slayer_n1_mre | 12 missing/N/A (new case)
tj_kinetic_calculated | 20 unchanged
diiid_slayer_n1_coupled | 7 unchanged
gal_resistive_diiid | 11 unchanged
solovev_n1 | 22 unchanged
ggj_reference | 4 unchanged
tj_kinetic_ntv | 7 unchanged
solovev_multi_n | 15 unchanged
diiid_n1 | 53 unchanged
tj_kinetic_ntv_rates | 2 unchanged
diiid_slayer_n1_chi_perp_e | 22 unchanged
diiid_n1_riccati | 17 unchanged
diiid_n1_callen | 3 missing/N/A (new case)
lar_ggj_tearing_n1 | 3 unchanged
diiid_multi_n | 19 unchanged
diiid_slayer_n1_winding | 22 unchanged
efit_fixedbdy_separatrix | 5 unchanged
tj_kinetic_multiion | 6 unchanged
diiid_kinetic_multi_n | 8 unchanged
diiid_n1_riccati_precompiled | 17 unchanged
lar_reversed_shear_n1 | 22 unchanged

The two new cases have no develop baseline, since the features are new on this branch. Against the previous head of this branch (02de80f), the three diiid_n1_callen preset quantities and all twelve diiid_slayer_n1_mre quantities are unchanged, so retiring CallenProblem moves no MRE values. The four Callen quantities the case used to track went with it.

Notes for reviewers

  • The default F(w) is provisional. It pairs the Lütjens curvature with Benjamin's bootstrap term, which come from different normalization families. The follow-up adds ⟨j_bs·B⟩ so that both can take Fitzpatrick's 4I₂ coefficient (Fitzpatrick 2023 Eq. 14.196).
  • Carrying Lütjens' term through M(w). Lütjens' form is large-aspect-ratio. Carrying it through the classical factor M(w) = (w/2)^(2α−1)√(−4D_I) is a convention that keeps the small-w match with SLAYER's gate at any D_I. The docstrings say so.
  • Linearly unstable surfaces. SLAYER γ > 0 marks a surface linearly unstable. It then gets no seed threshold but keeps its saturated widths.
  • Branch output. Branch arrays are surface × branch, padded with NaN to the most branches found on any surface. HDF5 dims are (branch, surface), like the term matrices.
  • Critical fields. With the island locked in phase with the field, the forced F is positive where k B_vac > G(w) = −F₀(w)w². b_crit is the smallest local minimum of G below the unforced seed threshold, the bottom of the search range included. b_trigger is the largest G between there and the threshold. For Callen's F₀ the first is his critical field exactly; the unit test checks both against brute force on an analytic G with a window and a barrier.
  • No separate Callen solver. An earlier commit on this branch had a closed-form CallenProblem with [Tearing.Callen]; the preset replaces it, and a deck with [Tearing.Callen] stops at parse time. The closed form survives in the API test as the preset's reference, and Callen's Fig. 8 widths are checked through the MRE root finder. Its extra outputs (ρ_i, w_pol, Δ_c, the root count) are not written. It is in the branch history if reviewers want it back.
  • Seed field. vacuum_island_field is the exact inverse of the perturbed equilibrium's island width. The API test checks it against island_half_width and resonant_area_weighted_field at every surface.
  • Test run. On the full suite earlier on this branch, runtests_equil.jl (TokaMaker re-form) failed identically on develop and every other file passed. At this head, the files the last commit touches pass: runtests_modified_rutherford.jl 78/78, runtests_result_struct.jl 241/241 and runtests_solve_api.jl 256/256.
  • Left for follow-ups. Sharing the θ-metric kernel with ResistEval is left for a follow-up pull request.

logan-nc and others added 26 commits November 6, 2026 23:48
…ford terms and F(w) output

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…diagnostics and format the submodule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…e ModifiedRutherford sub-table

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
… linear gate and Callen preset

The modified Rutherford solve now returns the seed threshold w_m and saturated
width w_sat per surface (grid brackets refined by Brent), their minimum
w_m_star, and validity flags (fully stabilized, unstable to any seed,
resistive interchange, linear tearing, below w_ib, large Delta' w, complex
Delta'). A [Tearing] layer solve in the same deck gates the roots and sets the
lower end of the search from its layer width (API keyword `tearing`).
The island frequency defaults to omega_*e (1 + eta_e/4) (omega_model), and
delta_prime_factor, D_nc_factor and rho_theta_factor support scans.

[Tearing.Callen] / CallenProblem(pe) adds Callen's reduced equation for
RMP-driven islands on a coil-driven perturbed equilibrium: critical vacuum
half-width, critical resonant field and the cubic roots, with the vacuum
resonant field from the coil harmonic on each rational surface. The roots
solve the cubic exactly, twice the Fortran 1.6.0 values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
Callen 2016 Eq. 17 poses the steady cubic in the full island width with w_vac = 2h_v,
so its exact roots are full widths. hw_min and hw_sat are now half of them, matching
Fortran GPEC 1.6.0 (roots agree to 1.2e-3 on the DIII-D-like example at 10x coil current).
Adds the Callen Fig. 8 roots as a unit test and a deck and API parity case for [Tearing.Callen].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
… unstable surfaces, use true Delta' in Callen

- Polarization rho_theta, w_ib and the layer width delta_r map metres to psi_N with the
  radial_label dr/dpsi used by SLAYER and LayerOverlap, replacing 2 psi_s / r_s.
- Linearly unstable surfaces (gamma > 0) keep w_sat; w_m is NaN there. Only interchange
  instability or D_I >= 0 excludes a surface.
- Callen preset takes Delta_c from the computed Delta' referred to rho_tor by default
  (main adds a Riccati solve); delta_prime_source = "cylindrical" restores Callen's -2m/rho.
- The MRE accepts delta_prime_source = "cylindrical" (-2m referred to psi_N), so it runs
  from a Forward solve without Delta'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…Rutherford equation

saturation = true replaces r_s Delta' by r_s Delta'(0) - C_sat W/r_s (Fitzpatrick 2023 Eq. 9.15,
after Hastie, Militello & Porcelli 2005), C_sat = 0.8 alpha_s^2 - 0.27 beta_s - 0.09 alpha_s, with
alpha_s, beta_s from J_z = (2 - s)/q in the radial_label radius. beta_s takes r^2 (dimensionless),
which reproduces Fitzpatrick's Fig. 9.2. The correction is referred to psi_N as the outer Delta' is
and carried through the classical term's pressure factor. Off by default; the unit test checks the
coefficients against the Wesson cylinder and the root against Eq. 9.21.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…franov with a windowed fit

The cylindrical identity J_z = (2 - s)/q gives J_z <= 0 at q = 2 on shaped equilibria (the
radial_label shear exceeds 2 inside the current channel), and its central differences need q''',
which is piecewise constant on the spline, so C_sat did not converge. J_z is now
(R0^2/B0) <mu0 j_phi/R> from the Grad-Shafranov flux-surface identity (current_density_profile),
J_z' and J_z'' come from a least-squares quadratic over psi_N +- saturation_window (0.02, replacing
saturation_dpsi), and saturation_unconverged flags surfaces whose C_sat moves more than 20% at half
or double the window or whose window leaves the grid. On the TJ equilibrium J_z(0) = 2/q0 to 0.5%
and C_sat at q = 2 matches the cylinder to 5%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…rm matched to the SLAYER critical Delta

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…earingResult is saved after them

The TearingResult writer now clears only its own part of Tearing/, and record_inputs! keeps recorded
sub-tables a result does not state, so the saved file no longer depends on the order of the writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
… factor and fix review findings

The Lütjens term was in Δ′_mn units while the other MRE terms are in ψ_N⁻¹; it now passes through
classical_term as the saturation term does, so classical plus curvature change sign where SLAYER's gate
does at every D_I (unchanged at D_I = −¼). large_delta_prime_w uses the dimensionless Δ′(w) w.
Also: Callen single-n parse guard, the cubic at a = 0, the J_z sign probe, stale docstrings, and
tests that reuse main's Riccati solve and drop assertions restating the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…ratio by their real keys

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…in's return

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…convention for D_I ≠ −¼

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…d test header

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…ng root above the island start and flag unsaturated, off-grid and gate-disagreeing surfaces

An island seeded at w_m grows to the first root above it, not the largest; where F(w_lo) > 0 it starts from w_lo.
New flags: unsaturated (F > 0 at w_max), outside_plasma (psi +- w_sat/2 leaves the psi grid) and gate_disagrees
(the sign of F(w_lo) against the linear growth rate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…Callen module with output under Tearing/Callen/PerSurface

Callen shares no geometry or terms with the modified Rutherford equation. Its result names the surface rational_psi
and rational_q, as TearingResult does, and its datasets move to Tearing/Callen/PerSurface/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…or every layer consumer

layer_plasma returns the profiles, Z_eff, the main-ion mass and charge, the mass per electron, the resistivity
closure and the chi splines once; the tearing solve, the layer-overlap scan, Callen, layer_eta and layer_rho use it
in place of the SLAYER private helpers. Values passed to the layers are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…d the result by keyword and drive the writer from its annotations

The surface loop becomes _surface_inputs, _mre_terms, _gate and _surface_roots, and ModifiedRutherfordResult is
built by keyword. The surface identity is rational_psi and rational_q, as in Tearing/PerSurface/; the writer emits
every annotated field. The plasma comes from layer_plasma, so the ion mass
is the one the layers use. Both Tearing subgroups are listed in hdf5-conventions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…e the MRE harness case a variant of diiid_slayer_n1_toroidal

Info/Runtimes gains callen_delta_prime. The Callen case reads Tearing/Callen/PerSurface/ and comments its override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…h the seed threshold that reaches it

When F crosses zero more than once, w_sat is the first of several saturated
solutions. The result now keeps all of them (w_sat_all, branch-padded) with
the rising crossing below each (w_seed_all), the seed above which the island
grows past w_max (w_seed_unsaturated), the count n_sat and a
multiple_saturated flag, and writes them under PerSurface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…he vacuum island as wide as each seed threshold

b_m, b_seed_all and b_seed_unsaturated are the resonant area-weighted fields
[T] whose vacuum islands are as wide as w_m, each saturated branch's seed
and the unsaturated seed, B = pi |n q'| chi1 w^2/(8A), with b_m_star their
minimum. The new public vacuum_island_field inverts the perturbed
equilibrium's island width; the API test checks it against that width and
field at every surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
…quation, with its critical fields and a Callen preset

- Tearing.ResonantForcing: the resonant forcing of a coil-driven perturbed equilibrium (vacuum and total resonant
  fields, Callen's Delta_RMP, vacuum island width, toroidal-flux radius) shared by Callen and the MRE, and
  forcing_thresholds for the critical fields.
- ModifiedRutherfordProblem takes pe; the forcing term Delta_RMP (drho/dpsi_N)(w_v/w)^2 enters F (forcing = false
  switches it off), so roots and branches include the applied field. b_crit (a forced window opens below the unforced
  seed threshold) and b_trigger (it reaches that threshold) follow from the unforced F.
- delta_prime_model and pol_model gain :callen; CALLEN_PRESET (deck preset = "callen") reduces F to Callen's steady
  state, whose roots and b_crit equal CallenProblem's.
- main shares one Riccati Delta' solve between the MRE and Callen when the force-free-states solve has none.
- HDF5 output, docs, tests and the diiid_n1_callen harness case extended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
@logan-nc logan-nc self-assigned this Nov 8, 2026
@github-actions github-actions Bot added the feature New capability label Nov 8, 2026
…allen preset replaces it

The MRE's preset = "callen" reproduces Callen's cubic roots and critical field on the same resonant forcing, so the
closed-form CallenProblem, its [Tearing.Callen] deck section and its Tearing/Callen/PerSurface output are removed;
a deck with [Tearing.Callen] now stops at parse time. The closed form stays as the test reference for the preset,
evaluated from resonant_forcing and Callen's polarization width, and the Callen 2016 Fig. 8 check now runs through
the MRE root finder. main runs its Riccati Δ′ solve for the MRE alone, and the diiid_n1_callen harness case tracks
the preset's quantities.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgNxCBwLNHQmVq7iXMCTae
@logan-nc logan-nc changed the title Tearing - FEATURE - Add the modified Rutherford equation with saturated-island roots, applied-field forcing and the Callen threshold Tearing - FEATURE - Add the modified Rutherford equation with saturated-island roots, applied-field forcing and a Callen preset Nov 8, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant