From e8fdfed5d4d4d3f27957e423c759486e4691c28a Mon Sep 17 00:00:00 2001 From: logan-nc <6198372+logan-nc@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:43:31 -0400 Subject: [PATCH] EQUIL - BUG FIX - Initialize sol_run 4th profile column; size auto-mpsi buffer to actual profile count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a cross-run state corruption: calling main() repeatedly in one Julia process made the log_asymptotic auto-mpsi for the Solovev equilibrium produce wildly varying grids (mpsi 16 -> 600-900, occasionally InexactError), only after a prior KINETIC run in the same process. Fresh subprocesses (harness/CLI) were unaffected, so it was latent. Root cause: `sol_run` (AnalyticEquilibrium.jl) allocated the source-profile array `sqfs = Array{Float64}(undef, ma+1, 4)` but filled only columns 1-3, leaving column 4 uninitialized. In a fresh process that memory reads ~0 (benign); after a kinetic run it holds stale data. The auto-mpsi curvature estimate (`_estimate_mid_spacing`) reads that 4th column, so the garbage spiked the estimated profile curvature, collapsing the mid-region knot spacing and inflating mpsi. Diagnosed by bisection (ideal full run does not corrupt; kinetic does) and by instrumenting the per-profile second-derivative estimate (column 4 only). Fixes: - `sol_run`: allocate `sqfs` with `zeros` so column 4 is a deterministic 0.0, matching the benign fresh-process state the validated baselines were built on. - `_estimate_mid_spacing`: size the evaluation buffer to the actual profile count (`size(sq_in.y, 2)`) instead of a hardcoded 4, so it never reads beyond the real profiles — protecting the 3-profile (F, P, q) builders from the same class of bug. Mirrors InverseEquilibrium.jl, which already sizes this way. Not a pool re-entrancy bug: the AdaptiveArrayPools task-local pool depth returns to 1 after every run (no @with_pool leak); this was use-of-uninitialized-memory. Verified: deterministic repro (kinetic-then-auto x4) now stable at mpsi=16; full test suite green; regression harness diiid_n1, solovev_n1, solovev_multi_n, solovev_kinetic_calculated all 0.0-diff vs develop (zero numerical impact). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Equilibrium/AnalyticEquilibrium.jl | 4 ++-- src/Equilibrium/DirectEquilibrium.jl | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Equilibrium/AnalyticEquilibrium.jl b/src/Equilibrium/AnalyticEquilibrium.jl index 176968b4f..6e7abbc5c 100644 --- a/src/Equilibrium/AnalyticEquilibrium.jl +++ b/src/Equilibrium/AnalyticEquilibrium.jl @@ -809,8 +809,8 @@ function sol_run(equil_inputs::EquilibriumConfig, sol_inputs::SolovevConfig) zmax = 1.5 * e * a zmin = -zmax - # Compute 1D data and spline - sqfs = Array{Float64}(undef, ma + 1, 4) + # zeros, not undef: only columns 1-3 are filled, so column 4 must stay a deterministic 0. + sqfs = zeros(ma + 1, 4) psis = [(ia / (ma + 1))^2 for ia in 1:(ma+1)] sqfs[:, 1] .= f0 .* f0fac sqfs[:, 2] .= pfac .* (1 .* p0fac .- psis) diff --git a/src/Equilibrium/DirectEquilibrium.jl b/src/Equilibrium/DirectEquilibrium.jl index 5e7821fe0..13b161afa 100644 --- a/src/Equilibrium/DirectEquilibrium.jl +++ b/src/Equilibrium/DirectEquilibrium.jl @@ -413,14 +413,16 @@ function _estimate_mid_spacing(sq_in, psi_split_core, psi_split_edge, tau) psi_samp = range(psi_split_core, psi_split_edge; length=n_samp) h_samp = step(psi_samp) h_min = Inf - buf = zeros(4) + # Size the buffer to the actual profile count, not a hardcoded 4 (cf. InverseEquilibrium.jl). + nq = size(sq_in.y, 2) + buf = zeros(nq) all_vals = [ begin sq_in(buf, ψ) copy(buf) end for ψ in psi_samp ] - for k in 1:4 + for k in 1:nq vals = [all_vals[i][k] for i in 1:n_samp] f_scale = max(maximum(abs.(vals)), 1e-12) d2_max = 0.0