Skip to content

Commit a932227

Browse files
jhalpern30claude
andcommitted
EQUIL - CLEANUP - Tighten the psihigh-clamp seam after the EquilibriumConfig freeze
Review follow-ups on the previous commit; no behavior change. - Drop the unused 1-arg clamp_psihigh_to_separatrix(::DirectRunInput). It had no callers left and took psihigh from raw_profile.config, which is exactly what the config no longer owns. - Replace it with a 2-arg forwarder taking anything that carries the EFIT flux map, so the NamedTuple-or-DirectRunInput duck typing lives next to the clamp instead of being spelled out at the call site. - Map the read_efit_arrays NamedTuple onto DirectRunInput by field name in one place rather than splatting it positionally at two call sites, where adding a struct field would break silently. - Say where the authoritative psihigh comes from at the three places a reader cannot infer it: equil.config in the two-pass block, why the clamp merges instead of mutating the caller's dict (it is re-serialized into gpec.h5), and that the rerun config is a placeholder. - Trim the setup_equilibrium docstring to what a caller needs, replace a comment that described the previous implementation, and unwind a ||-return into an if. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5fad7a0 commit a932227

5 files changed

Lines changed: 30 additions & 17 deletions

File tree

src/Equilibrium/DirectEquilibriumByInversion.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ end
7272

7373
"""
7474
clamp_psihigh_to_separatrix(psi_in, psi_in_xs, psi_in_ys, psio, psihigh) -> (clamped_psihigh, was_adjusted)
75-
clamp_psihigh_to_separatrix(raw_profile::DirectRunInput) -> (clamped_psihigh, was_adjusted)
75+
clamp_psihigh_to_separatrix(flux_map, psihigh) -> (clamped_psihigh, was_adjusted)
7676
7777
Binary-searches for the highest psihigh ≤ the requested one at which the ψ level set is still a
7878
closed curve in the EFIT grid. Returns the safe value and a Bool indicating whether any clamping
@@ -98,9 +98,9 @@ function clamp_psihigh_to_separatrix(psi_in, psi_in_xs, psi_in_ys, psio, psihigh
9898
return (lo, true)
9999
end
100100

101-
clamp_psihigh_to_separatrix(raw_profile::DirectRunInput) =
102-
clamp_psihigh_to_separatrix(raw_profile.psi_in, raw_profile.psi_in_xs, raw_profile.psi_in_ys,
103-
raw_profile.psio, raw_profile.config.psihigh)
101+
# `flux_map` is anything carrying the EFIT flux map: a `read_efit_arrays` NamedTuple or a `DirectRunInput`.
102+
clamp_psihigh_to_separatrix(flux_map, psihigh) =
103+
clamp_psihigh_to_separatrix(flux_map.psi_in, flux_map.psi_in_xs, flux_map.psi_in_ys, flux_map.psio, psihigh)
104104

105105
"""
106106
resample_contour_to_theta_grid!(R_out, Z_out, curve, ro, zo, theta_grid)

src/Equilibrium/Equilibrium.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,8 @@ Read an equilibrium file, run the appropriate solver, and return the processed
7777
`PlasmaEquilibrium` with global parameters, q-profile, and GSE diagnostics.
7878
7979
The efit-family kinds must use the `equil_dict` method: their `psihigh` is clamped to the
80-
outermost closed flux surface, and that clamp is resolved from the g-file *before* the
81-
(immutable) `EquilibriumConfig` is built, so the run has exactly one config and never
82-
rewrites it. The `EquilibriumConfig` method serves the kinds with no such clamp and rejects
83-
efit kinds rather than silently skipping it.
80+
outermost closed flux surface before the immutable `EquilibriumConfig` is built. The
81+
`EquilibriumConfig` method serves every other kind and errors on efit.
8482
8583
`override_psi_nodes` bypasses the config-driven radial grid (`grid_type`/`mpsi`) and forms
8684
the equilibrium on the given strictly increasing ψ_N node vector, whose endpoints must match
@@ -95,8 +93,10 @@ end
9593
function setup_equilibrium(equil_dict::Dict{String,Any}, base_path::AbstractString="./", additional_input=nothing;
9694
override_psi_nodes::Union{Nothing,Vector{Float64}}=nothing)
9795
haskey(equil_dict, "eq_type") || error("Missing required key in [Equilibrium]: eq_type")
98-
equil_dict["eq_type"] in EFIT_KINDS || return setup_equilibrium(
99-
EquilibriumConfig(equil_dict, base_path), additional_input; override_psi_nodes)
96+
if !(equil_dict["eq_type"] in EFIT_KINDS)
97+
# Only efit kinds need the separatrix clamp; every other kind builds its config directly.
98+
return setup_equilibrium(EquilibriumConfig(equil_dict, base_path), additional_input; override_psi_nodes)
99+
end
100100

101101
eq_input = build_efit_input(equil_dict, base_path, additional_input)
102102
return run_equilibrium_solver(eq_input; override_psi_nodes)
@@ -112,22 +112,22 @@ rerun TOML is re-validated); otherwise the g-file is read here.
112112
"""
113113
function build_efit_input(equil_dict::Dict{String,Any}, base_path::AbstractString, additional_input)
114114
prebuilt = additional_input isa DirectRunInput
115-
# The requested psihigh carries the constructor's own 1.0 ceiling, so the separatrix search
116-
# starts where it did when the clamp still wrote back onto the config.
115+
# Mirrors the config constructor's psihigh ≤ 1.0 ceiling the search must start from the
116+
# same validated request the config would hold.
117117
psihigh_req = min(Float64(get(equil_dict, "psihigh", DEFAULT_PSIHIGH)), 1.0)
118-
arrays = if prebuilt
118+
flux_map = if prebuilt
119119
additional_input
120120
else
121121
eq_filename = resolve_eq_filename(equil_dict, base_path)
122122
eq_filename === nothing && error("Missing required key in [Equilibrium]: eq_filename (eq_type=$(equil_dict["eq_type"]))")
123123
read_efit_arrays(eq_filename)
124124
end
125125

126-
psihigh_safe, adjusted = clamp_psihigh_to_separatrix(
127-
arrays.psi_in, arrays.psi_in_xs, arrays.psi_in_ys, arrays.psio, psihigh_req)
126+
psihigh_safe, adjusted = clamp_psihigh_to_separatrix(flux_map, psihigh_req)
128127
if adjusted
129128
@warn "psihigh=$(psihigh_req) has no closed flux surface in EFIT grid; " *
130129
"clamped to $(round(psihigh_safe; sigdigits=7))"
130+
# Merge, not mutate: the caller's dict is re-serialized into gpec.h5 and must keep the request.
131131
equil_dict = merge(equil_dict, Dict{String,Any}("psihigh" => psihigh_safe))
132132
end
133133
eq_config = EquilibriumConfig(equil_dict, base_path)
@@ -136,7 +136,7 @@ function build_efit_input(equil_dict::Dict{String,Any}, base_path::AbstractStrin
136136
additional_input.config = eq_config
137137
return additional_input
138138
end
139-
return DirectRunInput(eq_config, arrays...)
139+
return DirectRunInput(eq_config, flux_map)
140140
end
141141

142142
function setup_equilibrium(eq_config::EquilibriumConfig, additional_input=nothing; override_psi_nodes::Union{Nothing,Vector{Float64}}=nothing)

src/Equilibrium/ReadEquilibrium.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,23 @@ function read_efit_arrays(eq_filename::AbstractString)
118118
return (; sq_in, psi_in, psi_in_xs, psi_in_ys, rmin, rmax, zmin, zmax, psio, bt_sign=fpol_sign, ingest)
119119
end
120120

121+
"""
122+
DirectRunInput(config, arrays::NamedTuple) -> DirectRunInput
123+
124+
Bundle a [`read_efit_arrays`](@ref) NamedTuple with the config it was read for. The one place
125+
the reader's field names are mapped onto the struct's, so adding a field is a single edit.
126+
"""
127+
DirectRunInput(config::EquilibriumConfig, arrays::NamedTuple) =
128+
DirectRunInput(config, arrays.sq_in, arrays.psi_in, arrays.psi_in_xs, arrays.psi_in_ys,
129+
arrays.rmin, arrays.rmax, arrays.zmin, arrays.zmax, arrays.psio, arrays.bt_sign, arrays.ingest)
130+
121131
"""
122132
read_efit(config::EquilibriumConfig) -> DirectRunInput
123133
124134
Parse the EFIT g-file named by `config` and bundle it with `config` into a `DirectRunInput`
125135
ready for the direct solver.
126136
"""
127-
read_efit(config::EquilibriumConfig) = DirectRunInput(config, read_efit_arrays(config.eq_filename)...)
137+
read_efit(config::EquilibriumConfig) = DirectRunInput(config, read_efit_arrays(config.eq_filename))
128138

129139

130140
"""

src/GeneralizedPerturbedEquilibrium.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ function main_from_inputs(
239239
# Two-pass auto grid: measure the pass-1 equilibrium's curvature (profiles, geometry,
240240
# kinetic profiles), pin knots on rational surfaces, and re-form on the refined grid
241241
# from the in-memory input — no file re-read.
242+
# equil.config is authoritative: for efit kinds its psihigh is the separatrix-clamped value,
243+
# not the request still sitting in equil_dict.
242244
if Equilibrium.wants_two_pass(equil.config)
243245
mandatory = ForceFreeStates.rational_psi_nodes(equil; nlow=intr.nlow, nhigh=intr.nhigh)
244246
psi_nodes = Equilibrium.refined_psi_grid(equil;

src/Rerun.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ function build_inputs_from_h5(args::Vector{String})
271271
# code, and `inputs` itself is re-serialized into the rerun's own input/gpec_toml_raw.
272272
equil_dict = merge(inputs["Equilibrium"], Dict{String,Any}("eq_filename" => ""))
273273
eq_type = get(equil_dict, "eq_type", nothing)
274+
# Placeholder config for the ingest rebuild; setup_equilibrium replaces it with the clamped one.
274275
eq_config = Equilibrium.EquilibriumConfig(equil_dict, output_dir)
275276

276277
# Analytic kinds regenerate from their TOML section; file-based kinds rebuild splines from

0 commit comments

Comments
 (0)