Skip to content

ForceFreeStates - BUGFIX! - Trigger the Gaussian reduction on the conditioning of U₁ and factor U₁ in the plasma response matrix - #520

Open
jhalpern30 wants to merge 5 commits into
developfrom
bugfix/ffs-reduction-conditioning-trigger
Open

jhalpern30 wants to merge 5 commits into
developfrom
bugfix/ffs-reduction-conditioning-trigger

Conversation

@jhalpern30

@jhalpern30 jhalpern30 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #490 and enables #481. Stacked on #489 (base bugfix/ffs-crossing-resonant-elimination); retarget to develop once #489 merges.

Accurate ideal energies close to rational surfaces, and trustworthy perturbed equilibria at high n.

Regime Before → after Case
Smaller singfac_min 8500× → 1e-6 Solovev et₁ at singfac_min 1e-8. Before, anything below ~1e-5 was unusable on this equilibrium; now accurate down to 1e-8. Prerequisite for #481, whose crossing defect scales with singfac_min.
High-α surfaces 4.1 → 7e-8 Solovev q₀ ≥ 1.85, singfac_min 1e-6
High-n perturbed equilibrium 1e95 → 2e-4 DIIID n = 6 and 8 overflowed the old 100-slot reduction storage and silently corrupted ξ, island widths and coupling outputs (n = 10 overflows too). Eigenvalues were unaffected.
Tolerance convergence (#490) 1e-3 → ≤ 1e-6 Solovev tolerance scan; renorm_threshold now insensitive from 10 to 1e8

The current full harness finds small DIII-D-like headline energy changes (n = 1 et₁: 4.741e-9 absolute); both Riccati cases are unchanged. Kinetic-field norm changes require further interpretation, as detailed below. Errors are relative to the same build reducing at every step (renorm_threshold = 1).

Review package with the figures, the #490 rerun, the threshold ladder (called ucrit in the original study) and the decision table: https://claude.ai/artifact/JqVHgVrx3xHbZLRJmqNAPe

Release note

  • Audience: users
  • Numerical impact: forward-integrator energies and perturbed-equilibrium outputs change. In the full stacked comparison, ideal Solovev et₁ changes by 1.780e-8 absolute and DIII-D n = 1 et₁ by 4.741e-9. In solovev_kinetic_calculated, raw displacement/derivative matrix norms change by approximately 5% and the regularized magnetic-field norm by 1.64%; these require a common-grid comparison before physical interpretation. Both Riccati cases are unchanged. (harness @ b8e1e71)
  • Migration: none. renorm_threshold keeps its default of 1e3, but in the forward path it now means the U₁ condition-number limit (Riccati keeps the entry-size meaning).

You can now move the ideal jump much closer to rational surfaces (singfac_min down to 1e-8) and get accurate energies, and high-n perturbed-equilibrium runs no longer silently corrupt their output. The forward solve now applies a Gaussian reduction when the U₁ solution columns lose independence, not when their norms spread apart; the plasma response matrix W = U₂U₁⁻¹ is also formed stably.

Regression report

Full SDCC fanout run on 2026-10-28, after merging #489 into this branch:

./regress_fanout.sh --refs a319ed2955ed3603a75bf074a4a66e4b201d5e46,b8e1e71037cd3ab0215e0554062ead908b525329 --name pr520_b8e1e7103

Baseline: #489 at a319ed295. Tested head: b8e1e7103. This stacked comparison isolates #520's changes. All 21 cases ran on SDCC sirius using Julia 1.12.6 and CI manifest pin 4c9d54fa. Cases used 2 Julia threads / 1 BLAS thread, except diiid_error_field at 16 / 8, with matching settings between refs. All 42 tasks, prep, and report job 1298807 completed with exit code zero.

  • 11 cases unchanged, including both Riccati builds, all three SLAYER cases, both extracted Galerkin cases, both GGJ cases, fixed-boundary EFIT, and Solovev multi-n.
  • 9 cases changed: diiid_error_field, diiid_multi_n, diiid_n1, diiid_truncation_n1, solovev_kinetic_calculated, solovev_kinetic_multiion, solovev_kinetic_ntv, solovev_kinetic_nuzero, and solovev_n1.
  • 1 inconclusive case: gal_resistive_pe extracts none of its eight quantities on either ref, as before; it provides no numerical validation.
  • Ideal headline energies: Solovev et₁ changes by 1.780e-8 absolute; DIII-D n = 1 by 4.741e-9; the DIII-D truncation case by 3.601e-9. Some response/error-field quantities change by approximately 0.02%.
  • Kinetic results requiring follow-up: in solovev_kinetic_calculated, ||xi_clebsch_alpha|| changes by 5.02%, ||dxi_clebsch_psi/dpsi|| by 5.01%, and ||Jb_theta_reg|| by 1.64%, while Re(et₁) changes by 1.935e-7 absolute. These are raw matrix norms; the saved ODE grid also changes from 608 to 595 points. A common-grid comparison is needed to distinguish sampling effects from changes in the physical fields. The report alone does not establish the cause.
  • Collisionless kinetic energy: solovev_kinetic_nuzero Re(et₁) changes by 7.647e-7 absolute, with saved ODE steps changing from 608 to 597.

The earlier parameter-scan results above are historical evidence; this run covers the shipped harness cases and does not rerun those scans or the targeted unit/full-run tests.

SDCC output: /home/ITER/pharrm/Projects/GPEC_dev/regress-fanout/runs/pr520_b8e1e7103/report.txt.

Full regression report (21 cases)
================================================================
Case: diiid_error_field — DIII-D-like error-field assessment, n=1: ErrorFields stage with the NTV-limited correction couplings
================================================================

Regression Report: diiid_error_field
=====================================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 16 threads/8 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 16 threads/8 BLAS, jit build
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
EFC overlap per kAt  [1 elem]  [1 elem]  4.508e-10 (0.00%)  ** CHANGED **
EFC whole-field NTV torque per kAt² [N·m]  [1 elem]  [1 elem]  8.421e-08 (0.00%)  ** CHANGED **
EFC residual-field NTV torque per kAt² [N·m]     [1 elem]  [1 elem]  3.414e-07 (0.02%)  ** CHANGED **
EFC reference rotation ω_ref [rad/s]  [1 elem]  [1 elem]  0.0e+00  OK  
EFC rotation-scan shifts [rad/s]  [9 elem]  [9 elem]  0.0e+00  OK  
EFC residual torque against rotation [N·m/kAt²]  [9 elem]  [9 elem]  1.137e-06 (0.02%)  ** CHANGED **
dominant-coupling singular values  [3 elem]  [3 elem]  2.065e-04 (0.01%)  ** CHANGED **
|delta_nominal| of coil set 1  1.406896e-06  1.406860e-06  3.6e-11  OK  
|Σ δ_as_designed| of the error-field coils       1.359053e-18  1.359072e-18  1.9e-23  OK  
Monte Carlo mean |δ| of the error-field coils    4.701298e-05  4.701295e-05  2.8e-11  OK  
Runtime (s)  263.0s  249.4s  --  
=====================================================================================================================================================================
Summary: 5 changed, 5 unchanged


================================================================
Case: diiid_multi_n — DIII-D-like equilibrium, n = 1, 2, ideal + perturbed equilibrium
================================================================

Regression Report: diiid_multi_n
==================================================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
total energy Re(et[1])  -5.750527e-01  -5.750527e-01  9.1e-11  OK  
total energy (all)  [58 elem]  [58 elem]  4.145e-07 (0.00%)  ** CHANGED **
ODE steps (saved)  626  620  6.000e+00 (0.96%)  ** CHANGED **
mpert  29  29  0.0e+00  OK  
npert  2  2  0.0e+00  OK  
toroidal mode label of each mode index (checksum)  8d8d42b8832d...  8d8d42b8832d...  identical  OK  
permeability diagonal P~_ii (all n blocks)  [58 elem]  [58 elem]  4.369e-08 (0.00%)  ** CHANGED **
forcing b~ (root-area-weighted), n = 1, 2  [58 elem]  [58 elem]  0.0e+00  OK  
response b~ (root-area-weighted), n = 1, 2  [58 elem]  [58 elem]  7.4e-13  OK  
PE plasma energy (sum over n)  2.936559e+00  2.936559e+00  1.235e-10 (0.00%)  ** CHANGED **
PE vacuum energy (sum over n)  3.113946e+00  3.113946e+00  0.0e+00  OK  
PE surface energy (sum over n)  3.523427e+00  3.523427e+00  5.039e-10 (0.00%)  ** CHANGED **
PE toroidal torque sum_n -2n Im(Phi_n' Lambda_nn^-1 Phi_n)/4  1.820338e-06  1.820338e-06  5.0e-13  OK  
resonant n per row (rows sorted by psi across n)  [7 elem]  [7 elem]  0.0e+00  OK  
resonant m per row  [7 elem]  [7 elem]  0.0e+00  OK  
resonant area-weighted field b^r per row  [7 elem]  [7 elem]  7.2e-09  OK  
island half-widths per row  [7 elem]  [7 elem]  3.677e-06 (0.02%)  ** CHANGED **
Chirikov parameter per row  [7 elem]  [7 elem]  3.003e-05 (0.01%)  ** CHANGED **
forcing-driven Delta' per row  [7 elem]  [7 elem]  4.558e-07 (0.01%)  ** CHANGED **
Runtime (s)  199.4s  199.1s  --  
==================================================================================================================================================================================
Summary: 8 changed, 11 unchanged


================================================================
Case: diiid_n1 — DIII-D-like equilibrium, n=1, ideal + perturbed equilibrium
================================================================

Regression Report: diiid_n1
==================================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
------------------------------------------------------------------------------------------------------------------------------------------------------------------
total energy Re(et[1])  8.006122e-01  8.006122e-01  4.741e-09 (0.00%)  ** CHANGED **
total energy Im(et[1])  7.924882e-05  7.924882e-05  3.4e-12  OK  
plasma energy Re(ep[1])  -1.372261e+00  -1.372261e+00  7.500e-09 (0.00%)  ** CHANGED **
vacuum energy Re(ev[1])  2.172874e+00  2.172874e+00  2.759e-09 (0.00%)  ** CHANGED **
vacuum matrix min eigenvalue  1.873975e-01  1.873975e-01  0.0e+00  OK  
plasma energy (all)  [35 elem]  [35 elem]  2.121e-06 (0.00%)  ** CHANGED **
vacuum energy (all)  [35 elem]  [35 elem]  2.508e-07 (0.00%)  ** CHANGED **
total energy (all)  [35 elem]  [35 elem]  2.084e-06 (0.00%)  ** CHANGED **
ODE steps (saved)  789  797  8.000e+00 (1.01%)  ** CHANGED **
ODE steps (total)  784  792  8.000e+00 (1.02%)  ** CHANGED **
q0  1.204212e+00  1.204212e+00  0.0e+00  OK  
q95  4.781723e+00  4.781723e+00  0.0e+00  OK  
beta_t  1.327024e-02  1.327024e-02  0.0e+00  OK  
beta_n  1.372511e+00  1.372511e+00  0.0e+00  OK  
internal inductance li1  8.842268e-01  8.842268e-01  0.0e+00  OK  
internal inductance li2  7.080757e-01  7.080757e-01  0.0e+00  OK  
internal inductance li3  7.304340e-01  7.304340e-01  0.0e+00  OK  
poloidal beta betap1  6.680739e-01  6.680739e-01  0.0e+00  OK  
poloidal beta betap2  5.349836e-01  5.349836e-01  0.0e+00  OK  
poloidal beta betap3  5.518764e-01  5.518764e-01  0.0e+00  OK  
# singular surfaces  5  5  0.0e+00  OK  
singular psi locations  [5 elem]  [5 elem]  0.0e+00  OK  
singular q values  [5 elem]  [5 elem]  0.0e+00  OK  
current beta betaj  4.236480e-01  4.236480e-01  0.0e+00  OK  
plasma volume  1.829472e+01  1.829472e+01  0.0e+00  OK  
plasma current  1.152130e+00  1.152130e+00  0.0e+00  OK  
mpert  35  35  0.0e+00  OK  
npert  1  1  0.0e+00  OK  
toroidal field bt0  2.006573e+00  2.006573e+00  0.0e+00  OK  
wall field bwall  3.880145e-01  3.880145e-01  0.0e+00  OK  
aspect ratio  2.845746e+00  2.845746e+00  0.0e+00  OK  
elongation kappa  1.708322e+00  1.708322e+00  0.0e+00  OK  
q profile (checksum)  6da947d0754e...  6da947d0754e...  identical  OK  
pressure profile (checksum)  c9c6ca4072ca...  c9c6ca4072ca...  identical  OK  
Mercier D_I profile (checksum)  26babbe22505...  26babbe22505...  identical  OK  
resistive interchange D_R profile (checksum)  c46830151a0b...  c46830151a0b...  identical  OK  
ballooning Delta' profile (checksum)  c27c6779804a...  c27c6779804a...  identical  OK  
island half-widths  [5 elem]  [5 elem]  1.400e-06 (0.00%)  ** CHANGED **
Chirikov parameter  [5 elem]  [5 elem]  1.114e-05 (0.00%)  ** CHANGED **
||resonant area-weighted field||  5.288611e-04  5.288704e-04  9.3e-09  OK  
dominant-coupling singular values  [3 elem]  [3 elem]  2.065e-04 (0.01%)  ** CHANGED **
|forcing overlap with dominant mode|  1.411520e-04  1.411483e-04  3.6e-09  OK  
|delta_nominal| of coil set 1  7.034480e-05  7.034300e-05  1.803e-09 (0.00%)  ** CHANGED **
||ddelta/d(shift)|| over coil sets  1.245436e-04  1.245425e-04  1.068e-09 (0.00%)  ** CHANGED **
||ddelta/d(tilt)|| over coil sets  4.177322e-07  4.177305e-07  1.7e-12  OK  
PE plasma energy  3.320466e+00  3.320466e+00  1.576e-09 (0.00%)  ** CHANGED **
PE vacuum energy  3.174319e+00  3.174319e+00  0.0e+00  OK  
PE surface energy  5.508585e+00  5.508585e+00  1.932e-08 (0.00%)  ** CHANGED **
PE toroidal torque  1.119354e-04  1.119354e-04  3.287e-12 (0.00%)  ** CHANGED **
NTV torque FGAR [N·m]  5.480624e-01  5.480610e-01  1.3e-06  OK  
NTV kinetic energy dW FGAR [J]  8.163328e-02  8.163282e-02  4.7e-07  OK  
Runtime (s)  235.5s  238.2s  --  
||forcing b~|| (root-area-weighted)  4.683171e-04  4.683171e-04  0.0e+00  OK  
resonant area-weighted field b^r  [5 elem]  [5 elem]  1.986e-08 (0.00%)  ** CHANGED **
==================================================================================================================================================================
Summary: 17 changed, 36 unchanged


================================================================
Case: diiid_n1_riccati — DIII-D-like equilibrium, n=1, Riccati integrator Δ' matrix
================================================================

Regression Report: diiid_n1_riccati
==================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
----------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
----------------------------------------------------------------------------------------------------------------------------------
delta prime (BVP diagonal)     [5 elem]  [5 elem]  0.0e+00  OK    
delta prime (raw side-major)   [10 elem]  [10 elem]  0.0e+00  OK    
edge coil response delta_coil  [10 elem]  [10 elem]  0.0e+00  OK    
total energy Re(et[1])  8.031971e-01  8.031971e-01  1.1e-14  OK    
plasma energy Re(ep[1])  -1.368534e+00  -1.368534e+00  6.3e-14  OK    
vacuum energy Re(ev[1])  2.171732e+00  2.171732e+00  6.4e-14  OK    
total energy (all)  [35 elem]  [35 elem]  3.9e-11  OK    
# singular surfaces  5  5  0.0e+00  OK    
singular psi locations  [5 elem]  [5 elem]  0.0e+00  OK    
singular q values  [5 elem]  [5 elem]  0.0e+00  OK    
ODE steps (saved)  93  93  0.0e+00  OK    
ODE steps (total)  2631  2631  0.0e+00  OK    
mpert  35  35  0.0e+00  OK    
npert  1  1  0.0e+00  OK    
q0  1.204212e+00  1.204212e+00  0.0e+00  OK    
q95  4.781723e+00  4.781723e+00  0.0e+00  OK    
beta_n  1.372511e+00  1.372511e+00  0.0e+00  OK    
Runtime (s)  197.9s  189.5s  --    
==================================================================================================================================
Summary: 17 unchanged


================================================================
Case: diiid_n1_riccati_precompiled — DIII-D-like equilibrium, n=1, Riccati integrator Δ' matrix, precompiled build
================================================================

Regression Report: diiid_n1_riccati_precompiled
==================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, aot build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, aot build
----------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
----------------------------------------------------------------------------------------------------------------------------------
delta prime (BVP diagonal)     [5 elem]  [5 elem]  0.0e+00  OK    
delta prime (raw side-major)   [10 elem]  [10 elem]  0.0e+00  OK    
edge coil response delta_coil  [10 elem]  [10 elem]  0.0e+00  OK    
total energy Re(et[1])  8.031971e-01  8.031971e-01  1.1e-14  OK    
plasma energy Re(ep[1])  -1.368534e+00  -1.368534e+00  6.3e-14  OK    
vacuum energy Re(ev[1])  2.171732e+00  2.171732e+00  6.4e-14  OK    
total energy (all)  [35 elem]  [35 elem]  3.9e-11  OK    
# singular surfaces  5  5  0.0e+00  OK    
singular psi locations  [5 elem]  [5 elem]  0.0e+00  OK    
singular q values  [5 elem]  [5 elem]  0.0e+00  OK    
ODE steps (saved)  93  93  0.0e+00  OK    
ODE steps (total)  2631  2631  0.0e+00  OK    
mpert  35  35  0.0e+00  OK    
npert  1  1  0.0e+00  OK    
q0  1.204212e+00  1.204212e+00  0.0e+00  OK    
q95  4.781723e+00  4.781723e+00  0.0e+00  OK    
beta_n  1.372511e+00  1.372511e+00  0.0e+00  OK    
Runtime (s)  25.3s  25.8s  --    
==================================================================================================================================
Summary: 17 unchanged


================================================================
Case: diiid_slayer_n1 — DIII-D-like H-mode equilibrium, n=1, SLAYER tearing-mode analysis (uncoupled per-surface, AMR, validity-gated)
================================================================

Regression Report: diiid_slayer_n1
=======================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
---------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
---------------------------------------------------------------------------------------------------------------------------------------
SLAYER surface indices  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER poloidal m  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER toroidal n  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER minor radius rs  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER r-based shear  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Lundquist S  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER D_norm  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER P_perp  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER tauk  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER iota_e  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Q_e  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Q_i  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Q_root [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER ω_Hz [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER γ_Hz [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER no_root flags [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER enabled flag  1  1  0.0e+00  OK    
Runtime (s)  226.5s  225.4s  --    
=======================================================================================================================================
Summary: 17 unchanged


================================================================
Case: diiid_slayer_n1_chi_perp_e — DIII-D-like H-mode equilibrium, n=1, SLAYER tearing-mode analysis with P_perp_model = chi_perp_e
================================================================

Regression Report: diiid_slayer_n1_chi_perp_e
=======================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
---------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
---------------------------------------------------------------------------------------------------------------------------------------
SLAYER surface indices  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER poloidal m  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER toroidal n  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER minor radius rs  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER r-based shear  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Lundquist S  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER D_norm  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER P_perp  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER tauk  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER iota_e  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Q_e  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Q_i  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER Q_root [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER ω_Hz [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER γ_Hz [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER no_root flags [2/1,3/1,4/1]  [3 elem]  [3 elem]  0.0e+00  OK    
SLAYER enabled flag  1  1  0.0e+00  OK    
Runtime (s)  232.6s  232.2s  --    
=======================================================================================================================================
Summary: 17 unchanged


================================================================
Case: diiid_slayer_n1_coupled — DIII-D-like H-mode equilibrium, n=1, SLAYER coupled multi-surface determinant with kinetic-file E×B rotation (reuses the n=1 deck via overrides)
================================================================

Regression Report: diiid_slayer_n1_coupled
================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
--------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
--------------------------------------------------------------------------------------------------------------------------------
SLAYER tauk  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER q_shift  [6 elem]  [6 elem]  0.0e+00  OK    
SLAYER coupled Q_root  [1 elem]  [1 elem]  0.0e+00  OK    
SLAYER coupled ω  [1 elem]  [1 elem]  0.0e+00  OK    
SLAYER coupled γ  [1 elem]  [1 elem]  0.0e+00  OK    
SLAYER coupled no_root flag  [1 elem]  [1 elem]  0.0e+00  OK    
SLAYER enabled flag  1  1  0.0e+00  OK    
Runtime (s)  231.2s  230.7s  --    
================================================================================================================================
Summary: 7 unchanged


================================================================
Case: diiid_truncation_n1 — DIII-D-like, n=1, psihigh=0.9999 with the resistive-layer overlap cap binding (truncation only)
================================================================

Regression Report: diiid_truncation_n1
===========================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
-----------------------------------------------------------------------------------------------------------------------------------------------------------
layer-overlap cap applied  1  1  0.0e+00  OK  
layer-overlap psilim  9.985081e-01  9.985081e-01  0.0e+00  OK  
first overlapping surface index  7  7  0.0e+00  OK  
diffusive-resistive layer widths (Δψ)  [7 elem]  [7 elem]  0.0e+00  OK  
qlim  6.200000e+00  6.200000e+00  0.0e+00  OK  
psilim  9.949767e-01  9.949767e-01  0.0e+00  OK  
mpert  37  37  0.0e+00  OK  
# singular surfaces  5  5  0.0e+00  OK  
singular q values  [5 elem]  [5 elem]  0.0e+00  OK  
singular psi locations  [5 elem]  [5 elem]  0.0e+00  OK  
plasma energy Re(ep[1])  -1.372379e+00  -1.372379e+00  6.398e-09 (0.00%)  ** CHANGED **
total energy Re(et[1])  8.005436e-01  8.005436e-01  3.601e-09 (0.00%)  ** CHANGED **
Runtime (s)  192.5s  191.8s  --  
===========================================================================================================================================================
Summary: 2 changed, 10 unchanged


================================================================
Case: efit_fixedbdy_separatrix — Fixed-boundary EFIT (box hugs LCFS), Newton separatrix-find regression
================================================================

Regression Report: efit_fixedbdy_separatrix
===============================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
-------------------------------------------------------------------------------------------------------------------------------
psio (total poloidal flux)  3.344390e-01  3.344390e-01  0.0e+00  OK    
q0  1.155202e+00  1.155202e+00  0.0e+00  OK    
q95  5.741022e+00  5.741022e+00  0.0e+00  OK    
beta_t  3.945172e-05  3.945172e-05  0.0e+00  OK    
beta_n  1.009505e-01  1.009505e-01  0.0e+00  OK    
Runtime (s)  48.0s  47.7s  --    
===============================================================================================================================
Summary: 5 unchanged


================================================================
Case: gal_resistive_diiid — DIII-D-like, n=1, RDCON outer-region Galerkin Δ′ with rpec coil columns (delta_coil)
================================================================

Regression Report: gal_resistive_diiid
============================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
----------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
----------------------------------------------------------------------------------------------------------------------------
gal # singular surfaces  4  4  0.0e+00  OK    
gal singular q values    [4 elem]  [4 elem]  0.0e+00  OK    
gal PEST3 Δ diagonal     [4 elem]  [4 elem]  0.0e+00  OK    
||gal Δ′ matrix||  2.302048e+03  2.302048e+03  0.0e+00  OK    
||gal Δ_coil block||     1.581714e+01  1.581714e+01  0.0e+00  OK    
gal D_I per surface      [4 elem]  [4 elem]  0.0e+00  OK    
gal α per surface  [4 elem]  [4 elem]  0.0e+00  OK    
||gal inner-layer Δ||    1.474577e+06  1.474577e+06  0.0e+00  OK    
||gal match cout||       3.048397e-02  3.048397e-02  0.0e+00  OK    
||gal match bpen||       1.206218e+00  1.206218e+00  0.0e+00  OK    
gal match residual       1.709610e-16  1.709610e-16  0.0e+00  OK    
Runtime (s)  184.9s  183.9s  --    
============================================================================================================================
Summary: 11 unchanged


================================================================
Case: gal_resistive_diiid_galinner — DIII-D-like, n=1, Galerkin Δ′ matched with the Galerkin inner-layer backend
================================================================

Regression Report: gal_resistive_diiid_galinner
============================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
----------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
----------------------------------------------------------------------------------------------------------------------------
gal # singular surfaces  4  4  0.0e+00  OK    
gal singular q values    [4 elem]  [4 elem]  0.0e+00  OK    
gal PEST3 Δ diagonal     [4 elem]  [4 elem]  0.0e+00  OK    
||gal Δ′ matrix||  2.302048e+03  2.302048e+03  0.0e+00  OK    
||gal Δ_coil block||     1.581714e+01  1.581714e+01  0.0e+00  OK    
gal D_I per surface      [4 elem]  [4 elem]  0.0e+00  OK    
gal α per surface  [4 elem]  [4 elem]  0.0e+00  OK    
||gal inner-layer Δ||    1.474566e+06  1.474566e+06  0.0e+00  OK    
||gal match cout||       3.048397e-02  3.048397e-02  0.0e+00  OK    
||gal match bpen||       1.206218e+00  1.206218e+00  0.0e+00  OK    
gal match residual       1.872671e-16  1.872671e-16  0.0e+00  OK    
Runtime (s)  181.1s  181.3s  --    
============================================================================================================================
Summary: 11 unchanged


================================================================
Case: gal_resistive_pe — DIII-D-like, n=1, DRIVEN/RPEC: gal-matched resistive ξ → PerturbedEquilibrium (coil-driven singular coupling)
================================================================

Regression Report: gal_resistive_pe
========================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status
------------------------------------------------------------------------------------------------------------------------
rational q values       N/A  N/A  N/A   N/A   
island half-widths      N/A  N/A  N/A   N/A   
Chirikov parameter      N/A  N/A  N/A   N/A   
||resonant b field||    N/A  N/A  N/A   N/A   
resonant b field (all)  N/A  N/A  N/A   N/A   
penetrated b field      N/A  N/A  N/A   N/A   
PE Δ' per surface       N/A  N/A  N/A   N/A   
||C resonant b field||  N/A  N/A  N/A   N/A   
Runtime (s)  182.8s  183.8s  --    
========================================================================================================================
Summary: 8 missing/N/A


================================================================
Case: ggj_ray_q500i — GGJ inner-layer :ray backend, q=4 surface, Q = 500i (beyond :galerkin)
================================================================

Regression Report: ggj_ray_q500i
=======================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-----------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
-----------------------------------------------------------------------------------------------------------------------
Re(Δ₊ interchange)  2.472007e+00  2.472007e+00  0.0e+00  OK    
Im(Δ₊ interchange)  1.335402e+01  1.335402e+01  0.0e+00  OK    
Re(Δ₋ tearing)      1.374966e-01  1.374966e-01  0.0e+00  OK    
Im(Δ₋ tearing)      7.427542e-01  7.427542e-01  0.0e+00  OK    
Runtime (s)  22.4s  22.6s  --    
=======================================================================================================================
Summary: 4 unchanged


================================================================
Case: ggj_reference — GGJ inner-layer Galerkin solver, Glasser & Wang 2020 Eq. 55 with physical τ_A, τ_R, 1 kHz rotation (Q ≈ 0.22i)
================================================================

Regression Report: ggj_reference
=======================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-----------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff     Status
-----------------------------------------------------------------------------------------------------------------------
Re(Δ₊ interchange)  1.225728e+03  1.225728e+03  0.0e+00  OK    
Im(Δ₊ interchange)  2.064823e+04  2.064823e+04  0.0e+00  OK    
Re(Δ₋ tearing)      -5.345663e+00  -5.345663e+00  0.0e+00  OK    
Im(Δ₋ tearing)      2.592091e+01  2.592091e+01  0.0e+00  OK    
Runtime (s)  10.6s  10.7s  --    
=======================================================================================================================
Summary: 4 unchanged


================================================================
Case: solovev_kinetic_calculated — Solovev, n=1, kinetic_source=calculated, kinetic_factor=1.0, 50/50 D-T multi-ion kinetic matrices, mode-converged band (delta_m=8, mpert=32); pressure-consistent kinetic.dat (n·T tied to the Solovev P₀≈4.27e4 Pa).
================================================================

Regression Report: solovev_kinetic_calculated
=======================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-------------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
-------------------------------------------------------------------------------------------------------------------------------------------------------
vacuum energy Re(ev[1])  1.054739e+01  1.054739e+01  1.866e-07 (0.00%)  ** CHANGED **
q0  1.900003e+00  1.900003e+00  0.0e+00  OK  
NTV fgar quadrature torque [N·m]   2.267351e-03  2.267140e-03  2.105e-07 (0.01%)  ** CHANGED **
||xi_clebsch_alpha||  6.041963e-01  5.738948e-01  3.030e-02 (5.02%)  ** CHANGED **
total energy Re(et[1])  1.828278e+00  1.828278e+00  1.935e-07 (0.00%)  ** CHANGED **
total energy Im(et[1])  -1.326607e+00  -1.326607e+00  8.860e-08 (0.00%)  ** CHANGED **
plasma energy Re(ep[1])  -8.719112e+00  -8.719112e+00  3.801e-07 (0.00%)  ** CHANGED **
ODE steps (saved)  608  595  1.300e+01 (2.14%)  ** CHANGED **
# singular surfaces  2  2  0.0e+00  OK  
boundary-response torque [N·m]     2.285518e-03  2.285518e-03  2.400e-10 (0.00%)  ** CHANGED **
singular psi locations  [2 elem]  [2 elem]  0.0e+00  OK  
total energy (all)  [32 elem]  [32 elem]  6.377e-06 (0.00%)  ** CHANGED **
ODE steps (total)  607  594  1.300e+01 (2.14%)  ** CHANGED **
mpert  32  32  0.0e+00  OK  
q95  3.147422e+00  3.147422e+00  0.0e+00  OK  
||Jb_theta_reg||  3.945600e-03  3.880975e-03  6.463e-05 (1.64%)  ** CHANGED **
Runtime (s)  229.3s  228.0s  --  
T_xe applied-forcing torque [N·m]  2.285518e-03  2.285518e-03  2.455e-10 (0.00%)  ** CHANGED **
||dxi_clebsch_psi/dpsi||  3.791914e+00  3.601764e+00  1.901e-01 (5.01%)  ** CHANGED **
singular q values  [2 elem]  [2 elem]  0.0e+00  OK  
npert  1  1  0.0e+00  OK  
=======================================================================================================================================================
Summary: 13 changed, 7 unchanged


================================================================
Case: solovev_kinetic_multiion — Solovev n=1, ideal + PE + multi-ion NTV (D + T + electron summed)
================================================================

Regression Report: solovev_kinetic_multiion
=================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
-------------------------------------------------------------------------------------------------------------------------------------------------
NTV total (D+T+imp+e) [N·m]  3.141935e+01  3.141992e+01  5.620e-04 (0.00%)  ** CHANGED **
NTV Deuterium [N·m]  1.973825e+01  1.973854e+01  2.955e-04 (0.00%)  ** CHANGED **
NTV Tritium [N·m]  2.046391e+01  2.046422e+01  3.081e-04 (0.00%)  ** CHANGED **
NTV electron [N·m]  -8.782805e+00  -8.782846e+00  4.149e-05 (0.00%)  ** CHANGED **
total energy Re(et[1])       1.090559e-02  1.090560e-02  1.780e-08 (0.00%)  ** CHANGED **
# singular surfaces  2  2  0.0e+00  OK  
Runtime (s)  220.4s  220.2s  --  
=================================================================================================================================================
Summary: 5 changed, 1 unchanged


================================================================
Case: solovev_kinetic_ntv — Solovev analytical equilibrium, n=1, ideal + PE + NTV torque quadrature
================================================================

Regression Report: solovev_kinetic_ntv
===============================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
---------------------------------------------------------------------------------------------------------------------------------------------------------------
NTV torque fgar [Re, Im]  [1 elem]  [1 elem]  5.909e-04 (0.00%)  ** CHANGED **
NTV ψ quadrature evaluations  45  45  0.0e+00  OK  
fluid CGL energy fcgl [J]  1.552025e+01  1.552248e+01  2.227e-03 (0.01%)  ** CHANGED **
root-area-weighted total energy Re(et[1])  1.090559e-02  1.090560e-02  1.780e-08 (0.00%)  ** CHANGED **
# singular surfaces  2  2  0.0e+00  OK  
singular psi locations  [2 elem]  [2 elem]  0.0e+00  OK  
q0  1.900006e+00  1.900006e+00  0.0e+00  OK  
Runtime (s)  218.9s  217.4s  --  
===============================================================================================================================================================
Summary: 3 changed, 4 unchanged


================================================================
Case: solovev_kinetic_nuzero — Solovev, n=1, kinetic_source=calculated, nutype=zero (collisionless energy integral), 50/50 D-T multi-ion kinetic matrices, mode-converged band (delta_m=8, mpert=32). Reuses the calculated example deck via override; pressure-consistent kinetic.dat.
================================================================

Regression Report: solovev_kinetic_nuzero
=============================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
---------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
---------------------------------------------------------------------------------------------------------------------------------------------
vacuum energy Re(ev[1])  1.058613e+01  1.058613e+01  8.181e-07 (0.00%)  ** CHANGED **
q0  1.900003e+00  1.900003e+00  0.0e+00  OK  
total energy Re(et[1])   2.290965e+00  2.290964e+00  7.647e-07 (0.00%)  ** CHANGED **
total energy Im(et[1])   -1.564546e+00  -1.564546e+00  1.309e-08 (0.00%)  ** CHANGED **
plasma energy Re(ep[1])  -8.295163e+00  -8.295165e+00  1.583e-06 (0.00%)  ** CHANGED **
ODE steps (saved)  608  597  1.100e+01 (1.81%)  ** CHANGED **
# singular surfaces      2  2  0.0e+00  OK  
singular psi locations   [2 elem]  [2 elem]  0.0e+00  OK  
total energy (all)       [32 elem]  [32 elem]  2.735e-05 (0.00%)  ** CHANGED **
ODE steps (total)  607  596  1.100e+01 (1.81%)  ** CHANGED **
mpert  32  32  0.0e+00  OK  
q95  3.147422e+00  3.147422e+00  0.0e+00  OK  
Runtime (s)  230.1s  222.7s  --  
singular q values  [2 elem]  [2 elem]  0.0e+00  OK  
npert  1  1  0.0e+00  OK  
=============================================================================================================================================
Summary: 7 changed, 7 unchanged


================================================================
Case: solovev_multi_n — Solovev analytical equilibrium, multi-n, ideal stability
================================================================

Regression Report: solovev_multi_n
===================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
-----------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff       Status
-----------------------------------------------------------------------------------------------------------------------------------
total energy Re(et[1])  -2.134718e+00  -2.134718e+00  1.1e-13    OK    
total energy Im(et[1])  1.968592e-10  1.968506e-10  8.6e-15    OK    
vacuum matrix min eigenvalue  1.730832e+00  1.730832e+00  0.0e+00    OK    
plasma energy (all)  [70 elem]  [70 elem]  1.8e-11    OK    
vacuum energy (all)  [70 elem]  [70 elem]  2.0e-11    OK    
total energy (all)  [70 elem]  [70 elem]  1.4e-11    OK    
ODE steps (saved)  56  56  0.0e+00    OK    
ODE steps (total)  1044  1044  0.0e+00    OK    
q0  1.900006e+00  1.900006e+00  0.0e+00    OK    
q95  3.147422e+00  3.147422e+00  0.0e+00    OK    
# singular surfaces  3  3  0.0e+00    OK    
singular psi locations  [3 elem]  [3 elem]  0.0e+00    OK    
mpert  35  35  0.0e+00    OK    
npert  2  2  0.0e+00    OK    
q profile (checksum)  2340b888d410...  2340b888d410...  identical  OK    
Runtime (s)  189.3s  191.8s  --    
===================================================================================================================================
Summary: 15 unchanged


================================================================
Case: solovev_n1 — Solovev analytical equilibrium, n=1, ideal stability
================================================================

Regression Report: solovev_n1
==================================================================================================================================================
Ref 1: a319ed295  @ a319ed295 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
Ref 2: b8e1e7103  @ b8e1e7103 (2026-10-28)
       env: julia 1.12.6, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 2 threads/1 BLAS, jit build
--------------------------------------------------------------------------------------------------------------------------------------------------
Quantity  a319ed295  b8e1e7103  Diff  Status       
--------------------------------------------------------------------------------------------------------------------------------------------------
total energy Re(et[1])  1.090559e-02  1.090560e-02  1.780e-08 (0.00%)  ** CHANGED **
total energy Im(et[1])  1.446796e-10  1.014338e-10  4.3e-11  OK  
plasma energy Re(ep[1])       -1.041640e+01  -1.041640e+01  2.842e-08 (0.00%)  ** CHANGED **
vacuum energy Re(ev[1])       1.042730e+01  1.042730e+01  1.063e-08 (0.00%)  ** CHANGED **
vacuum matrix min eigenvalue  2.170703e+00  2.170703e+00  0.0e+00  OK  
plasma energy (all)  [32 elem]  [32 elem]  1.052e-05 (0.00%)  ** CHANGED **
vacuum energy (all)  [32 elem]  [32 elem]  4.494e-06 (0.00%)  ** CHANGED **
total energy (all)  [32 elem]  [32 elem]  1.030e-05 (0.00%)  ** CHANGED **
ODE steps (saved)  287  285  2.000e+00 (0.70%)  ** CHANGED **
ODE steps (total)  285  283  2.000e+00 (0.70%)  ** CHANGED **
q0  1.900006e+00  1.900006e+00  0.0e+00  OK  
q95  3.147422e+00  3.147422e+00  0.0e+00  OK  
beta_t  4.621027e-02  4.621027e-02  0.0e+00  OK  
beta_n  3.217775e+00  3.217775e+00  0.0e+00  OK  
# singular surfaces  2  2  0.0e+00  OK  
singular psi locations  [2 elem]  [2 elem]  0.0e+00  OK  
singular q values  [2 elem]  [2 elem]  0.0e+00  OK  
mpert  32  32  0.0e+00  OK  
npert  1  1  0.0e+00  OK  
q profile (checksum)  2340b888d410...  2340b888d410...  identical  OK  
pressure profile (checksum)   108b947087e6...  108b947087e6...  identical  OK  
ca_left (checksum)  fc761aba9205...  3b7494953686...  0.000e+00 (0.00%)  ** CHANGED **
Runtime (s)  192.5s  194.6s  --  
==================================================================================================================================================
Summary: 9 changed, 13 unchanged

Notes for reviewers

  • Trigger: compute_solution_norms! reduces when column_condition(U₁) (1-norm estimate of the column-normalized U₁, from QR via cond(UpperTriangular(R), 1)) exceeds renorm_threshold. Singular surfaces still always reduce.
  • Storage: the reduction arrays grow by doubling (grow_reduction_storage!). The old 100-slot cap overflowed silently at n ≥ 6 and corrupted PE output (island widths off by 1e73).
  • W = U₂ / lu(U₁) in free_run, free_compute_total and build_result: U₂ / U₁ factors U₁ᵀ, so partial pivoting runs across U₁'s column norms, which span ~1e110 just after a reduction. When the last event before the edge is a reduction, W was garbage (DIIID n = 4, δm = 24: et₁ −0.026 instead of 0.157). Older than this PR; the new trigger makes it more likely.
  • Decisions to weigh in on: default renorm_threshold 1e3 rather than 1e6 (1e6 is equally accurate in the forward path; 1e3 keeps Riccati, which shares the key, on its tested value); redefining renorm_threshold rather than adding a key; cond(U₁) rather than cond[U₁;U₂]; keeping elimination rather than an (S, I) or QR reset; no dedicated test for the lu fix (no small matrix reproduces it).

…ditioning of U₁ and factor U₁ in the plasma response matrix

The forward integrator reduced only when the U₁ column-norm spread exceeded
ucrit, which misses columns that grow alike and turn near-parallel. It now
reduces when the 1-norm condition estimate of the column-normalized U₁
exceeds ucrit (ucrit keeps its entry-size meaning in the Riccati path).
The reduction storage grows on demand instead of overflowing.

wp = U₂ / U₁ factored U₁ᵀ, so partial pivoting ran across U₁'s columns,
whose norms span ~1e110 right after a reduction; it now factors U₁ itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added bugfix Something was wrong and now is not changed-results Results move or an interface breaks - read before upgrading labels Oct 12, 2026
jhalpern30 and others added 3 commits October 12, 2026 17:14
…reduction check

Drop the column_condition helper and its linear-algebra unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reduction check

Drop the column_condition helper and its linear-algebra unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m:OpenFUSIONToolkit/GPEC into bugfix/ffs-reduction-conditioning-trigger
@jhalpern30 jhalpern30 self-assigned this Oct 12, 2026
@jhalpern30
jhalpern30 requested a review from d-burg October 12, 2026 21:53
@jhalpern30
jhalpern30 marked this pull request as ready for review October 12, 2026 21:53
@jhalpern30

jhalpern30 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

cc-ing @logan-nc @matt-pharr since this would basically fundamentally change the decades-old approach of how fixups are done in ForceFreeStates. After a lot of investigation/tests, it seems that the growth oriented reduction does not properly capture when a fixup is needed. Instead, the condition number of the displacement columns is more accurate, producing good numerics. Specifically, this fixes the Solov'ev case, which has a higher alpha (Frobenius coefficient in the expansion) at the singular surfaces, and was giving garbage as singfac_min was decreased

And on a related note, I've verified and tested that the 3D code also has this same issue (but worse) and is fixed by this method

@logan-nc

Copy link
Copy Markdown
Collaborator

I'm happy to implement tested improvemlike this! this! The fixup is the magic sauce, so having our own recipe is a good result of the rewrite effort

@matt-pharr

Copy link
Copy Markdown
Collaborator

@jhalpern30 be sure to post some plots highlighting the results, a table is nice but a plot is a much better record!

@logan-nc

Copy link
Copy Markdown
Collaborator

@matt-pharr figures are in the artifact linked in in the description

@d-burg d-burg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mechanism is convincing to me: columns that grow alike and turn parallel are invisible to a spread test, and the new unit tests separate the two triggers nicely. The storage overflow and the U₂ / lu(U₁) fix are clear bug fixes. I'm less sure the evidence is there yet for replacing the reduction trigger itself.

My and claude's brains merged into a disconcerting synthetic hybrid network and yielded the following items:

Blocking

  1. Migration. The title carries ! and Migration starts with "none", which is what fails the title check, and substantively a forward-path deck with a hand-set ucrit is reinterpreted without a warning. Suggested text: "Migration: forward-integrator decks that set ucrit by hand should return it to the default 1e3. The value is now compared with a condition estimate of the column-normalized U₁, not with the spread of column-norm growth; small values such as 10 now reduce very often, and values above about 1e10 lose digits. Riccati decks need no change."
  2. Regression report. The code block is empty and the stamp (1dc6138aa) predates the last two commits. This changes when every forward run reduces and how W is formed for every integrator, so I'd like the full harness now, d50a5e5b6 against the current head as two git refs, especially the Riccati, perturbed-equilibrium, kinetic Solovev and multi-n cases. Rerunning at merge time is cheap; reviewing blind is not.

Evidence I'd want for "changes the decades-old approach"

  • Every error in the table is measured against the same build with ucrit = 1, which shows self-consistency, not correctness. One independent reference per headline row would help: the Riccati integrator on the same Solovev decks at singfac_min 1e-8, or a tolerance-converged or Fortran value where available. The figures sit behind a link "shared on request"; the numbers the argument rests on should be in the PR.
  • Per case: accepted steps, number of reductions, the largest estimate at the moment of reduction, wall time and peak memory against #489's head. Without reduction counts nobody can judge 1e3 against 1e6, and with unbounded storage a run that reduces every other step is a memory problem the old 100-slot cap used to hide.
  • Cases not yet covered: an ideal-unstable equilibrium where det U₁ passes through zero inside the plasma (U₁ is singular there for a physical reason, so the trigger fires whatever its threshold), a kinetic run, and the largest multi-n deck.

On your five decisions

  1. 1e3 vs 1e6: undecided until the reduction counts are shown; leaning 1e6 for the forward path. The stated reason for 1e3 is about Riccati sharing the key, not about the forward path.
  2. One key, two meanings: I'd add a forward-path key. Decks are reinterpreted silently, switching integrator changes what the same number means, and the shared key is already forcing a default neither path would pick on its own. The ! in the title makes a new key cheap now.
  3. cond(U₁) rather than cond([U₁; U₂]): agree; the elimination pivots on U₁ and W inverts U₁.
  4. Elimination rather than an (S, I) or QR reset: agree for this PR. Worth a follow-up issue: the elimination never rescales columns, which is why the norms span 1e110 in the first place.
  5. No test for the lu fix: disagree; a 4×4 matrix reproduces it (inline).

Suggestion: the storage fix and the lu fix stand on their own and are easy to verify; the trigger change is a design decision that needs the evidence above. Splitting them would let the two bug fixes go in quickly on #489.

Small, outside the diff

  • The OdeState field docstrings in EulerLagrange.jl still give the reduction arrays fixed shapes ((numpert_total, numunorms_init), "length numunorms_init"); they are initial capacities now. docs/src/stability.md still calls numunorms_init a "renorm checkpoint budget".
  • The comment at Riccati/Driver.jl:77 still quotes wp = u[:,:,2] / u[:,:,1].
  • The description's notes refer to column_condition, which the last two commits inlined.

Comment thread src/ForceFreeStates/EulerLagrange.jl Outdated
Increase `numunorms_init` if needed. Automatic resizing will be added in a future version."
end
# Reduce at a singular surface or once the U₁ columns lose independence (1-norm condition estimate of the column-normalized U₁)
@views if sing_flag || cond(UpperTriangular(qr!(u[:, :, 1] ./ norm.(eachcol(u[:, :, 1]))').R), 1) > ctrl.ucrit

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two claude things about this trigger.

A 1-norm condition estimate of a column-normalised matrix has a floor that grows with the matrix size. Applying this elimination to random complex matrices leaves the estimate around 3e2 at N = 34 and above 1e3 from about N = 64, so for large numpert_total (multi-n, high n with a wide m range) the default 1e3 could fire on every second step. Real solution matrices are structured and may sit well below that, so this is a question: could you post odet.ifix and odet.step for each harness case, in particular the multi-n deck and DIIID n = 6/8/10? If ifix is close to step/2 anywhere, I'd compare against the value right after the last reduction, or scale the threshold with N, rather than raise the default.

It also does a QR plus about five N×N allocations on every accepted step, where the old test reused norms already computed. Standalone that is 0.05 ms at N = 34 and 7.7 ms at N = 400; probably small next to the right-hand side, but could you post benchmarks/benchmark_git_branches.jl numbers for the DIIID ideal and multi-n decks against #489's head? I'd also bring back the named helper the description mentions: it is the natural place for a reused work matrix and a direct unit test of the estimate.

# Compute plasma response matrix W = U₂ * U₁⁻¹
wp = zeros(ComplexF64, numpert_total, numpert_total)
@views wp .= (odet.u[:, :, 2] / odet.u[:, :, 1]) ./ equil.psio^2
@views wp .= (odet.u[:, :, 2] / lu(odet.u[:, :, 1])) ./ equil.psio^2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The claim checks out, and it is reproducible at 4×4: take L[i,j] = 1 on the diagonal and 0.8cis(i + 2j) below it, U1 = (L .* exp10.(range(110, 0; length=4))')[4:-1:1, :], A = [cis(0.3i*j)/(i+j) for i in 1:4, j in 1:4], W = A + A', and U2 = W * U1. Then U2 / U1 is off by 0.15 relative and U2 / lu(U1) by 2e-16 (other Hermitian W give 0.1 to 1). With random dense U₁ of the same column scales both forms are fine, which is probably why the search came up empty.

Since the expression now appears three times (here, line 279 and Result.jl:243), I'd put it in one small helper used by free_run, free_compute_total and build_result, and unit-test it with that matrix at rtol = 1e-12.

This path is shared with Riccati, so "the Riccati path is untouched" should be backed by the Riccati rows of the harness table. I expect rounding-level moves only.

Comment thread src/ForceFreeStates/CoreTypes.jl Outdated
- `ksing::Int` - Singular surface handling parameter
- `eulerlagrange_tolerance::Float64` - Relative tolerance for ODE integration of Euler-Lagrange equations
- `ucrit::Float64` - Critical value of unorm ratio to trigger solution normalization. In the standard path it triggers Gaussian reduction; in the Riccati path it triggers `renormalize_riccati_inplace!`. Default `1e3`. The trigger compares column-norm growth, not conditioning, so near-parallel columns can leave an error in the free-boundary energies that does not shrink with `eulerlagrange_tolerance`.
- `ucrit::Float64` - Normalization threshold, default `1e3`. Forward path: the condition number of the column-normalized U₁ above which a Gaussian reduction is applied; the reduction pivots on U₁, so its rounding error and that of the reconstructed `u_store` scale as eps·`ucrit` (values ≥ 1e10 lose digits). Riccati path: the largest entry of U₁ or U₂ above which `renormalize_riccati_inplace!` runs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says the rounding error scales as eps·ucrit, but the check runs once per accepted step and is skipped on the first step after a reduction, so the conditioning when the elimination actually runs is at least ucrit and can be well above it where growth per step is large, which is the small-singfac_min regime this PR targets. Do you have the distribution of the estimate at the moment each reduction fires for the Solovev singfac_min = 1e-8 case? If the overshoot is routinely a few decades, I'd say "at least", and also that this is a 1-norm estimate whose healthy baseline grows with the number of modes.

Doubles the capacity of the per-reduction arrays (`fixfac`, `index`, `sing_flag`, `fixstep`, `zeroed_idx`).
"""
function grow_reduction_storage!(odet::OdeState)
nfix = size(odet.fixfac, 3)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doubling zero is still zero, so numunorms_init = 0 throws a BoundsError on the first reduction now that the docstring says the storage grows as needed.

Suggested change
nfix = size(odet.fixfac, 3)
nfix = max(size(odet.fixfac, 3), 1)

Comment thread test/runtests_fullruns.jl Outdated
end
et_ref = total_energy(1e3)
for ucrit in (2.0, 3.0, 5.0, 10.0)
for ucrit in (3.0, 10.0, 1e2, 1e6)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why were 2.0 and 5.0 dropped from this ladder? The rtol is unchanged, but removing the smallest values narrows what the test accepts, and 5.0 was the value that fails on develop for the #489 bug. The reference for your whole results table is the ucrit = 1 run, and the description claims insensitivity from 10 to 1e8. If 2.0 still passes at 1e-6, please keep it and add 1.0 and 1e8 so the test covers the claimed range; if it doesn't, we should understand that before trusting the reference.

Comment thread test/runtests_fullruns.jl
end

# A ucrit reduction landing just before an ideal crossing must not change which solution the crossing removes.
# A reduction landing just before an ideal crossing must not change which solution the crossing removes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing pins the results that motivate this PR. This testset uses the stock Solovev deck (singfac_min = 1e-4), where the old and new triggers agree. Since it already rewrites the deck per run, a second loop at singfac_min = 1e-8 checking et₁ across the same ucrit ladder is a few lines, and a harness case for one high-n perturbed-equilibrium quantity (an island width at n = 6) would pin the storage fix end to end.

@logan-nc logan-nc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One item not in the six open threads, on the kinetic crossing. compute_solution_norms! (head 2baa20b, EulerLagrange.jl:1007) returns early when odet.new && !at_ideal_crossing, resetting the baseline. The kinetic-crossing call at EulerLagrange.jl:847 passes sing_flag = true with no resonant_rows, so when the step before the surface already reduced (odet.new == true), that crossing resets and skips the reduction the flag is supposed to force. Fortran GPEC closed the same hole in PrincetonUniversity/GPEC#332 (fix_required = sing_flag ahead of the branch). The early return is pre-existing on develop, but this PR owns the trigger now, so if odet.new && !at_ideal_crossing && !sing_flag (or passing the surface's rows from the kinetic path too) belongs here.

@matt-pharr

Copy link
Copy Markdown
Collaborator

@jhalpern30 what is the status on this? Can this make it in for 2.0.0?

Base automatically changed from bugfix/ffs-crossing-resonant-elimination to develop October 29, 2026 01:20
@matt-pharr

Copy link
Copy Markdown
Collaborator

@logan-nc thoughts? This seems like it would be valuable. If Jake doesn't have time before APS then I guess this could be 2.1? Or someone else could get into the weeds on this?

@jhalpern30

Copy link
Copy Markdown
Collaborator Author

@jhalpern30 what is the status on this? Can this make it in for 2.0.0?

Probably not. Daniel raised some valid points that I don't have the time/effort to look into before APS

@logan-nc logan-nc removed this from the GPEC v2.0.0 milestone Oct 29, 2026
@logan-nc

Copy link
Copy Markdown
Collaborator

@jhalpern30 @matt-pharr it's time to face reality on v2.0 scope and it seems reasonable that such a fundamental change could come in a minor version update with clean validation. That lets us release the "decades-old approach" for warm familiarity and creates a clean historical record of the improvement.

I am not afraid of releasing 2.1 in Dec. so please do keep this on your todo list for immediately post APS.

@logan-nc

logan-nc commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

@jhalpern30 The kinetic-crossing hole I flagged above (Fortran GPEC PR 332) is fixed separately in #626, so it doesn't wait on this PR's trigger change. In compute_solution_norms!, a call with sing_flag set now always reduces, and if it comes right after a reduction it keeps that reduction's pivot order (Fortran commit 1f8abc54).

This PR rewrites the trigger in the same function, so whichever of the two lands second needs a small rebase there. The changes compose: #626 only replaces the early return, and your conditioning test slots in where the uratio test is. This PR also needs a rebase onto develop for #602's move of ctrl.renorm_threshold to ctrl.algorithm.renorm_threshold.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Something was wrong and now is not changed-results Results move or an interface breaks - read before upgrading

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ForceFreeStates - Gaussian reduction trigger misses near-parallel solutions

4 participants