Skip to content

Commit 3cea719

Browse files
logan-ncclaude
andcommitted
EQUIL/EXAMPLES - Default psihigh_from_layer_overlap on across every deck
Flips EquilibriumConfig.psihigh_from_layer_overlap to true and sets it explicitly, annotated, in all 17 decks. Kinetic profiles are wired via a [SLAYER] profile_file entry using each deck's OWN family file -- never a [KineticForces] block, which would flip needs_kinetic_profiles and change the auto-grid knot density, an unrelated results channel. A [SLAYER] section carrying only profile_file is inert: slayer_ctrl.enabled defaults false and the stage returns before running or writing anything. Deck sweep, equilibrium stage, all 17: MOVED DIIID-like_SLAYER 0.9995 -> 0.993281 MOVED DIIID-like_ideal 0.995 -> 0.993281 no-op DIIID gal_resistive, _pe 0.993 (already at the floor) no-op Solovev x5, LAR x4, a10 n/a DIIID-like_ideal_IMAS needs a runtime dd; flag left off anyway The analytic decks no-op exactly as predicted: limited plasmas with finite edge q, so the edge q-law fit is rejected and the two well-separated surfaces never overlap. LAR has no legitimate kinetic file, so those four no-op on the missing profiles instead. NOTE FOR REVIEW: on all four DIII-D decks the psiedge floor OVERRIDES the layer criterion. Each asks for ~0.9892 and each is held at the q=6 window top. The feature as configured therefore reduces to "clamp psihigh to the first rational surface outboard of psiedge"; the layer physics sets the domain nowhere in the shipped examples. IMAS deck keeps the flag off with a comment: its rerun path re-runs read_imas and must resolve the same psihigh on both passes (_validate_psi_nodes errors otherwise), which is what resolving a new domain changes; "imas" is also outside EFIT_KINDS so there is no separatrix clamp and no raw psi map. benchmarks/equil_psihigh_scan.jl now records the structured equilibrium_edge_quality verdict per point instead of a hand-rolled round-trip, captures et/ep/ev, and adds a gate-validation pass that fails loudly if any psihigh exists where the gate passes while the energies are already unusable. It forces the flag off so the ladder tests the psihigh it sets. docs: corrects a naming bug I introduced -- workflow.md called the variable dW_edge_scan_start, but that rename is an unmerged draft (PR #382) and the code says psiedge. Documents the floor, the cross-section dependency on [ForceFreeStates] psiedge, and that truncations are reported in mm as well as psi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSrf6JCViFfVzqzkQ66o6b
1 parent 288fe03 commit 3cea719

20 files changed

Lines changed: 223 additions & 59 deletions

File tree

benchmarks/equil_psihigh_scan.jl

Lines changed: 112 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ methods near the separatrix.
66
77
Key outputs:
88
- Last successful psihigh for each method
9-
- Max roundtrip error in outer 10% (ψ > 0.90) vs. psihigh
9+
- The `equilibrium_edge_quality` verdict (jacobian / roundtrip / ringing) vs. psihigh
1010
- q monotonicity violations in outer 10%
11-
- et[1] (free-boundary MHD stability eigenvalue) vs. psihigh
11+
- et[1], ep[1], ev[1] (free-boundary MHD energies) vs. psihigh
12+
- GATE VALIDATION: does the edge-quality verdict flip at or before the energies go bad?
13+
14+
This doubles as the validation of the psihigh back-off gate used by
15+
`[Equilibrium] psihigh_from_layer_overlap`. That flag is forced OFF here so the
16+
ladder actually tests each requested psihigh instead of the feature resolving its own.
1217
1318
Physics note: In a diverted tokamak (DIIID), q → ∞ as ψ → 1. The EFIT q-profile
1419
extrapolates to a finite qa, which is physically wrong near the separatrix. This
@@ -29,7 +34,7 @@ using GeneralizedPerturbedEquilibrium.Equilibrium
2934
using TOML, Printf, Statistics, HDF5
3035

3136
example_path = length(ARGS) > 0 ? ARGS[1] : joinpath(@__DIR__, "../examples/DIIID-like_ideal_example")
32-
config_path = joinpath(example_path, "gpec.toml")
37+
config_path = joinpath(example_path, "gpec.toml")
3338

3439
psihigh_values = [0.980, 0.985, 0.990, 0.993, 0.995, 0.996, 0.997, 0.998, 0.999, 0.9995, 0.9999, 1.0]
3540
methods = ["efit", "efit_arclength", "efit_by_inversion"]
@@ -38,20 +43,22 @@ function make_config(path::String, eq_type::String, psihigh::Float64)
3843
raw = TOML.parsefile(path)
3944
raw["Equilibrium"]["eq_type"] = eq_type
4045
raw["Equilibrium"]["psihigh"] = psihigh
46+
raw["Equilibrium"]["psihigh_from_layer_overlap"] = false # the ladder sets psihigh; don't let the feature move it
4147
base = dirname(path)
4248
return Equilibrium.EquilibriumConfig(raw["Equilibrium"], base)
4349
end
4450

4551
"""
4652
Run the full ForceFreeStates pipeline for a given eq_type and psihigh, returning
47-
real(et[1]). Uses a temporary directory so the example directory is not modified.
48-
Returns NaN on failure.
53+
(et[1], ep[1], ev[1]) as reals. Uses a temporary directory so the example directory
54+
is not modified. Returns all-NaN on failure.
4955
"""
50-
function run_ffs_et1(config_path::String, eq_type::String, psihigh::Float64)::Float64
56+
function run_ffs_energies(config_path::String, eq_type::String, psihigh::Float64)
5157
example_dir = dirname(config_path)
5258
raw = TOML.parsefile(config_path)
5359
raw["Equilibrium"]["eq_type"] = eq_type
5460
raw["Equilibrium"]["psihigh"] = psihigh
61+
raw["Equilibrium"]["psihigh_from_layer_overlap"] = false # the ladder sets psihigh; don't let the feature move it
5562
raw["ForceFreeStates"]["force_termination"] = true # write gpec.h5 after FFS, skip PE
5663
raw["ForceFreeStates"]["vac_flag"] = true
5764
raw["ForceFreeStates"]["verbose"] = false
@@ -69,22 +76,24 @@ function run_ffs_et1(config_path::String, eq_type::String, psihigh::Float64)::Fl
6976
try
7077
GeneralizedPerturbedEquilibrium.main([tmpdir])
7178
h5open(joinpath(tmpdir, "gpec.h5"), "r") do h5
72-
et = read(h5["ForceFreeStates/FreeBoundaryStability/eigenmode_energies"])
73-
return real(et[1])
79+
g = h5["ForceFreeStates/FreeBoundaryStability"]
80+
return (real(read(g["eigenmode_energies"])[1]),
81+
real(read(g["eigenmode_plasma_energies"])[1]),
82+
real(read(g["eigenmode_vacuum_energies"])[1]))
7483
end
7584
catch
76-
return NaN
85+
return (NaN, NaN, NaN)
7786
end
7887
end
7988
end
8089

8190
# JIT warmup: run equilibrium + FFS once before timing
8291
println("JIT warmup (equilibrium + FFS)...")
83-
run_ffs_et1(config_path, "efit", 0.990)
92+
run_ffs_energies(config_path, "efit", 0.990)
8493

85-
println("=" ^ 70)
94+
println("="^70)
8695
println("psihigh robustness scan (equilibrium + stability)")
87-
println("=" ^ 70)
96+
println("="^70)
8897

8998
rows = []
9099

@@ -109,31 +118,24 @@ for method in methods
109118
q_at_psihigh = NaN
110119
q_edge_slope = NaN
111120
et1 = NaN
121+
ep1 = NaN
122+
ev1 = NaN
123+
gate_ok = false
124+
gate_failed = "formation-threw"
125+
jac_ok = false
126+
ringing = NaN
112127

113128
if success
114129
raw_profile = Equilibrium.read_efit(cfg)
115130
psi_xs = pe.rzphi_xs
116-
mtheta = length(pe.rzphi_ys) - 1
117-
psio = pe.psio
118-
119-
# Roundtrip error in outer 10%
120-
errors = Float64[]
121-
for ipsi in 1:length(psi_xs)
122-
psi_xs[ipsi] < 0.90 && continue
123-
ψ = psi_xs[ipsi]
124-
for itheta in 1:8:mtheta+1
125-
θ = pe.rzphi_ys[itheta]
126-
r2 = pe.rzphi_rsquared((ψ, θ))
127-
off = pe.rzphi_offset((ψ, θ))
128-
rfac = sqrt(max(r2, 0.0))
129-
η = 2π *+ off)
130-
R = pe.ro + rfac * cos(η)
131-
Z = pe.zo + rfac * sin(η)
132-
ψ_recon = 1.0 - raw_profile.psi_in((R, Z)) / psio
133-
push!(errors, abs(ψ_recon - ψ))
134-
end
135-
end
136-
roundtrip_max_edge = isempty(errors) ? NaN : maximum(errors)
131+
132+
# One implementation of the edge verdict, shared with the psihigh back-off gate.
133+
eq = Equilibrium.equilibrium_edge_quality(pe; psi_map=raw_profile.psi_in, psio=pe.psio)
134+
gate_ok = eq.ok
135+
gate_failed = isempty(eq.failed) ? "-" : join(eq.failed, "+")
136+
jac_ok = eq.jacobian_ok
137+
ringing = eq.ringing
138+
roundtrip_max_edge = eq.roundtrip_err
137139

138140
# q monotonicity in outer 10%
139141
q_profile = [pe.profiles.q_spline(ψ) for ψ in psi_xs]
@@ -149,25 +151,27 @@ for method in methods
149151
(psi_xs[end] - psi_xs[end-1])
150152
end
151153

152-
# et[1]: full FFS run
153-
et1 = run_ffs_et1(config_path, method, psihigh)
154+
# Downstream sanity: full FFS run
155+
et1, ep1, ev1 = run_ffs_energies(config_path, method, psihigh)
154156
end
155157

156158
status = success ? "OK" : "FAIL"
157159
et1_str = isnan(et1) ? " FAIL" : @sprintf("%+.4f", et1)
158-
@printf(" psihigh=%.4f %-4s t=%.2fs rt_edge=%.2e q_mono_viol=%d q(end)=%.2f et[1]=%s\n",
159-
psihigh, status, runtime, roundtrip_max_edge,
160-
Int(isnan(q_mono_violations_edge) ? -1 : q_mono_violations_edge),
161-
isnan(q_at_psihigh) ? -1.0 : q_at_psihigh,
162-
et1_str)
163-
164-
push!(rows, (
165-
method=method, psihigh=psihigh, success=success, runtime=runtime,
166-
roundtrip_max_edge=roundtrip_max_edge,
167-
q_mono_violations_edge=q_mono_violations_edge,
168-
q_at_psihigh=q_at_psihigh, q_edge_slope=q_edge_slope,
169-
et1=et1, error_msg=err_msg
170-
))
160+
@printf(" psihigh=%.4f %-4s gate=%-4s(%-16s) rt=%.2e ring=%-8.3g q_viol=%d et[1]=%s\n",
161+
psihigh, status, gate_ok ? "PASS" : "FAIL", gate_failed, roundtrip_max_edge, ringing,
162+
Int(isnan(q_mono_violations_edge) ? -1 : q_mono_violations_edge), et1_str)
163+
164+
push!(
165+
rows,
166+
(
167+
method=method, psihigh=psihigh, success=success, runtime=runtime,
168+
gate_ok=gate_ok, gate_failed=gate_failed, jacobian_ok=jac_ok, ringing=ringing,
169+
roundtrip_max_edge=roundtrip_max_edge,
170+
q_mono_violations_edge=q_mono_violations_edge,
171+
q_at_psihigh=q_at_psihigh, q_edge_slope=q_edge_slope,
172+
et1=et1, ep1=ep1, ev1=ev1, error_msg=err_msg
173+
)
174+
)
171175
end
172176
end
173177

@@ -181,7 +185,7 @@ end
181185
println("\nResults saved to: $output_csv")
182186

183187
# Summary
184-
println("\n" * "=" ^ 70)
188+
println("\n" * "="^70)
185189
println("Last successful psihigh per method:")
186190
for method in methods
187191
method_rows = filter(r -> r.method == method && r.success, rows)
@@ -193,14 +197,69 @@ for method in methods
193197
end
194198
end
195199

196-
println("\n" * "=" ^ 70)
200+
# ---------------------------------------------------------------------------
201+
# GATE VALIDATION -- does the edge-quality verdict track downstream sanity?
202+
#
203+
# The gate is validated if, walking psihigh outward, it never says PASS while the
204+
# ForceFreeStates energies are already garbage. A PASS-with-garbage band means the
205+
# metric is blind to the failure it exists to catch, and the psihigh back-off gate
206+
# must not be trusted (nor the feature defaulted on) until that is fixed.
207+
# ---------------------------------------------------------------------------
208+
println("\n" * "="^70)
209+
println("GATE VALIDATION: edge-quality verdict vs. ForceFreeStates sanity")
210+
println("="^70)
211+
et_finite(r) = isfinite(r.et1) && isfinite(r.ep1) && isfinite(r.ev1)
212+
verdict_bad = NamedTuple[]
213+
for method in methods
214+
mrows = sort(filter(r -> r.method == method, rows); by=r -> r.psihigh)
215+
# Reference energy from the innermost psihigh that produced finite energies.
216+
ref = findfirst(r -> r.success && et_finite(r), mrows)
217+
et_ref = ref === nothing ? NaN : mrows[ref].et1
218+
println("\n--- $method (reference et[1] = $(isnan(et_ref) ? "n/a" : @sprintf("%+.4f", et_ref))) ---")
219+
@printf(" %-9s %-7s %-6s %-18s %-11s %-11s %s\n",
220+
"psihigh", "formed", "gate", "gate failed on", "et[1]", "|et/et_ref|", "assessment")
221+
for r in mrows
222+
sane = r.success && et_finite(r) &&
223+
(isnan(et_ref) || abs(r.et1) < 100 * max(abs(et_ref), eps()))
224+
ratio = (isnan(et_ref) || !isfinite(r.et1)) ? NaN : abs(r.et1 / et_ref)
225+
assess =
226+
!r.success ? "formation threw" :
227+
(sane && r.gate_ok) ? "consistent (both good)" :
228+
(!sane && !r.gate_ok) ? "consistent (both bad)" :
229+
(!sane && r.gate_ok) ? "*** GATE BLIND: passes but energies bad ***" :
230+
"conservative (gate fails, energies still ok)"
231+
(!sane && r.gate_ok && r.success) && push!(verdict_bad, r)
232+
@printf(" %-9.4f %-7s %-6s %-18s %-11s %-11s %s\n", r.psihigh,
233+
r.success ? "yes" : "THREW", r.gate_ok ? "PASS" : "FAIL", r.gate_failed,
234+
isfinite(r.et1) ? @sprintf("%+.4f", r.et1) : "nonfinite",
235+
isnan(ratio) ? "-" : @sprintf("%.3g", ratio), assess)
236+
end
237+
end
238+
239+
println("\n" * "="^70)
240+
if isempty(verdict_bad)
241+
println("GATE VALIDATED: no psihigh where the gate passes but the energies are bad.")
242+
else
243+
println("GATE NOT VALIDATED -- $(length(verdict_bad)) point(s) where the gate PASSES")
244+
println("but the ForceFreeStates energies are already unusable:")
245+
for r in verdict_bad
246+
@printf(" %-20s psihigh=%.4f et[1]=%s\n", r.method, r.psihigh,
247+
isfinite(r.et1) ? @sprintf("%+.4f", r.et1) : "nonfinite")
248+
end
249+
println("\nFix the edge-quality metric before relying on the back-off gate.")
250+
end
251+
println("="^70)
252+
253+
println("\n" * "="^70)
197254
println("et[1] vs psihigh (free-boundary stability eigenvalue):")
198255
println(" (positive = stable, negative = unstable)")
199256
@printf(" %-22s %s\n", "psihigh", join([@sprintf("%-22s", m) for m in methods]))
200257
for psihigh in psihigh_values
201-
vals = [let r = findfirst(x -> x.method == m && x.psihigh == psihigh, rows)
202-
r === nothing || isnan(rows[r].et1) ? " -" : @sprintf("%+.4f", rows[r].et1)
203-
end for m in methods]
258+
vals = [
259+
let r = findfirst(x -> x.method == m && x.psihigh == psihigh, rows)
260+
r === nothing || isnan(rows[r].et1) ? " -" : @sprintf("%+.4f", rows[r].et1)
261+
end for m in methods
262+
]
204263
@printf(" %-22.4f %s\n", psihigh, join([@sprintf("%-22s", v) for v in vals]))
205264
end
206265
println()

docs/src/workflow.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ to different stages and are computed at different times:
5252
|:--- |:--- |:--- |:--- |
5353
| `psihigh` | `[Equilibrium]` | The **equilibrium spline domain** — where the ψ grid ends and every spline stops being defined | The deck, then optionally the separatrix clamp and the layer-overlap scan below |
5454
| `psilim` | internal (`ForceFreeStatesInternal`) | The **stability integration domain** — where the Euler-Lagrange / Δ′ integration stops. Always ≤ `psihigh` | `sing_lim!`, from `qhigh`, `dmlim`, and the equilibrium's `qmax` |
55-
| `dW_edge_scan_start` | `[ForceFreeStates]` | Start of a **diagnostic** dW(ψ) scan. Moves nothing | The deck |
55+
| `psiedge` | `[ForceFreeStates]` | Start of the **diagnostic** dW(ψ) scan over `[psiedge, psilim]`. Moves nothing itself, but floors the layer-overlap truncation below | The deck |
5656

5757
`psihigh` is chosen before the equilibrium is built; `psilim` is derived from the finished
5858
equilibrium. Shrinking `psihigh` therefore also pulls `psilim` in, but not the reverse.
@@ -71,6 +71,11 @@ by default:
7171
`truncate_at_dW_peak` is **not** in this list. It moves `psilim`, not `psihigh`, is documented
7272
as making Δ′ and δW unreliable, and is retained only for experimental work.
7373

74+
!!! note "Pending rename"
75+
PR #382 proposes renaming `psiedge` to `dW_edge_scan_start`, precisely because it reads like
76+
the plasma edge when it is only a diagnostic scan start. That PR is still a draft, so the
77+
name in the code and in every deck today is `psiedge`.
78+
7479
### Choosing `psihigh` from resistive-layer physics
7580

7681
With `psihigh_from_layer_overlap = true`, the deck's `psihigh` becomes a *first-pass* bound —
@@ -84,7 +89,13 @@ field-line-tracing noise — and the final domain is computed:
8489
Widths are converted from meters to normalized flux before being compared against surface
8590
separations.
8691
3. Where two neighbouring layers overlap, neither surface has a well-separated inner region,
87-
so the domain is cut at the inner edge of the inner one.
92+
so the domain is cut at the inner edge of the inner one — **floored** at
93+
`min(psihigh, top of the rational-q window containing psiedge)`, i.e. the first rational
94+
surface outboard of `psiedge`. The dW edge scan runs over `[psiedge, psilim]`, so the domain
95+
has to stay far enough out for its maximum to be findable. When the floor binds, the run
96+
warns that the retained surfaces are set by `psiedge` rather than by the layer physics.
97+
Setting `psiedge >= psihigh` (the documented "scan disabled" sentinel) removes the floor.
98+
The scan also refuses to truncate at all if fewer than two rational surfaces would survive.
8899
4. If that asks for a larger domain than the first pass, form it and check it with
89100
[`equilibrium_edge_quality`](@ref Equilibrium.equilibrium_edge_quality); on failure, back off toward the first-pass bound and
90101
warn, naming the check that failed.
@@ -93,7 +104,12 @@ field-line-tracing noise — and the final domain is computed:
93104

94105
The scan needs kinetic profiles, taken from `[SLAYER] profile_file` when present and
95106
`[KineticForces] kinetic_file` otherwise; with neither configured it is skipped with a message.
96-
Single-`n` runs only. The domain is chosen once, from the first-pass equilibrium, and the final
107+
It also reads `psiedge` from `[ForceFreeStates]` for the floor — so although the switch lives in
108+
`[Equilibrium]`, the behaviour depends on two settings in other sections. Single-`n` runs only.
109+
110+
Truncations are reported in millimetres of minor radius as well as in ψ. A cut that reads as
111+
large in ψ is usually small in physical terms, because ψ is heavily compressed near the edge:
112+
on the DIII-D-like deck, 0.9995 → 0.98919 removes 3.6 mm, 0.66% of the minor radius. The domain is chosen once, from the first-pass equilibrium, and the final
97113
equilibrium is re-scanned as a check — it is deliberately not iterated to a fixed point, since
98114
the auto grid it sits on top of does not itself reach one.
99115

examples/DIIID-like_SLAYER_example/gpec.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_
1111
grid_type = "log_asymptotic" # Radial grid packing type
1212
psilow = 1e-4 # Lower limit of normalized flux coordinate
1313
psihigh = 0.9995 # Upper limit of normalized flux coordinate (bounds the equilibrium splines; the integration bound psilim is derived from qhigh/dmlim)
14-
psihigh_from_layer_overlap = false # Choose the final psihigh from resistive-layer overlap instead of taking it from this file; when true the psihigh above is the first-pass (spline-safe) bound
14+
psihigh_from_layer_overlap = true # Resolve the final psihigh from resistive-layer overlap; the psihigh above is then the first-pass (spline-safe) bound, floored by the rational-q window psiedge sits in
1515
mpsi = 0 # Number of radial grid points (0 = auto-compute from psi_accuracy)
1616
psi_accuracy = 0.001 # Target absolute error in q for auto-mpsi
1717
mtheta = 256 # Number of poloidal grid points

examples/DIIID-like_gal_resistive_example/gpec.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_
1111
grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals)
1212
psilow = 1e-4 # Lower limit of normalized flux coordinate
1313
psihigh = 0.993 # Upper limit of normalized flux coordinate (0.993 stays clear of the separatrix; truncating at ≳0.998 is numerically unreasonable here)
14+
psihigh_from_layer_overlap = true # Resolve the final psihigh from resistive-layer overlap; the psihigh above is then the first-pass (spline-safe) bound, floored by the rational-q window psiedge sits in
1415
mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy)
1516
psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid
1617
mtheta = 256 # Number of poloidal grid points
@@ -83,3 +84,9 @@ gal_eta = [8e-8, 8e-8, 8e-8, 8e-8] # Per-surface resisti
8384
gal_rho = [3.3e-7, 3.3e-7, 3.3e-7, 3.3e-7] # Per-surface mass density ρ [kg/m³] (core→edge)
8485
gal_rotation = [1.0, 1.0, 1.0, 1.0] # Per-surface rotation frequency f [Hz] (core→edge); forced eigenvalue γ_s = 2πi·n·f
8586
gal_gamma = 1.6666666666666667 # Ratio of specific heats Γ for the resistive-layer coefficients (5/3)
87+
88+
[SLAYER]
89+
# Kinetic profiles for the resistive-layer overlap scan that resolves psihigh
90+
# ([Equilibrium] psihigh_from_layer_overlap). The tearing analysis itself stays off
91+
# (enabled defaults false), so only profile_file is read here.
92+
profile_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic profile file (HDF5 GPEC kinetic schema or ASCII table)

examples/DIIID-like_gal_resistive_pe_example/gpec.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_
1010
grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals)
1111
psilow = 1e-4 # Lower limit of normalized flux coordinate
1212
psihigh = 0.993 # Upper limit of normalized flux coordinate (0.993 stays clear of the separatrix; truncating at ≳0.998 is numerically unreasonable here)
13+
psihigh_from_layer_overlap = true # Resolve the final psihigh from resistive-layer overlap; the psihigh above is then the first-pass (spline-safe) bound, floored by the rational-q window psiedge sits in
1314
mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy)
1415
psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid
1516
mtheta = 256 # Number of poloidal grid points
@@ -101,3 +102,9 @@ compute_singular_coupling = true # Compute singular layer coupling metric
101102
verbose = true # Enable verbose logging
102103
write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5
103104
reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled)
105+
106+
[SLAYER]
107+
# Kinetic profiles for the resistive-layer overlap scan that resolves psihigh
108+
# ([Equilibrium] psihigh_from_layer_overlap). The tearing analysis itself stays off
109+
# (enabled defaults false), so only profile_file is read here.
110+
profile_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic profile file (HDF5 GPEC kinetic schema or ASCII table)

0 commit comments

Comments
 (0)