|
| 1 | + |
| 2 | +""" |
| 3 | + refine_grid_at_fbar_peaks(xs, kw, kt, evaluate, ffit, equil, intr, psi_c; |
| 4 | + ngrid=1000, relaxed_frac=0.01, target=3, max_add=24) → (xs, kw, kt) |
| 5 | +
|
| 6 | +Insert kinetic evaluation knots across near-singular structure of F̄ that the grid does not |
| 7 | +resolve. Scans cond(F̄) (the same operator `find_kinetic_singular_surfaces!` searches — Park & |
| 8 | +Logan Eq. 70, so shifted and split resonances are included), takes peaks between |
| 9 | +`relaxed_frac`·threshold and the singular threshold, measures each peak's FWHM, and adds knots |
| 10 | +only where fewer than `target` knots lie inside it. New points respect `MIN_KNOT_SPACING`, stay |
| 11 | +above the near-axis validity band, and are capped at `max_add`; each costs one kernel evaluation |
| 12 | +and the existing values are reused. A well-resolved grid inserts nothing. |
| 13 | +""" |
| 14 | +function refine_grid_at_fbar_peaks(xs::Vector{Float64}, kw::Array{ComplexF64,3}, kt::Array{ComplexF64,3}, |
| 15 | + evaluate::Function, ffit::FourFitVars, equil::Equilibrium.PlasmaEquilibrium, |
| 16 | + intr::ForceFreeStatesInternal, psi_c::Float64; |
| 17 | + ngrid::Int=1000, relaxed_frac::Float64=0.01, target::Int=3, max_add::Int=24, |
| 18 | + cond_threshold::Float64=1e8) |
| 19 | + |
| 20 | + lo, hi = xs[1], xs[end] |
| 21 | + scan = collect(range(lo, hi; length=ngrid)) |
| 22 | + hint = Ref(1) |
| 23 | + cond_vals = [ |
| 24 | + try |
| 25 | + evaluate_fbar_condition(x, ffit, equil, intr; hint=hint) |
| 26 | + catch |
| 27 | + Inf |
| 28 | + end for x in scan |
| 29 | + ] |
| 30 | + |
| 31 | + add = Float64[] |
| 32 | + for i in 2:(ngrid-1) |
| 33 | + c = cond_vals[i] |
| 34 | + (c > cond_vals[i-1] && c > cond_vals[i+1] && relaxed_frac * cond_threshold < c <= cond_threshold) || continue |
| 35 | + l = i |
| 36 | + while l > 1 && cond_vals[l] > c / 2 |
| 37 | + l -= 1 |
| 38 | + end |
| 39 | + r = i |
| 40 | + while r < ngrid && cond_vals[r] > c / 2 |
| 41 | + r += 1 |
| 42 | + end |
| 43 | + inside = count(x -> scan[l] <= x <= scan[r], xs) |
| 44 | + inside >= target && continue |
| 45 | + w = (scan[r] - scan[l]) / 3 |
| 46 | + for x in (scan[i], scan[i] - w, scan[i] + w) |
| 47 | + (lo < x < hi && x > 2 * psi_c) || continue |
| 48 | + minimum(abs.(xs .- x)) < Equilibrium.MIN_KNOT_SPACING && continue |
| 49 | + isempty(add) || minimum(abs.(add .- x)) >= Equilibrium.MIN_KNOT_SPACING || continue |
| 50 | + push!(add, x) |
| 51 | + end |
| 52 | + end |
| 53 | + isempty(add) && return xs, kw, kt |
| 54 | + length(add) > max_add && (add = sort(add)[1:max_add]) |
| 55 | + |
| 56 | + sort!(add) |
| 57 | + @info "Kinetic grid: $(length(add)) knot(s) added across unresolved near-singular F̄ structure at " * |
| 58 | + "ψ=$(round.(add; digits=4)) (cond peaks below the singular threshold)" |
| 59 | + kw_new, kt_new = evaluate(add) |
| 60 | + allxs = vcat(xs, add) |
| 61 | + perm = sortperm(allxs) |
| 62 | + kw_all = cat(kw, kw_new; dims=1)[perm, :, :] |
| 63 | + kt_all = cat(kt, kt_new; dims=1)[perm, :, :] |
| 64 | + return allxs[perm], kw_all, kt_all |
| 65 | +end |
| 66 | + |
1 | 67 | """ |
2 | 68 | make_kinetic_matrix(ctrl, equil, ffit, intr, metric; |
3 | 69 | calculated_source=nothing) |
@@ -72,6 +138,31 @@ function make_kinetic_matrix( |
72 | 138 | # Pre-compute FKG derived matrices (corresponds to Fortran method=0) |
73 | 139 | _compute_fkg_matrices!(ffit, equil, intr, metric, kw_flat, kt_flat; xs=xs) |
74 | 140 |
|
| 141 | + # The FKG splines now exist, so F̄ can be scanned: add knots only where near-singular |
| 142 | + # structure (shifted/split kinetic resonances) falls in an interval that does not resolve it. |
| 143 | + if ctrl.kinetic_source == "calculated" && calculated_source !== nothing |
| 144 | + xs2, kw_flat, kt_flat = refine_grid_at_fbar_peaks( |
| 145 | + collect(xs), kw_flat, kt_flat, |
| 146 | + psis -> begin |
| 147 | + kwn, ktn = calculated_source(ctrl, equil, intr, metric, ffit; psis=psis) |
| 148 | + (kwn .* ctrl.kinetic_factor, ktn .* ctrl.kinetic_factor) |
| 149 | + end, |
| 150 | + ffit, equil, intr, axis_validity_psi_c) |
| 151 | + if length(xs2) != length(xs) |
| 152 | + xs = xs2 |
| 153 | + # _compute_fkg_matrices! folds the kinetic increments into amats/bmats/cmats (saving the |
| 154 | + # ideal copies), so restore those before recomputing or the increments are added twice. |
| 155 | + ffit.amats = ffit.amats_ideal |
| 156 | + ffit.bmats = ffit.bmats_ideal |
| 157 | + ffit.cmats = ffit.cmats_ideal |
| 158 | + for ic in 1:6 |
| 159 | + ffit.kwmats[ic] = cubic_interp(xs, Series(@view(kw_flat[:, :, ic])); ffit.itp_opts...) |
| 160 | + ffit.ktmats[ic] = cubic_interp(xs, Series(@view(kt_flat[:, :, ic])); ffit.itp_opts...) |
| 161 | + end |
| 162 | + _compute_fkg_matrices!(ffit, equil, intr, metric, kw_flat, kt_flat; xs=xs) |
| 163 | + end |
| 164 | + end |
| 165 | + |
75 | 166 | return nothing |
76 | 167 | end |
77 | 168 |
|
|
0 commit comments