Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1f01e9c
FORCEFREESTATES - MINOR - added first iteration of looping BC in Riccati
viaweber98 Jul 8, 2026
3a0837e
InnerLayer (GGJ) - NEW - complex rotated-ray entire-solution-based co…
matt-pharr Jul 8, 2026
a4ec72d
DOCS - NEW FEATURE - JCP-style documentation standard and full InnerL…
logan-nc Jul 9, 2026
3c8aeb7
FORCEFREESTATES - Minor - first iterations for looping BC in Riccati
viaweber98 Jul 9, 2026
e5ffbd6
DOCS - UPDATE - Fix some inaccuracies in docs additions
matt-pharr Jul 10, 2026
f232247
ForceFreeStates - NEW - Add inner layer parameters to hdf5 for easy i…
matt-pharr Jul 10, 2026
a7e5eba
PerturbedEquilibrium, ForceFreeStates - BUG FIX + NEW - penetrated fi…
matt-pharr Jul 10, 2026
cd884c4
PerturbedEquilibrium, ForceFreeStates - NEW - coefficient-based Delta…
matt-pharr Jul 10, 2026
9dc37bb
ForceFreeStates - NEW - rotated-ray inner-layer backend as the resist…
matt-pharr Jul 10, 2026
ec0f85e
FORCEFREESTATES - MINOR - Add delta_coil edge-BC test harness for gal…
viaweber98 Jul 12, 2026
91c03c7
FORCEFREESTATES - MINOR - test runs for delta_coil
viaweber98 Jul 12, 2026
31814dc
FORCEFREESTATES - MINOR - updates comparing plots with galerkin
viaweber98 Jul 13, 2026
f842329
FORCEFREESTATES - MINOR - additional plots created for alignment of S…
viaweber98 Jul 14, 2026
63cd88d
FORCEFREESTATE - Minor - began adding resonant_match_rpec + resist_ev…
viaweber98 Jul 15, 2026
bdae287
FORCEFREESTATES - MMINOR - Testing for sensitivity of deltacoil
viaweber98 Jul 17, 2026
05e6739
FORCEFREESTATES - MINOR - deltacoil vs rdcon convergence tests
viaweber98 Jul 17, 2026
acb4278
Merge remote-tracking branch 'origin/GGJ_colocation_backend' into loo…
matt-pharr Jul 22, 2026
886bf7f
Merge pull request #331 from OpenFUSIONToolkit/loop-boundary-cond-ric…
matt-pharr Jul 22, 2026
205c531
Merge branch 'develop' into GGJ_colocation_backend
matt-pharr Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
!docs/resources/*.pdf
*.png
!docs/src/assets/**/*.png
# committed documentation figures (generated by docs/src/figures/**/make_*.jl)
!docs/src/figures/**/*.png
*.jld2
.gitattributes
Manifest.toml
# the docs figure-provenance manifest is not a Julia Manifest.toml (case-insensitive FS collides)
!docs/src/figures/manifest.toml
*.log
docs/build/
anaconda_projects/
Expand Down
6 changes: 2 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Expand Down Expand Up @@ -57,14 +58,11 @@ QuadGK = "2.11.3"
Roots = "2.2.13"
SparseArrays = "1"
SpecialFunctions = "2.5.1"
Random = "1"
StaticArrays = "1.9.15"
Statistics = "1"
TOML = "1"
Test = "1"
julia = "1.11"

[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[targets]
test = ["Random"]
96 changes: 96 additions & 0 deletions benchmarks/deltacoil_sensitivity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# `delta_coil` truncation & near-rational sensitivity study

Sensitivity of the STRIDE/Riccati **`delta_coil`** (outer small-solution coefficient at each rational
surface, driven by unit edge/coil modes; `singular/delta_coil_matrix`) to two numerical knobs:

- **`dmlim`** — the outer-domain truncation point, active only when `set_psilim_via_dmlim = true`
(`psilim = psi(q_last + dmlim)`).
- **`singfac_min`** — the fractional distance from a rational surface at which the ideal jump is
enforced (how close the integration approaches the rational).

Case: `examples/DIIID-like_gal_resistive_pe_example` (DIII-D-like, n=1, rationals q=2,3,4,5, `qmax=5.426`).

## Two metrics, per singular surface

`delta_coil_matrix` is `(2·msing, ncoil)`: each singular surface is **two rows** (its L/R small
solutions). A surface's response vector `v_s` is that `2×ncoil` block flattened. We report, against a
**nominal** run (the case as-shipped: `set_psilim_via_dmlim=false`, `dmlim=0.2`, `singfac_min=1e-4`):

1. **Norm** `‖v_s‖` — magnitude sensitivity.
2. **Cosine similarity** `|⟨v_nom, v_s⟩| / (‖v_nom‖·‖v_s‖)` — phase-invariant normalized dot product.
`1.0` = identical pattern (only rescaled); `<1` = the coil-response **pattern rotated** — a change
the norm cannot see.

All runs use `gal_match_flag = false`: `delta_coil` is produced by the rpec edge loop independently of
the resistive match, so there is no inner-layer `msing`/array guard and `dmlim` is free to change the
surface count. Results are aligned **by q-value**, so a surface appearing/disappearing is handled cleanly.

## Key findings

- **`singfac_min` is inert.** Norm and cosine are identical to 5–6 significant figures across
`1e-5 … 1e-3` at every surface including the edge — `delta_coil` is independent of the near-rational
approach distance in both magnitude and pattern.

- **Integration tolerance is inert too.** Sweeping `eulerlagrange_tolerance` over `1e-8 … 1e-12` leaves
`delta_coil` bit-identical — norms, cosine (= 1.000000), and the full singular-value spectrum unchanged.

- **SVD confirms good conditioning.** The `delta_coil` matrix is well-conditioned (cond ≈ 26, σ₁ ≈ 29,
σ_min ≈ 1.1). Below the `dmlim` threshold the singular-value spectrum and the leading left singular
vector are stable (`u₁·u₁_nom` = 0.998–1.000); at `dmlim = 0.5` the matrix drops to rank-6 (q5 gone),
so its norm and condition number fall (cond → 11) — the discrete surface loss shows cleanly in the SVD.

- **`dmlim` is the dominant sensitivity — and it is a *pattern-level* effect the norm hides.** Under the
`dmlim` sweep the norms of q2/q3/q4 barely move (~5–16%), which looks robust; but the cosine with the
nominal drops sharply — e.g. at `dmlim=0.5` the q4 pattern is nearly orthogonal to nominal
(cosine ≈ 0.17, ~80° rotation) at ~16% norm change. **A norm-only scan would falsely call this robust.**

- **The truncation rule drops the edge rational at a sharp threshold.** `dmlim` keeps the outermost
rational `q_r` only if `q_r + dmlim < qmax`. Here q=5 survives only for **`dmlim < qmax − 5 = 0.426`**.
Above that, q5 is dropped entirely (edge becomes q4), and removing it reorganizes the coil response at
every remaining surface — worst right at `dmlim ≈ 0.5`, partially realigning as `dmlim` grows further.

- **The collapse is a STEP at the threshold, not a smooth rotation** (fine sweep, 0.30–0.50). Just below
(`dmlim=0.42`) every surface's cosine is `0.9999+` — the truncation sits at `qmax`, i.e. the full
nominal domain. The instant q5 drops (`dmlim=0.44`) the cosine collapses discontinuously: q4 → 0.104
(near-orthogonal), q3 → 0.55, q2 → 0.78. Above the threshold it only slowly realigns (q4: 0.10 → 0.17
by `dmlim=0.5`). So the pattern change is driven by the *discrete* loss of the surface, not a gradual
boundary shift.

- Practical guidance: for this diverted-style case the shipped default `set_psilim_via_dmlim=true,
dmlim=0.2` sits comfortably below the 0.426 threshold (all four rationals retained). Choosing
`dmlim` near/above the threshold both changes which surfaces exist and abruptly rotates the response
pattern — avoid parking `dmlim` near `qmax − floor(qmax)`.

## Layout

```
scripts/ deltacoil_metrics.jl norm + cosine-vs-nominal, per surface (dmlim & singfac sweeps)
deltacoil_svd_tol.jl SVD (singular values + vectors) + integration-tolerance scan
deltacoil_sensitivity.jl per-surface norms only (earlier, simpler version)
plot_deltacoil_metrics.py cosine-similarity plots
plot_deltacoil_dmlim_fine.py fine dmlim sweep plot
plot_deltacoil_svd.py SVD spectrum + tolerance-invariance plot
plot_deltacoil_sensitivity.py norm plots
results/ *_result.txt raw scan tables (incl. deltacoil_svd_tol_result.txt)
figures/ deltacoil_metrics.png cosine similarity vs dmlim / singfac
deltacoil_sensitivity.png |delta_coil| vs dmlim / singfac
deltacoil_dmlim_fine.png fine dmlim sweep (0.30–0.50) through the q5-drop threshold
deltacoil_svd.png singular-value spectrum vs dmlim + tolerance invariance
```

## Reproduce

```bash
cd <repo-root>
# coarse dmlim + singfac sweeps:
julia --project=. benchmarks/deltacoil_sensitivity/scripts/deltacoil_metrics.jl \
examples/DIIID-like_gal_resistive_pe_example
# fine dmlim sweep only (skip singfac), custom dmlim points:
SKIP_SINGFAC=1 julia --project=. benchmarks/deltacoil_sensitivity/scripts/deltacoil_metrics.jl \
examples/DIIID-like_gal_resistive_pe_example 0.30 0.35 0.40 0.42 0.44 0.46 0.48 0.50
# then plot (edit the embedded values to match a new run):
python3 benchmarks/deltacoil_sensitivity/scripts/plot_deltacoil_metrics.py
```

Each sweep point is one GPEC run (~1–2 min). Scans use temp dirs and never overwrite the example's
own HDF5 outputs; the galerkin reference `gpec.h5` is never touched.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Sweep A — delta_coil vs dmlim ∈ (0,1) [nominal ncoil=26]
================================================================================
nominal ‖v‖ per surface: q2.0=11.94 q3.0=9.687 q4.0=12.59 q5.0=31.7

(1) ‖delta_coil‖ per surface (— = surface absent):
q\dmlim | 0.3 | 0.35 | 0.4 | 0.42 | 0.44 | 0.46 | 0.48 | 0.5
q=2.0 | 1.172e+01 | 1.181e+01 | 1.189e+01 | 1.193e+01 | 1.108e+01 | 1.107e+01 | 1.107e+01 | 1.107e+01
q=3.0 | 9.560e+00 | 9.609e+00 | 9.660e+00 | 9.681e+00 | 9.713e+00 | 9.681e+00 | 9.650e+00 | 9.619e+00
q=4.0 | 1.251e+01 | 1.254e+01 | 1.257e+01 | 1.258e+01 | 1.521e+01 | 1.498e+01 | 1.477e+01 | 1.457e+01
q=5.0 | 3.349e+01 | 3.273e+01 | 3.202e+01 | 3.177e+01 | — | — | — | — <- edge

(2) cosine similarity with nominal |<v_nom,v>|/(‖v_nom‖‖v‖) (1.0 = same pattern):
q\dmlim | 0.3 | 0.35 | 0.4 | 0.42 | 0.44 | 0.46 | 0.48 | 0.5
q=2.0 | 0.996348 | 0.998666 | 0.999843 | 0.999991 | 0.775613 | 0.784651 | 0.793553 | 0.802348
q=3.0 | 0.992477 | 0.997249 | 0.999675 | 0.999982 | 0.551682 | 0.568847 | 0.585903 | 0.602900
q=4.0 | 0.981228 | 0.993110 | 0.999184 | 0.999955 | 0.104129 | 0.125729 | 0.149245 | 0.174318
q=5.0 | 0.942364 | 0.978476 | 0.997414 | 0.999857 | — | — | — | —

read: ‖v‖ moves ⇒ magnitude sensitivity; cosine<1 ⇒ the response PATTERN over coil modes rotated.
cosine≈1 with ‖v‖ varying ⇒ pure rescaling; cosine falling ⇒ genuine change of the coil-response shape.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Sweep A — delta_coil vs dmlim ∈ (0,1) [nominal ncoil=26]
================================================================================
nominal ‖v‖ per surface: q2.0=11.94 q3.0=9.687 q4.0=12.59 q5.0=31.7

(1) ‖delta_coil‖ per surface (— = surface absent):
q\dmlim | 0.1 | 0.3 | 0.5 | 0.7 | 0.9
q=2.0 | 1.140e+01 | 1.172e+01 | 1.107e+01 | 1.100e+01 | 1.114e+01
q=3.0 | 9.389e+00 | 9.560e+00 | 9.619e+00 | 9.319e+00 | 9.279e+00
q=4.0 | 1.242e+01 | 1.251e+01 | 1.457e+01 | 1.305e+01 | 1.251e+01
q=5.0 | 3.857e+01 | 3.349e+01 | — | — | — <- edge

(2) cosine similarity with nominal |<v_nom,v>|/(‖v_nom‖‖v‖) (1.0 = same pattern):
q\dmlim | 0.1 | 0.3 | 0.5 | 0.7 | 0.9
q=2.0 | 0.975948 | 0.996348 | 0.802348 | 0.882046 | 0.938423
q=3.0 | 0.950813 | 0.992477 | 0.602900 | 0.762331 | 0.875203
q=4.0 | 0.879623 | 0.981228 | 0.174318 | 0.464867 | 0.705142
q=5.0 | 0.593958 | 0.942364 | — | — | —

================================================================================
Sweep B — delta_coil vs singfac_min [dmlim=0.2]
================================================================================
nominal ‖v‖ per surface: q2.0=11.94 q3.0=9.687 q4.0=12.59 q5.0=31.7

(1) ‖delta_coil‖ per surface (— = surface absent):
q\sfac | 1e-05 | 0.0001 | 0.001
q=2.0 | 1.155e+01 | 1.155e+01 | 1.155e+01
q=3.0 | 9.469e+00 | 9.469e+00 | 9.469e+00
q=4.0 | 1.245e+01 | 1.245e+01 | 1.245e+01
q=5.0 | 3.509e+01 | 3.510e+01 | 3.509e+01 <- edge

(2) cosine similarity with nominal |<v_nom,v>|/(‖v_nom‖‖v‖) (1.0 = same pattern):
q\sfac | 1e-05 | 0.0001 | 0.001
q=2.0 | 0.988340 | 0.988340 | 0.988340
q=3.0 | 0.976060 | 0.976060 | 0.976060
q=4.0 | 0.940802 | 0.940802 | 0.940802
q=5.0 | 0.818224 | 0.818224 | 0.818224

read: ‖v‖ moves ⇒ magnitude sensitivity; cosine<1 ⇒ the response PATTERN over coil modes rotated.
cosine≈1 with ‖v‖ varying ⇒ pure rescaling; cosine falling ⇒ genuine change of the coil-response shape.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
STRIDE delta_coil: self-convergence vs correlation with RDCON (inner surfaces)
======================================================================
order | (a) self-conv vs ord 8 | (b) corr with RDCON
| q2 q3 | q2 q3
--------------------------------------------------------------
4 | 1.71e-08 3.35e-08 | 0.920 0.574
6 | 0.00e+00 0.00e+00 | 0.920 0.574
8 | 0.00e+00 0.00e+00 | 0.920 0.574

interpretation: if (a) → 0 while (b) stays flat below 1, the STRIDE↔RDCON gap is representational
(weak-form vs strong-form), NOT STRIDE resolution — so refining STRIDE will not close it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Sweep A — delta_coil vs outer truncation dmlim ∈ (0,1) [set_psilim_via_dmlim=true]
|delta_coil| per surface (blank = surface absent at that setting)
========================================================================
q\dmlim | 0.1 | 0.3 | 0.5 | 0.7 | 0.9
q=2.0 | 1.140e+01 | 1.172e+01 | 1.107e+01 | 1.100e+01 | 1.114e+01
q=3.0 | 9.389e+00 | 9.560e+00 | 9.619e+00 | 9.319e+00 | 9.279e+00
q=4.0 | 1.242e+01 | 1.251e+01 | 1.457e+01 | 1.305e+01 | 1.251e+01
q=5.0 | 3.857e+01 | 3.349e+01 | — | — | — <- edge
------------------------------------------------------------------
relative change of |delta_coil| vs dmlim=0.9 (reference):
q=2.0 | 2.36e-02 | 5.24e-02 | 6.31e-03 | 1.19e-02 | 0.00e+00
q=3.0 | 1.18e-02 | 3.02e-02 | 3.66e-02 | 4.33e-03 | 0.00e+00
q=4.0 | 7.45e-03 | 5.73e-04 | 1.64e-01 | 4.32e-02 | 0.00e+00

========================================================================
Sweep B — delta_coil vs rational-surface cutoff singfac_min [dmlim fixed at 0.2]
|delta_coil| per surface (blank = surface absent at that setting)
========================================================================
q\sfac | 1e-05 | 0.0001 | 0.001
q=2.0 | 1.155e+01 | 1.155e+01 | 1.155e+01
q=3.0 | 9.469e+00 | 9.469e+00 | 9.469e+00
q=4.0 | 1.245e+01 | 1.245e+01 | 1.245e+01
q=5.0 | 3.509e+01 | 3.510e+01 | 3.509e+01 <- edge
------------------------------------------------------------------
relative change of |delta_coil| vs sfac=0.001 (reference):
q=2.0 | 8.85e-06 | 1.11e-05 | 0.00e+00
q=3.0 | 1.99e-05 | 1.52e-05 | 0.00e+00
q=4.0 | 8.78e-06 | 2.23e-05 | 0.00e+00
q=5.0 | 5.56e-06 | 3.68e-05 | 0.00e+00

interpretation: flat rows ⇒ delta_coil robust to that knob at that surface; the edge-most
surface is where truncation (dmlim) and approach distance (singfac_min) bite hardest.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
(A) SVD of the delta_coil matrix M = (2·msing × ncoil)
====================================================================================
run | 2msing | ‖M‖2 (σ1) | ‖M‖F | σ_min | cond | u1·u1_nom
--------------------------------------------------------------------------------
nominal | 8 | 2.8991e+01 | 3.7409e+01 | 1.107e+00 | 2.62e+01 | 1.00000
dmlim=0.3 | 8 | 3.0493e+01 | 3.8820e+01 | 1.121e+00 | 2.72e+01 | 0.99752
dmlim=0.42 | 8 | 2.9043e+01 | 3.7463e+01 | 1.107e+00 | 2.62e+01 | 0.99999
dmlim=0.5 | 6 | 1.5731e+01 | 2.0669e+01 | 1.395e+00 | 1.13e+01 | n/a
tol=1.0e-8 | 8 | 2.8991e+01 | 3.7409e+01 | 1.107e+00 | 2.62e+01 | 1.00000
tol=1.0e-10 | 8 | 2.8991e+01 | 3.7409e+01 | 1.107e+00 | 2.62e+01 | 1.00000
tol=1.0e-12 | 8 | 2.8991e+01 | 3.7409e+01 | 1.107e+00 | 2.62e+01 | 1.00000

singular-value spectrum σ_i (first 6):
run | σ1 | σ2 | σ3 | σ4 | σ5 | σ6
nominal | 2.899e+01 | 1.557e+01 | 1.294e+01 | 9.612e+00 | 6.048e+00 | 3.607e+00
dmlim=0.3 | 3.049e+01 | 1.555e+01 | 1.376e+01 | 9.490e+00 | 5.997e+00 | 3.594e+00
dmlim=0.42 | 2.904e+01 | 1.557e+01 | 1.299e+01 | 9.607e+00 | 6.045e+00 | 3.607e+00
dmlim=0.5 | 1.573e+01 | 1.062e+01 | 6.220e+00 | 4.523e+00 | 2.437e+00 | 1.395e+00
tol=1.0e-8 | 2.899e+01 | 1.557e+01 | 1.294e+01 | 9.612e+00 | 6.048e+00 | 3.607e+00
tol=1.0e-10 | 2.899e+01 | 1.557e+01 | 1.294e+01 | 9.612e+00 | 6.048e+00 | 3.607e+00
tol=1.0e-12 | 2.899e+01 | 1.557e+01 | 1.294e+01 | 9.612e+00 | 6.048e+00 | 3.607e+00

====================================================================================
(B) eulerlagrange_tolerance scan — per-surface ‖v‖ and cosine vs nominal
====================================================================================
q\tol | 1e-08 | 1e-10 | 1e-12 (nominal = as-shipped)
(1) ‖delta_coil‖ per surface:
q=2.0 | 1.194e+01 | 1.194e+01 | 1.194e+01
q=3.0 | 9.687e+00 | 9.687e+00 | 9.687e+00
q=4.0 | 1.259e+01 | 1.259e+01 | 1.259e+01
q=5.0 | 3.170e+01 | 3.170e+01 | 3.170e+01
(2) cosine with nominal:
q=2.0 | 1.000000 | 1.000000 | 1.000000
q=3.0 | 1.000000 | 1.000000 | 1.000000
q=4.0 | 1.000000 | 1.000000 | 1.000000
q=5.0 | 1.000000 | 1.000000 | 1.000000

read: (A) stable σ-spectrum + u1·u1_nom≈1 ⇒ matrix conditioning robust; (B) cosine≈1 across
tolerances ⇒ delta_coil independent of the ODE tolerance (as it is of singfac_min).
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
STRIDE delta_coil vs RDCON (galerkin/delta_coil), per-surface correlation of |delta_coil|
variant | q2L q2R q3L q3R q4L q4R q5L q5R | inner outer
strong-form (driven) | 0.90 0.94 0.48 0.67 0.12 0.04 -.38 -.22 | 0.75 -0.11
weak-form projection | 0.49 0.51 0.24 0.38 -.07 0.01 -.36 -.37 | 0.41 -0.20
energy projection | 0.91 0.94 0.48 0.56 0.12 0.70 -.38 -.22 | 0.72 0.06
Loading
Loading