From 2ad51e538c2cad96eb4c2f726f222d316956c3c7 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Fri, 21 Aug 2026 17:52:15 -0400 Subject: [PATCH 1/2] EQUIL - NEW FEATURE - Pin located kinetic-resonance surfaces into the auto psi grid The two-pass auto grid's criterion is ideal-driven and knows nothing about kinetic resonance locations. When a run builds calculated kinetic matrices, locate the Omega_l = 0 surfaces (same locator as the NTV quadrature paneling) and insert them as plain knots via merge_mandatory_nodes -- knot-at-node, no cleared zone, inserted before rational bracketing so the Delta-prime clean-interval treatment wins locally. Nodes inside the near-axis validity region are suppressed anyway and not pinned. DIII-D: +2 net knots, et[1] unchanged to 2e-6, EL steps drop 14%. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk --- src/Equilibrium/GridRefinement.jl | 8 +++++++- src/GeneralizedPerturbedEquilibrium.jl | 26 +++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Equilibrium/GridRefinement.jl b/src/Equilibrium/GridRefinement.jl index e58eb81dc..d9441bbe8 100644 --- a/src/Equilibrium/GridRefinement.jl +++ b/src/Equilibrium/GridRefinement.jl @@ -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 @@ -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, @@ -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) diff --git a/src/GeneralizedPerturbedEquilibrium.jl b/src/GeneralizedPerturbedEquilibrium.jl index 52d255a07..b95d2f263 100755 --- a/src/GeneralizedPerturbedEquilibrium.jl +++ b/src/GeneralizedPerturbedEquilibrium.jl @@ -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 From a38c41d08168ac150c2defc7960955a730100e9e Mon Sep 17 00:00:00 2001 From: logan-nc Date: Fri, 21 Aug 2026 20:55:08 -0400 Subject: [PATCH 2/2] AGENT - MEMORY - fortran-physics-reviewer: torque scalar vs profile constructions Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk --- .claude/agent-memory/fortran-physics-reviewer/MEMORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/agent-memory/fortran-physics-reviewer/MEMORY.md b/.claude/agent-memory/fortran-physics-reviewer/MEMORY.md index 1a3bc1fc1..768855eed 100644 --- a/.claude/agent-memory/fortran-physics-reviewer/MEMORY.md +++ b/.claude/agent-memory/fortran-physics-reviewer/MEMORY.md @@ -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