Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
7847c08
Explicit type equil
adrianaghiozzi Aug 12, 2026
98af781
Faster green function
adrianaghiozzi Aug 12, 2026
8adb33b
Tuple -> Vector in integrate_pitch_gar_quadgk to avoid type instability
adrianaghiozzi Aug 14, 2026
152e7e5
Better type instability fix for integrate_pitch_gar_quadgk
adrianaghiozzi Aug 14, 2026
69ad76e
FORCEFREESTATES - OPTIMIZATION - Loosen ballooning ODE abstol to matc…
adrianaghiozzi Aug 14, 2026
d266e79
Merge remote-tracking branch 'origin/develop' into profiling
adrianaghiozzi Aug 17, 2026
e952629
Add back Distance parameter comment
adrianaghiozzi Aug 17, 2026
09cb856
KF - NEW FEATURE - Suppress kinetic terms where the zero-orbit-width …
logan-nc Aug 20, 2026
f95230d
KF - NEW FEATURE - Output kinetic-model validity profiles under Kinet…
logan-nc Aug 20, 2026
3f1dbfe
KF - BUGFIX - Resolve the validity-envelope band on coarse grids; ope…
logan-nc Aug 20, 2026
12fca46
KF - MINOR - Add units to the Validity psi_c annotation (schema metad…
logan-nc Aug 20, 2026
0def353
EQUIL - NEW FEATURE - Pin located kinetic-resonance surfaces into the…
logan-nc Aug 21, 2026
168857b
PE/KF - BUGFIX! - Disable ideal-singularity regularization in self-co…
logan-nc Aug 22, 2026
5aa1122
FFS - IMPROVEMENT - Report sub-threshold near-singular kinetic F-bar …
logan-nc Aug 22, 2026
b3d7ab9
FFS - NEW FEATURE - Add kinetic knots across unresolved near-singular…
logan-nc Aug 22, 2026
f587e9b
PE - BUGFIX - Force reg_spot=0 in kinetic runs even when the deck omi…
logan-nc Aug 22, 2026
e4329fa
TEARING - BUG FIX - Convert Δ' to the r_s reference length before sla…
d-burg Aug 17, 2026
d2494c6
InnerLayer.SLAYER - NEW FEATURE - Add halfwidth and volume radial-lab…
d-burg Aug 20, 2026
b68f031
InnerLayer.SLAYER - FEATURE - Add the Fitzpatrick flux radial label a…
d-burg Aug 20, 2026
29df870
InnerLayer.SLAYER - BUGFIX - Guard the Delta-prime reference-length f…
d-burg Aug 27, 2026
53fffc0
InnerLayer.SLAYER - BUGFIX - Restore the toroidal mode number in the …
d-burg Aug 28, 2026
a5dea9f
InnerLayer.SLAYER - BUGFIX! - Normalize D by the electron diamagnetic…
d-burg Aug 28, 2026
6d25c77
Merge remote-tracking branch 'origin/develop' into sync/403-develop
d-burg Aug 28, 2026
28a0425
InnerLayer.SLAYER - MINOR - Tighten the omega-star commentary
d-burg Aug 28, 2026
8df7535
InnerLayer.SLAYER - MINOR - Deduplicate the iota_e normalization comm…
d-burg Aug 28, 2026
63dcc81
InnerLayer.SLAYER - MINOR - Compress the radial-label commentary
d-burg Aug 28, 2026
8ae06cd
Regression - BUGFIX - Materialize override decks beside the example s…
d-burg Aug 28, 2026
21fd724
Regression - TEST - Add the n=2 SLAYER case guarding the 3/2 surface …
d-burg Aug 28, 2026
9b44f8c
InnerLayer.SLAYER - MINOR - Restore the D-normalization formula citation
d-burg Aug 28, 2026
4c70d42
Regression - MINOR - Gitignore the override rundirs and note the load…
d-burg Aug 28, 2026
e32740a
InnerLayer.SLAYER - TEST - Give the shared fixtures the physical oppo…
d-burg Aug 28, 2026
c371c89
Merge branch 'develop' into bugfix/slayer-dnorm-iota-e
d-burg Aug 28, 2026
00621d5
Merge branch 'develop' into bugfix/slayer-omega-star-ntor
d-burg Aug 28, 2026
2024a8b
VACUUM - IMPROVEMENT - adding 3D conformal walls
jhalpern30 Aug 31, 2026
5f21e87
Merge branch 'develop' into feature/vacuum-3d-conformal-wall
jhalpern30 Aug 31, 2026
e11e168
InnerLayer.SLAYER - MINOR - Name the Mercier exponent alpha and split…
d-burg Sep 3, 2026
12f64da
InnerLayer.SLAYER - MINOR - Narrow the Delta-prime conversion signatu…
d-burg Sep 3, 2026
2321a49
Merge develop (via #398) into feature/kinetic-axis-validity
logan-nc Sep 4, 2026
37c4eeb
KF - BUGFIX! - Keep the validity envelope clear of rational surfaces
logan-nc Sep 4, 2026
4dd32b2
FFS/KF/PE - MINOR - Clean-code review follow-ups
logan-nc Sep 4, 2026
25d285e
Regression - TEST - Track Q_e/Q_i on the n=1 SLAYER case and drop the…
d-burg Sep 4, 2026
47feada
InnerLayer.SLAYER - REFACTOR - Fold the toroidal mode number into the…
d-burg Sep 4, 2026
bddd428
InnerLayer.SLAYER - MINOR - Drop the redundant D-normalization commen…
d-burg Sep 4, 2026
14254f5
Merge remote-tracking branch 'origin/develop' into feature/kinetic-ax…
logan-nc Sep 5, 2026
18587da
Merge branch 'develop' into bugfix/slayer-dprime-reference-length
ebursch Sep 11, 2026
f36ecef
Tearing - BUGFIX! - Convert Δ' to the r_s reference length before sla…
ebursch Sep 11, 2026
e01d105
Equilibrium - BUGFIX! - Build the root-area weight with the inverse F…
logan-nc Sep 12, 2026
5cb7adc
Repo - TEST - Track the root-area-weighted forcing field in the diiid…
logan-nc Sep 12, 2026
9674e1e
ForcingTerms - BUGFIX! - Take the coil-grid helicity from the equilib…
logan-nc Sep 12, 2026
76808df
Equilibrium - MINOR - Take the unit mode to θ-space with the library …
logan-nc Sep 13, 2026
87a9b60
Merge branch 'develop' into bugfix/slayer-dnorm-iota-e
d-burg Sep 14, 2026
f4d33ae
Merge branch 'develop' into bugfix/slayer-omega-star-ntor
d-burg Sep 14, 2026
384bb92
VACUUM - MINOR -addressing Daniel's PR review
jhalpern30 Sep 15, 2026
25a1096
Merge branch 'develop' into feature/vacuum-3d-conformal-wall
jhalpern30 Sep 15, 2026
ed72b9f
Vacuum - FEATURE - Add 3D conformal conducting wall capabilities (#436)
jhalpern30 Sep 16, 2026
cd0fabd
Merge branch 'develop' into bugfix/slayer-dnorm-iota-e
d-burg Sep 16, 2026
2da8edc
Tearing - BUGFIX - Describe D_norm with the iota_e normalization in t…
d-burg Sep 16, 2026
74b6c94
InnerLayer.SLAYER - TEST - Cover the diamagnetic-frequency guards in …
d-burg Sep 16, 2026
cbe890c
InnerLayer.SLAYER - BUGFIX! - Normalize D by the electron diamagnetic…
d-burg Sep 16, 2026
d46a553
Merge branch 'develop' into bugfix/slayer-omega-star-ntor
d-burg Sep 16, 2026
11666e3
Merge remote-tracking branch 'origin/develop' into profiling
adrianaghiozzi Sep 17, 2026
ea5d465
Regression - BUGFIX - Give each override run its own uniquely named r…
d-burg Sep 17, 2026
df8f940
LocalStability - PERF! - Loosen ballooning ODE abstol and fix pitch-q…
logan-nc Sep 17, 2026
0d5275e
Merge branch 'develop' into bugfix/slayer-omega-star-ntor
d-burg Sep 17, 2026
6e32708
InnerLayer.SLAYER - BUGFIX - Restore the toroidal mode number in the …
d-burg Sep 17, 2026
73f7e22
Merge branch 'develop' into bugfix/equilibrium-sqrtamat-inverse-fourier
ebursch Sep 18, 2026
158c9eb
Equilibrium - BUGFIX! - Build the root-area weight with the inverse F…
ebursch Sep 18, 2026
0d2c8f3
Merge branch 'develop' into bugfix/forcingterms-helicity-from-current…
ebursch Sep 18, 2026
f574611
Equilibrium - BUGFIX - Read the IMAS plasma current only when the fil…
logan-nc Sep 19, 2026
44fd0a9
ForcingTerms - BUGFIX! - Take the coil-grid helicity from the equilib…
ebursch Sep 19, 2026
e0b4c54
Merge remote-tracking branch 'origin/performance/consistent-surface-t…
logan-nc Sep 19, 2026
cf7a40c
Merge remote-tracking branch 'origin/develop' into feature/kinetic-ax…
logan-nc Sep 19, 2026
2dcbbf1
KineticForces - BUGFIX! - Use one validity boundary and bound its rat…
logan-nc Sep 19, 2026
292c50b
KineticForces - TEST - Cover the near-axis validity boundary and regu…
logan-nc Sep 19, 2026
fd3411a
ForceFreeStates/KineticForces - MINOR - Trim the review-flagged comments
logan-nc Sep 19, 2026
0f8dd76
KineticForces - MINOR - Compress the nested-tolerance commentary
logan-nc Sep 19, 2026
2abea51
KineticForces - BUGFIX - Annotate the Validity group where it is written
logan-nc Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ scratch/

# Local profiling scratch (one-off study scripts, not part of the package)
profiling/

# Override-case rundirs materialized beside their example by the regression harness
.regress-override-*
2 changes: 1 addition & 1 deletion docs/development/hdf5-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ These rules govern `gpec.h5` (and any future GPEC-produced HDF5 output); harness
- **"rational" over "singular"** in dataset names (`rational_psi`, `rational_q`, `rational_m`, `rational_n`, `rational_index`, `rational_count`) — kinetic/resistive runs are not singular at the rationals. Specifier order is standardized specifier-first (`rational_psi`, never `psi_rational`).
- **Vector components** follow `[d]<variable>[_<representation>]_<coordinate>[dpsi]` — the variable always comes first and the coordinate is always the trailing subscript. A bare coordinate suffix is the **contravariant** component (`xi_psi` = ξ^ψ), `_cov_` marks the **covariant** one (`b_cov_theta` = b_θ), a leading `J` marks a **Jacobian-weighted** component (`Jxi_theta` = J·ξ^θ), and other representations sit in the same slot (`xi_clebsch_psi`, `dxi_clebsch_psidpsi`). Never drop the variable: `clebsch_psi` is wrong because it does not say *what* is being represented. There is no HDF5/netCDF standard for super- vs subscripts — flat `_` names are universal — so the typeset form always appears in the dataset's `long_name`.
- **Coordinates**: the radial abscissa is `psi` (normalized poloidal flux ψ_N) and the poloidal one is `theta` in every group; never `psi_n`, `xs`, or `ys`.
- **One name per physical quantity**: a quantity written in several groups carries the identical leaf name everywhere (`rational_psi` in `SingularSurfaces/`, `Solutions/GalerkinIntegration/`, and `SingularCoupling/`; `Delta_prime_matrix` in `SingularSurfaces/` and `Tearing/PerSurface/`; `dVdpsi` in `Profiles/`, `SingularSurfaces/`, and `KineticForces/<method>/`) — the group supplies the context, the leaf supplies the identity.
- **One name per physical quantity**: a quantity written in several groups carries the identical leaf name everywhere (`rational_psi` in `SingularSurfaces/`, `Solutions/GalerkinIntegration/`, and `SingularCoupling/`; `Delta_prime_matrix` in `SingularSurfaces/` and, on the GGJ path, `Tearing/PerSurface/` — the r_s-referenced matrix the slab layer matches is a different quantity and so carries its own name, `Tearing/PerSurface/Delta_prime_matrix_rs`; `dVdpsi` in `Profiles/`, `SingularSurfaces/`, and `KineticForces/<method>/`) — the group supplies the context, the leaf supplies the identity.

## Inputs live only under `Input/`

Expand Down
29 changes: 29 additions & 0 deletions docs/src/kinetic_forces.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,35 @@ terms respectively, and do not modify the stored kinetic profile splines.
when computing the `toroidal_rotation_factor` back-solve. Julia uses a
clean reimplementation with consistent pre-scaling derivatives throughout.

## Regularization: why kinetic runs set `reg_spot = 0`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like too many lines for a comment, per our desire to reduce Claude verbosity.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed in fd3411a, though with one push-back worth stating: this is docs/src/kinetic_forces.md, user-facing documentation rather than a code comment, so the one-line rule in CLAUDE.md does not really apply to it. A user who sets reg_spot and finds it silently overridden needs somewhere that explains why.

What I did cut is the part that genuinely did not belong: the single-case DIII-D validation table (max |xi^alpha|, torque, EL dissipation for one deck). Per-case benchmark numbers go stale and belong in the PR review package, not in permanent docs. The physics argument and its citations stay, and I added the Logan (2015) Ch. 7 Eq. 7.46 reference alongside Park & Logan (2017) since it states the same resonance shifting/splitting result in GPEC's own notation.

Net effect on that section: 36 lines to 23.


`[PerturbedEquilibrium] reg_spot` smooths the displacements before they drive the NTV
integrand, multiplying ``\xi^{\psi\prime}`` and ``\xi^\alpha`` by
``Q^2/(Q^2 + \mathrm{reg\_spot}^2)`` with ``Q = m - nq``. It exists because **ideal** MHD is
singular at the rationals: ``\bar F_\mathrm{ideal} = Q F Q`` has ``\det \bar F = 0`` there, so
those two components diverge as ``1/Q`` and the torque integral does not converge.

The **self-consistent kinetic** workflow has no such singularity. Park & Logan
([Phys. Plasmas 24, 032505 (2017)](https://doi.org/10.1063/1.4978562), §III D) decompose the
kinetic composite matrix as ``F_k = Q \bar F_k Q - P_l^\dagger Q - Q P_u + R_1`` where
``R_1 \neq 0`` at ``Q = 0``; with finite torque ``\det \bar F`` is complex, its zeros leave the
real ``\psi`` axis, and the singularity is removed from both the solution and the torque
integral. (Torque-free kinetic energy principles instead *shift and split* the zeros to
``\psi_r \mp r_{L,R}``, where the singularity is logarithmic and integrable — still not a case
for smoothing.) Logan, *Electromagnetic Torque in Tokamaks with Toroidal Asymmetries* (PhD thesis,
Princeton, 2015), Ch. 7 Eq. 7.46 states the same resonance shifting/splitting result in GPEC's own
notation.

GPEC therefore **forces `reg_spot = 0` whenever `kinetic_factor > 0`**, logging the override.
Leaving it on suppresses a finite physical response and does so inconsistently — ``\xi^\psi``
is never regularized, so damping the other two breaks their near-resonance cancellation in
``\delta B/B`` and leaves a spurious residue driving the NTV integrand.

The check that this is right is that the NTV torque and the Euler–Lagrange solution's own
dissipation ``-2n\,\mathrm{Im}\langle \xi, u_2\rangle/4\mu_0`` — two independent calculations
of the same quantity — agree to a fraction of a percent with the regularization off, and disagree
by tens of percent with it on.

## HDF5 outputs: complex torque convention and the EnergyIntegrals layout

The method level of `KineticForces/<method>/` reports the two physical scalars a user
Expand Down
7 changes: 5 additions & 2 deletions examples/DIIID-like_SLAYER_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ dmlim = 0.2 # Truncate integration at (last_rational_q + dmli
# Runs in the force_termination path (PE skipped). Kinetic profiles (incl. the
# χ⊥/χ_φ heat/momentum diffusivities) are read from the standardized HDF5
# kinetic file shared with the kinetic/NTV physics. Per-surface (uncoupled)
# analysis: the headline is the unstable 2/1; the validity gate drops surfaces
# with no real root (e.g. the 5/1, whose Δ' BVP yields a huge uncancellable Δ').
# analysis: track the 2/1, 3/1, and 4/1 (the headline is the unstable 2/1).
# The 5/1 and 6/1 sit near the edge (ψ_N ≥ 0.97), far from the core where the
# slab-layer approximation holds; their Δ' are excessively large and likely
# invalid, no Re/Im contour crossing polishes to a true zero, and the validity
# gate correctly reports them as no_root rather than a spurious growth rate.
enabled = true # Run the SLAYER tearing-mode analysis
inner_model = "slayer_fitzpatrick" # Inner-layer Δ(Q) model: "slayer_fitzpatrick", "ggj_shooting", or "ggj_galerkin"
scan_mode = "amr" # Q-plane scan strategy: "amr" (adaptive refinement) or "brute_force"
Expand Down
1 change: 1 addition & 0 deletions examples/DIIID-like_ideal_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@ f0type = "maxwellian" # Equilibrium distribution
moment = "pressure" # Pressure-moment NTV torque
atol_xlmda = 1e-9 # Absolute tolerance for inner pitch + energy integrations
rtol_xlmda = 1e-5 # Relative tolerance for inner pitch + energy integrations
axis_validity_suppression = true # Suppress kinetic terms inside the near-axis orbit-width validity limit
write_outputs_to_HDF5 = true # Write outputs to the HDF5 file
verbose = true # Enable verbose logging
1 change: 1 addition & 0 deletions examples/Solovev_kinetic_NTV_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,4 @@ f0fac = 1 # Scale toroidal field at constant pressure (β, q change; Φ, p,

[KineticForces]
kinetic_file = "kinetic.dat" # Kinetic profile file: psi_n, n_i, n_e, T_i, T_e, omega_E columns
axis_validity_suppression = true # Suppress kinetic terms inside the near-axis orbit-width validity limit
1 change: 1 addition & 0 deletions examples/a10_kinetic_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ nutype = "harmonic" # Collision operator (zero, small, krook, harmoni
f0type = "maxwellian" # Distribution function (maxwellian, jkp, cgl)
atol_xlmda = 1e-9 # Absolute tolerance for inner pitch + energy integrations
rtol_xlmda = 1e-5 # Relative tolerance for inner pitch + energy integrations
axis_validity_suppression = true # Suppress kinetic terms inside the near-axis orbit-width validity limit
11 changes: 11 additions & 0 deletions regression-harness/cases/diiid_n1.toml
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,14 @@ extract = "value"
label = "Runtime (s)"
noise_threshold = 0.0
order = 999

# Root-area-weighted (coordinate-invariant) forcing field b̃ on the control surface: the
# applied coil spectrum pushed through the √(J|∇ψ|) weight operator. Tracks the field-space
# conversion that every ResponseMatrices/ field-space output depends on.
[quantities.forcing_b_rootarea_norm]
h5path = "PerturbedEquilibrium/forcing_b_root_area"
type = "complex_vector"
extract = "norm"
label = "||forcing b~|| (root-area-weighted)"
noise_threshold = 1e-10
order = 1000
22 changes: 21 additions & 1 deletion regression-harness/cases/diiid_slayer_n1.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Regression case: DIII-D-like H-mode n=1 SLAYER tearing-mode growth rates.
# Uncoupled per-surface analysis with the AMR Q-plane scan; tracks the per-surface
# layer parameters and the extracted roots (Q, ω, γ) for the inner three rationals.
# layer parameters, the diamagnetic drive Q_e/Q_i, and the extracted roots (Q, ω, γ)
# for the inner three rationals. The n-scaling of Q_e/Q_i, which no single-n case can
# constrain, is covered by a unit test in test/runtests_slayer_inputs.jl instead.
# Each [quantities.*] block names an HDF5 path in the run output, how to extract it,
# and the noise floor below which a difference is treated as zero.
[case]
Expand Down Expand Up @@ -89,6 +91,24 @@ label = "SLAYER iota_e"
noise_threshold = 1e-12
order = 19

# Normalized diamagnetic frequencies. iota_e above is their ratio, so it is blind
# to a common factor on both; these pin the drive itself.
[quantities.slayer_Q_e]
h5path = "Tearing/PerSurface/Q_e"
type = "real_vector"
extract = "all_real"
label = "SLAYER Q_e"
noise_threshold = 1e-12
order = 20

[quantities.slayer_Q_i]
h5path = "Tearing/PerSurface/Q_i"
type = "real_vector"
extract = "all_real"
label = "SLAYER Q_i"
noise_threshold = 1e-12
order = 21

# Tearing eigenvalue (coupled mode → length 1). The headline deliverable:
# a real, nonzero growth rate on a realistic equilibrium. Root-extraction
# is sensitive to the AMR cell topology and ODE solver, so the Q/ω/γ
Expand Down
22 changes: 13 additions & 9 deletions regression-harness/src/runner.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ end
Materialize the directory GPEC will actually run in.

With no `overrides`, the example deck runs in place (returns it untouched). With overrides,
the deck is copied to a throwaway temp dir and the named gpec.toml keys are patched there,
so one shared example can serve several cases (e.g. a collisionless variant via
`"KineticForces.nutype" => "zero"`). Override keys are dotted paths into the TOML; missing
intermediate tables are created. Returns `(rundir, is_temp)`; the caller removes the temp
tree when `is_temp`.
the deck is copied to a uniquely named throwaway sibling of the example dir and the named gpec.toml keys are
patched there, so one shared example can serve several cases (e.g. a collisionless variant via
`"KineticForces.nutype" => "zero"`). The copy sits beside the original so relative file
references in the deck (e.g. `SLAYER.profile_file = "../<other_example>/..."`) still resolve.
Override keys are dotted paths into the TOML; missing intermediate tables are created.
Returns `(rundir, is_temp)`; the caller removes `rundir` when `is_temp`.
"""
function _materialize_rundir(example_path::String, overrides::Dict{String,Any})
isempty(overrides) && return (example_path, false)
rundir = joinpath(mktempdir(), "deck")
cp(example_path, rundir)
# Unique per invocation, so concurrent runs from one checkout cannot share (and race on) a rundir.
rundir = mktempdir(dirname(example_path); prefix=".regress-override-" * basename(example_path) * "-", cleanup=false)
cp(example_path, rundir; force=true)
rm(joinpath(rundir, "gpec.h5"); force=true) # drop any stale output copied along
cfg = TOML.parsefile(joinpath(rundir, "gpec.toml"))
for (dotted, val) in overrides
Expand Down Expand Up @@ -523,7 +525,8 @@ function run_local(db::SQLite.DB, case_spec::CaseSpec, repo_root::String;
rm(runinfo_file; force=true)
end
if rundir_is_temp && rundir !== nothing
rm(dirname(rundir); recursive=true, force=true)
# rundir sits beside the example; removing its parent would delete examples/ itself
rm(rundir; recursive=true, force=true)
end
end
end
Expand Down Expand Up @@ -647,7 +650,8 @@ function run_at_commit(db::SQLite.DB, commit_hash::String, ref_name::String,
rm(runinfo_file; force=true)
end
if rundir_is_temp && rundir !== nothing
rm(dirname(rundir); recursive=true, force=true)
# rundir sits beside the example; removing its parent would delete examples/ itself
rm(rundir; recursive=true, force=true)
end
if own_worktree && worktree_path !== nothing
remove_worktree(worktree_path, repo_root)
Expand Down
6 changes: 1 addition & 5 deletions src/Analysis/CoilForcing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,7 @@ function plot_mode_spectrum(forcing_modes; mlow=nothing, mhigh=nothing, save_pat
end

# Helicity = sign(Bt)·sign(Ip); the SFL→machine toroidal-angle handedness.
function _equil_helicity(equil)
bt = isnothing(equil.params.bt_sign) ? 1 : Int(sign(equil.params.bt_sign))
ip = isnothing(equil.params.crnt) ? 1 : Int(sign(equil.params.crnt))
return bt * ip
end
_equil_helicity(equil) = equil.params.bt_sign * equil.params.ip_sign

# Unpack `modes` (a Vector of ForcingMode-like objects filtered to toroidal mode `n`,
# or a `(m_vals, amplitudes)` tuple) into parallel m and complex-amplitude vectors.
Expand Down
Loading
Loading