Developer Notes

Git Workflow

This project uses GitFlow:

  • Two permanent branches: main and develop
  • main is updated only at release-ready stages
  • Feature branches should come off develop and merge back with --no-ff

Commit Message Format

Area[.Submodule] - TAG[!] - Imperative summary

Area is a module name such as Equilibrium or ForceFreeStates, or a repository area such as Docs or CI. TAG is one of FEATURE, BUGFIX, PERF, API, DEPRECATION, DOCS (which appear in release notes) or MINOR, REFACTOR, TEST (which do not). Append ! when results move or an interface changes, for example Equilibrium - BUGFIX! - Correct bp0 edge quadrature.

Both vocabularies are closed — do not invent new ones. Pull request titles use the same grammar and are checked in CI; commit subjects follow it by convention rather than enforcement. Pull request bodies carry a release-note block, which is what the changelog is compiled from. The full grammar, the abbreviations to use in prose, and the release-note block are documented in docs/development/naming.md.

Documentation standard

Module reference pages follow a shared Journal of Computational Physics-style structure (governing equations → numerical method → validation figures → API), and every documentation figure is committed together with the script that made it and a provenance stamp. The full policy — page template, figure organization under docs/src/figures/<module>/, provenance, and the regenerate-only-when-depends-change rule — is in docs/DOC_STANDARD.md. The Ballooning and Mercier Local Stability and Inner Layer Module pages are the reference exemplars.

Regression Testing

The regression harness must be run on every pull request before merging into develop. It is the project's primary safeguard for tracking how numerical results evolve across changes, so it is only useful if every PR exercises it. When you open a PR, paste the regression report into the PR thread so reviewers can see what moved (and what did not). If your change touches a quantity that is not yet tracked, add a new regression case — or extend an existing one — in the same PR.

Open problem: pinning grid-sensitive Δ′ robustly

The ideal-MHD Δ′ values pinned in test/runtests_parallel_integration.jl (delta_prime_matrix diagonal) and tracked by the harness are single-point snapshots: one value at one psi_accuracy on one grid. They exist to catch unintended changes, and are explicitly not converged Δ′. The extraction is intrinsically grid-sensitive — a psi_accuracy scan from 2e-3 to 2.5e-4 swings the DIII-D-like dpm[1,1] by roughly 50% (about 6.2 to 9.9), and switching the grid generator moves it again. Any such pin therefore encodes an arbitrary point on a varying curve, and re-pinning is required whenever the grid changes, which weakens it as a regression signal.

A more defensible criterion is to pin the plateau: scan Δ′ across psi_accuracy and the integration-truncation controls, and take the value where the result is stationary — the mode of the resulting distribution rather than any single sample. Where a plateau exists it is a property of the physics rather than of the discretization, so it would survive grid changes and would be a genuine convergence statement.

A psi_accuracy scan on the DIII-D-like SLAYER deck (2e-3 down to 3.125e-5, a 64x range) shows what a plateau search is up against:

psi_accuracyknotsimplieddpm[1,1]dpm[2,2]dpm[3,3]gamma 2/1 (Hz)
2.0e-31726.850-5.783-16.308165.3
1.0e-32056.387-5.873-16.867154.3
5.0e-42528.003-6.105-16.413192.9
2.5e-43075228.567-6.235-16.529206.2
1.25e-43726668.146-6.285-16.649196.2
6.25e-54579168.911-6.477-16.510214.4
3.125e-555912269.514-6.260-16.485228.7

Three things follow. First, convergence is per-surface: the outer surface dpm[3,3] (q=4) does plateau under the auto grid — the last four points agree to about 1% — while dpm[2,2] is marginal and dpm[1,1] (q=2) never settles, still moving ~7% between the two tightest grids. A plateau detector must therefore report per-surface rather than pass/fail for the whole diagonal. (As the ldp scan below shows, q=2 is not inherently unconvergeable — it is the auto grid that prevents it from settling.)

Second, the growth rate is linear in Δ′: gamma/dpm[1,1] is 24.1 to within 0.5% across the whole scan, so a converged Δ′ is both necessary and sufficient for a converged SLAYER growth rate on this surface.

Third — and this is the blocker — the implied column (what implied_knot_count requests after the refined solve) grows monotonically relative to the grid actually used, from 1.7x to 2.2x. The two-pass scheme stops after pass 2, so tightening psi_accuracy moves it further from self-consistency rather than closer, and the warning's advice to "consider tightening psi_accuracy" is counterproductive in this regime.

The same deck on a deterministic ldp grid, which skips the measure-and-re-form step entirely, converges:

mpsidpm[1,1]dpm[2,2]dpm[3,3]gamma 2/1 (Hz)
1285.208-8.666-19.157126.2
2567.097-5.187-15.729171.2
5128.489-6.217-16.517204.4
10248.932-6.298-16.529214.9
20488.914-6.385-16.468214.5

Successive dpm[1,1] increments are +1.889, +1.392, +0.443, -0.018: the last doubling moves both Δ′ and the growth rate by 0.2%. So the q=2 Δ′ is not intrinsically ill-conditioned — it converges to ≈8.91, with gamma ≈214.5 Hz, and dpm[3,3] converges to ≈-16.5 on both grid families, which is what a physical value should do. The non-convergence under the auto grid is an artifact of the generator, not of the Δ′ extraction.

Two consequences. A plateau criterion is implementable today against a fixed ldp grid, without waiting on the auto-grid work. And the auto grid's answers are biased in both directions relative to the converged value: at its default psi_accuracy it gave 6.39 (28% low), at its tightest 9.51 (7% high). Anything pinned on the auto grid should be read with that in mind.

This is not implemented. Doing it properly needs:

  • either a fixed ldp grid (which already converges, see above) or knot refinement iterated to a fixed point (repeat the measure-and-re-form step until implied_knot_count stops exceeding the grid in use), since without it the scan target keeps moving;
  • a scan driver over psi_accuracy × truncation (psihigh, dmlim, qhigh) that records the Δ′ diagonal per configuration;
  • a plateau/mode detector with an explicit stationarity tolerance, plus a defined failure mode for surfaces where no plateau exists (the near-separatrix surfaces are expected to fall in this class);
  • a harness case that pins plateau values and their scan width, replacing the single-point pins.

Until then, treat the pinned diagonal as an order-of-magnitude and sign diagnostic only, and expect to re-pin it whenever the equilibrium grid changes.

Regression Harness: Quick usage guide

Set up an alias for convenience (optional):

alias regress='julia --project=regression-harness regression-harness/regress.jl'

List available cases:

regress --list-cases
Available regression cases:
----------------------------------------------------------------
  diiid_n1                 DIII-D-like equilibrium, n=1, ideal + perturbed equilibrium
                           dir: examples/DIIID-like_ideal_example  (24 quantities)
  solovev_multi_n          Solovev analytical equilibrium, multi-n, ideal stability
                           dir: examples/Solovev_ideal_example_multi_n  (12 quantities)
  solovev_n1               Solovev analytical equilibrium, n=1, ideal stability
                           dir: examples/Solovev_ideal_example  (18 quantities)

Compare two branches/commits:

regress --cases diiid_n1 --refs develop,feature/kinetic-damping
================================================================
Case: diiid_n1 — DIII-D-like equilibrium, n=1, ideal + perturbed equilibrium
================================================================
[ Info: Cached: diiid_n1 @ 0a905a7d (2026-04-06T23:41:50+09:00)
[ Info: Cached: diiid_n1 @ 44b2494f (2026-04-08T18:30:46+09:00)

Regression Report: diiid_n1
==================================================================================================================
Ref 1: develop  @ 0a905a7d (2026-04-06)
Ref 2: feature/kinetic-damping  @ 44b2494f (2026-04-08)
------------------------------------------------------------------------------------------------------------------
Quantity                     develop                  feature/kinetic-damping  Diff                  Status
------------------------------------------------------------------------------------------------------------------
beta_n                       -1.376214e+00            -1.376214e+00            0.0e+00               OK
beta_t                       1.322850e-02             1.322850e-02             0.0e+00               OK
Chirikov parameter           [4 elements]             [4 elements]             0.0e+00               OK
delta prime                  [4 elements]             [4 elements]             0.0e+00               OK
plasma energy Re(ep[1])      -8.809610e-01            -8.809610e-01            0.0e+00               OK
total energy Im(et[1])       6.175834e-05             6.175834e-05             0.0e+00               OK
total energy Re(et[1])       1.199597e+00             1.199597e+00             0.0e+00               OK
vacuum energy Re(ev[1])      2.080558e+00             2.080558e+00             0.0e+00               OK
island half-widths           [4 elements]             [4 elements]             0.0e+00               OK
mpert                        34                       34                       0.0e+00               OK
# singular surfaces          4                        4                        0.0e+00               OK
npert                        1                        1                        0.0e+00               OK
ODE steps (saved)            740                      740                      0.0e+00               OK
ODE steps (total)            1348                     1348                     0.0e+00               OK
PE plasma energy             0.000000e+00             0.000000e+00             0.0e+00               OK
PE total energy              0.000000e+00             0.000000e+00             0.0e+00               OK
pressure profile (checksum)  657ad2329d7b...          657ad2329d7b...          identical             OK
q0                           1.209710e+00             1.209710e+00             0.0e+00               OK
q95                          4.505007e+00             4.505007e+00             0.0e+00               OK
q profile (checksum)         75912afcc351...          75912afcc351...          identical             OK
||resonant flux||            4.523707e+02             4.523707e+02             0.0e+00               OK
Runtime (s)                  50.9s                    52.0s                                          --
singular psi locations       [4 elements]             [4 elements]             0.0e+00               OK
singular q values            [4 elements]             [4 elements]             0.0e+00               OK
==================================================================================================================
Summary: 23 unchanged, 3 missing/N/A

Compare your uncommitted working tree against develop:

regress --cases solovev_n1 --refs develop,local

Track a specific quantity across cached commits:

regress --show et_real --case solovev_n1
History: et_real — solovev_n1
================================================================================
Commit      Date          Value                 Δ from prev           Status
--------------------------------------------------------------------------------
edff6e86    2026-04-02    -4.624928e-01         --                    --
0a905a7d    2026-04-06    -4.624928e-01         0.0e+00               OK
================================================================================

Scan across a range of commits (git-bisect style):

regress --cases solovev_n1 --ref-range develop~10..develop

Other useful flags:

  • --force — re-run even if cached
  • --verbose — print GPEC subprocess output
  • --no-instantiate — skip Pkg.instantiate() (faster if deps are already resolved)

The full flag list, the environment-pinning behaviour that keeps source code the only variable in a comparison, and the exit-status contract are documented in docs/development/regression-harness.md.