Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .claude/agent-memory/fortran-physics-reviewer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
- [KineticForces (NTV) Audit Checklist](kinetic_ntv_map.md) — pentrc->KineticForces map, Logan 2015 matrices, what to verify
- [InnerLayer (Resistive) Audit Checklist](resistive_layer_map.md) — rmatch->InnerLayer map, GGJ Wasow basis / Δ′, what to verify
- [Galerkin Δ′ Assembly Map](galerkin_assembly_map.md) — gal.f<->GalerkinAssembly.jl; resonant sign chain verified; PASS
- [Torque Scalar vs Profile](torque_scalar_vs_profile.md) — scalar toroidal_torque (ported, faithful) vs gpout_dw/dw_matrix psi-profile (unported); why PE vs fgar mismatch isn't necessarily a bug
8 changes: 7 additions & 1 deletion src/Equilibrium/GridRefinement.jl
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ Build the refined pass-2 ψ grid from a formed pass-1 equilibrium: measured-curv
density (`_knot_density`), equidistribution, a global minimum-spacing floor
(`enforce_min_spacing`), and rational-surface bracketing (`bracket_mandatory_nodes`). `tau` is
the target interpolation accuracy (`psi_accuracy`); `kin` optionally supplies kinetic profiles
whose pedestal gradients attract knots; `mandatory` lists rational-surface ψ values to bracket;
whose pedestal gradients attract knots; `mandatory` lists rational-surface ψ values to bracket; `pinned` lists ψ values inserted as plain knots without a cleared zone (kinetic-resonance surfaces);
`singfac_min` and `n_min` (smallest |n| in the run) set each surface's matching half-stencil
`dpsi = singfac_min/(n_min·|q′|)`, and the bracket half-width is `bracket_coef·dpsi` (floored at
`min_spacing`). Rational surfaces are bracketed, not pinned: a knot on the surface would make the
Expand All @@ -428,6 +428,7 @@ function refined_psi_grid(equil::PlasmaEquilibrium;
tau::Float64,
kin::Union{Nothing,KineticProfileSplines}=nothing,
mandatory::Vector{Float64}=Float64[],
pinned::Vector{Float64}=Float64[],
singfac_min::Float64=1e-4,
n_min::Int=1,
bracket_coef::Float64=BRACKET_COEF,
Expand All @@ -449,6 +450,11 @@ function refined_psi_grid(equil::PlasmaEquilibrium;
N == N_cap && M_total > N_cap &&
@warn "refined_psi_grid: knot count capped at $N_cap (density integral wants $(ceil(Int, M_total))); psi_accuracy=$tau may not be attainable"
grid = enforce_min_spacing(_equidistribute(xs, rho, N), min_spacing)
# Pinned knots (e.g. kinetic-resonance surfaces): knot-at-node semantics via
# merge_mandatory_nodes — no cleared zone. Inserted before rational bracketing, so a
# pinned node inside a rational's bracket zone is cleared by it (the Δ′ clean-interval
# requirement wins locally; the rational's own dense floor resolves that neighbourhood).
grid = isempty(pinned) ? grid : merge_mandatory_nodes(grid, pinned)
isempty(mandatory) && return grid
min_half_widths = [max(bracket_coef * singfac_min / (n_min * abs(equil.profiles.q_deriv(m))), min_spacing) for m in mandatory]
return bracket_mandatory_nodes(grid, mandatory, min_half_widths, min_spacing)
Expand Down
26 changes: 25 additions & 1 deletion src/GeneralizedPerturbedEquilibrium.jl
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,32 @@ function main_from_inputs(
# Smallest |n| in the run sets the widest matching half-stencil dpsi = singfac_min/(n_min·|q′|),
# so the rational-surface brackets clear a zone large enough for every mode.
n_min = minimum(abs(n) for n in intr.nlow:intr.nhigh if n != 0)
# Pin the located kinetic-resonance surfaces (Ω_ℓ = 0) as plain knots when the run
# builds calculated kinetic matrices — the ideal-driven grid criterion knows nothing
# about kinetic resonance locations. Nodes inside the near-axis validity region are
# suppressed anyway and not pinned.
pinned = Float64[]
if ctrl.kinetic_factor > 0 && ctrl.kinetic_source == "calculated" && kinetic_profiles !== nothing
for n_res in intr.nlow:intr.nhigh
n_res == 0 && continue
append!(
pinned,
KineticForces.kinetic_resonance_psi_nodes(
kinetic_profiles, equil;
n=n_res, nl=kf_ctrl.nl, zi=kf_ctrl.zi, mi=kf_ctrl.mi,
electron=kf_ctrl.electron, wdfac=kf_ctrl.wdfac)
)
end
if kf_ctrl.axis_validity_suppression
psi_c_grid = KineticForces.kinetic_axis_validity_psi(kinetic_profiles, equil;
zi=kf_ctrl.zi, mi=kf_ctrl.mi, electron=kf_ctrl.electron)
filter!(p -> p > psi_c_grid, pinned)
end
isempty(pinned) ||
@info "Pinning $(length(pinned)) kinetic-resonance surfaces into the ψ grid: $(round.(sort(pinned); digits=3))"
end
psi_nodes = Equilibrium.refined_psi_grid(equil;
tau=eq_config.psi_accuracy, kin=kinetic_profiles, mandatory=mandatory,
tau=eq_config.psi_accuracy, kin=kinetic_profiles, mandatory=mandatory, pinned=pinned,
singfac_min=ctrl.singfac_min, n_min=n_min)
rerun_input = if additional_input !== nothing
# Analytic *Config, IMAS dd, or prebuilt RunInput — all re-formable. The IMAS
Expand Down
Loading