Skip to content

Commit d2abb6f

Browse files
logan-ncclaude
andcommitted
FFS - EXPERIMENT - Resonant-layer knot ladders for the kinetic evaluation grid
The kinetic matrices are near-singular at the located Omega_l = 0 surfaces (measured: the DIII-D pedestal omega_E crossing carries 86% of the NTV torque in a ~2e-3-wide core that the EL grid samples with ~2 knots, so the EL solution collects 0.046 of fgar's 0.622 N*m there). Give each located node a local knot ladder (offsets 2.5e-4..8e-3, ~21 knots/node) in the kinetic matrix splines only -- equilibrium and Delta-prime grids untouched -- with a 1e-4 snap guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
1 parent 831e708 commit d2abb6f

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

src/ForceFreeStates/Kinetic.jl

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ function make_kinetic_matrix(
9999
intr::ForceFreeStatesInternal,
100100
metric::MetricData;
101101
calculated_source::Union{Nothing,Function}=nothing,
102-
axis_validity_psi_c::Float64=0.0
102+
axis_validity_psi_c::Float64=0.0,
103+
resonance_psis::Vector{Float64}=Float64[]
103104
)
104105
xs = metric.xs
105106
mpsi = length(xs)
@@ -108,6 +109,24 @@ function make_kinetic_matrix(
108109
# suppression boundary; coarse equilibrium grids cannot represent env·(increment), and the
109110
# spline overshoot can land on a rational surface. Pin the band ends (the smoothstep is
110111
# only C² there) and resolve the transition with a fixed set of knots.
112+
# Resonant-layer ladder: the kinetic matrices are near-singular at the located Ω_ℓ = 0
113+
# surfaces (measured core width ~2e-3 at the DIII-D pedestal ω_E crossing), so the kinetic
114+
# evaluation grid gets a local knot ladder at each node — kinetic splines only; the
115+
# equilibrium/Δ′ grids are untouched.
116+
layer_offsets = [0.00025, 0.0005, 0.00075, 0.001, 0.0015, 0.002, 0.003, 0.0045, 0.006, 0.008]
117+
function layer_knots(lo, hi)
118+
out = Float64[]
119+
for r in resonance_psis
120+
r <= max(lo, 2 * axis_validity_psi_c) && continue
121+
r >= hi && continue
122+
push!(out, r)
123+
for w in layer_offsets, sgn in (-1, 1)
124+
x = r + sgn * w
125+
lo < x < hi && push!(out, x)
126+
end
127+
end
128+
return out
129+
end
111130
band_knots(lo, hi) = axis_validity_psi_c > 0 ?
112131
[x for x in range(axis_validity_psi_c, 2 * axis_validity_psi_c; length=9) if lo < x < hi] : Float64[]
113132

@@ -121,12 +140,19 @@ function make_kinetic_matrix(
121140
"calling make_kinetic_matrix directly, or pass " *
122141
"`calculated_source=KineticForces.compute_calculated_kinetic_matrices` explicitly."
123142
)
124-
band = band_knots(xs[1], xs[end])
125-
if isempty(band)
143+
extra = vcat(band_knots(xs[1], xs[end]), layer_knots(xs[1], xs[end]))
144+
if isempty(extra)
126145
kw_flat, kt_flat = calculated_source(ctrl, equil, intr, metric, ffit)
127146
else
128-
xs = sort!(unique!(vcat(collect(xs), band)))
147+
xs = collect(xs)
148+
for x in sort!(extra) # snap-guard: skip knots that would ring against existing ones
149+
i = searchsortedfirst(xs, x)
150+
((i > 1 && x - xs[i-1] < 1e-4) || (i <= length(xs) && xs[i] - x < 1e-4)) && continue
151+
insert!(xs, i, x)
152+
end
129153
mpsi = length(xs)
154+
isempty(resonance_psis) ||
155+
@info "Kinetic evaluation grid: resonant-layer ladders at $(round.(filter(r -> r > 2 * axis_validity_psi_c, resonance_psis); digits=3)) -> $mpsi knots"
130156
kw_flat, kt_flat = calculated_source(ctrl, equil, intr, metric, ffit; psis=xs)
131157
end
132158
if ctrl.kinetic_grid_bisect > 0

src/GeneralizedPerturbedEquilibrium.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,20 @@ function main_from_inputs(
448448
KineticForces.compute_calculated_kinetic_matrices(
449449
c, e, i, m, f;
450450
kf_ctrl=kf_ctrl, kinetic_profiles=kinetic_profiles, psis=psis)
451+
# Located resonance surfaces for the kinetic evaluation grid's layer ladders.
452+
stability_resonance_psis = Float64[]
453+
if ctrl.kinetic_source == "calculated" && kinetic_profiles !== nothing
454+
for n_res in intr.nlow:intr.nhigh
455+
n_res == 0 && continue
456+
append!(
457+
stability_resonance_psis,
458+
KineticForces.kinetic_resonance_psi_nodes(
459+
kinetic_profiles, equil;
460+
n=n_res, nl=kf_ctrl.nl, zi=kf_ctrl.zi, mi=kf_ctrl.mi,
461+
electron=kf_ctrl.electron, wdfac=kf_ctrl.wdfac)
462+
)
463+
end
464+
end
451465
# Near-axis validity boundary for the calculated kinetic matrices: the envelope band
452466
# must be resolved by the kernel grid, so make_kinetic_matrix needs its location.
453467
axis_psi_c = 0.0
@@ -457,7 +471,8 @@ function main_from_inputs(
457471
zi=kf_ctrl.zi, mi=kf_ctrl.mi, electron=kf_ctrl.electron)
458472
end
459473
make_kinetic_matrix(ctrl, equil, ffit, intr, metric;
460-
calculated_source=calculated_cb, axis_validity_psi_c=axis_psi_c)
474+
calculated_source=calculated_cb, axis_validity_psi_c=axis_psi_c,
475+
resonance_psis=stability_resonance_psis)
461476

462477
# Find kinetically-displaced singular surfaces (zeros of det(F̄)) for ODE crossings.
463478
# Matches Fortran ksing_find (sing.f:1486-1616). singfac_min > 0 gates crossings;

0 commit comments

Comments
 (0)