Skip to content

Commit 228a8e9

Browse files
committed
Merge branch 'develop' into bugfix/kf-periodic-vpar-spline
2 parents 33925a4 + 987e63f commit 228a8e9

56 files changed

Lines changed: 3669 additions & 3565 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/auto-merge.yaml

Lines changed: 0 additions & 33 deletions
This file was deleted.

.github/workflows/pr-conventions.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,18 @@ jobs:
124124
}
125125
126126
// Assignee and reviewer need a human decision, so comment rather than block.
127+
// A reviewer counts as named whether or not they have acted yet. GitHub drops a
128+
// reviewer from requested_reviewers the moment they submit a review, so the
129+
// submitted reviews are what remember an assignment that has already been acted
130+
// on; without them this nags precisely the pull requests that got reviewed.
131+
const { data: reviews } = await github.rest.pulls.listReviews({
132+
owner, repo, pull_number: pr.number, per_page: 100,
133+
});
134+
const reviewedByOther = reviews.some(r => r.user && r.user.login !== pr.user.login);
135+
127136
const missing = [];
128137
if (!pr.assignees.length) missing.push('an **assignee**');
129-
if (!pr.requested_reviewers.length && !pr.requested_teams.length) {
138+
if (!pr.requested_reviewers.length && !pr.requested_teams.length && !reviewedByOther) {
130139
missing.push('a **reviewer**');
131140
}
132141

benchmarks/benchmark_delta_prime_methods.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,16 @@ function setup_and_run_solovev()
4141
intr.mpert = intr.mhigh - intr.mlow + 1
4242
intr.numpert_total = intr.mpert * intr.npert
4343
metric = FFS.make_metric(equil, intr.mpert)
44-
ffit = FFS.make_matrix(equil, intr, metric)
45-
odet, _, _, _ = FFS.riccati_eulerlagrange_integration(ctrl, equil, ffit, intr)
46-
return ctrl, equil, ffit, intr, odet
44+
mats = FFS.build_matrix_splines(equil, intr, metric)
45+
odet, _, _, _ = FFS.riccati_eulerlagrange_integration(ctrl, equil, mats, intr)
46+
return ctrl, equil, mats, intr, odet
4747
end
4848

4949
println("\n=== compute_delta_prime_from_ca! consistency check ===")
5050
println("Verifies the standalone Δ' formula matches the inline Riccati crossing computation.")
5151
println("Expected error: exactly zero (same formula, same data).\n")
5252

53-
ctrl, equil, ffit, intr, odet = setup_and_run_solovev()
53+
ctrl, equil, mats, intr, odet = setup_and_run_solovev()
5454
msing = intr.msing
5555

5656
# Capture Δ' values set inline by riccati_cross_ideal_singular_surf! during integration

benchmarks/benchmark_riccati_der.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,19 @@ function setup_solovev()
4242
intr.mpert = intr.mhigh - intr.mlow + 1
4343
intr.numpert_total = intr.mpert * intr.npert
4444
metric = FFS.make_metric(equil, intr.mpert)
45-
ffit = FFS.make_matrix(equil, intr, metric)
46-
return ctrl, equil, ffit, intr
45+
mats = FFS.build_matrix_splines(equil, intr, metric)
46+
return ctrl, equil, mats, intr
4747
end
4848

4949
# Evaluate the Riccati RHS explicitly from splines: dS = w†·F̄⁻¹·w - S·Ḡ·S
50-
function riccati_rhs_manual(S, psi, equil, ffit, intr)
50+
function riccati_rhs_manual(S, psi, equil, mats, intr)
5151
N = intr.numpert_total
5252
L = zeros(ComplexF64, N, N)
5353
Kmat = zeros(ComplexF64, N, N)
5454
Gmat = zeros(ComplexF64, N, N)
55-
ffit.fmats_lower(vec(L), psi; hint=ffit._hint)
56-
ffit.kmats(vec(Kmat), psi; hint=ffit._hint)
57-
ffit.gmats(vec(Gmat), psi; hint=ffit._hint)
55+
mats.ideal.F_spline_lower(vec(L), psi; hint=mats._hint)
56+
mats.ideal.K_spline(vec(Kmat), psi; hint=mats._hint)
57+
mats.ideal.G_spline(vec(Gmat), psi; hint=mats._hint)
5858

5959
q = equil.profiles.q_spline(psi)
6060
singfac = vec(1.0 ./ ((intr.mlow:intr.mhigh) .- q .* (intr.nlow:intr.nhigh)'))
@@ -77,7 +77,7 @@ println("\n=== riccati_der! formula verification ===")
7777
println("Verifies riccati_der! output matches manual evaluation of Glasser 2018 Eq. 19.")
7878
println("Test state: Hermitian S (physical constraint). Expected error: ~machine epsilon.\n")
7979

80-
ctrl, equil, ffit, intr = setup_solovev()
80+
ctrl, equil, mats, intr = setup_solovev()
8181
N = intr.numpert_total
8282

8383
odet = FFS.OdeState(N, ctrl.numsteps_init, ctrl.numunorms_init, intr.msing)
@@ -101,15 +101,15 @@ max_err = let max_err = 0.0
101101
S = (A + A') / 2 # Hermitian by construction
102102

103103
# Manual RHS
104-
dS_manual = riccati_rhs_manual(S, psi, equil, ffit, intr)
104+
dS_manual = riccati_rhs_manual(S, psi, equil, mats, intr)
105105

106106
# riccati_der! RHS
107107
u_ric = zeros(ComplexF64, N, N, 2)
108108
du_ric = zeros(ComplexF64, N, N, 2)
109109
u_ric[:, :, 1] .= S
110110
u_ric[:, :, 2] .= Matrix{ComplexF64}(I, N, N)
111111
dummy_chunk = FFS.IntegrationChunk(psi, psi, false, 0, 1)
112-
params = (ctrl, equil, ffit, intr, odet, dummy_chunk)
112+
params = (ctrl, equil, mats, intr, odet, dummy_chunk)
113113
FFS.riccati_der!(du_ric, u_ric, params, psi)
114114
dS_ric = du_ric[:, :, 1]
115115

benchmarks/benchmark_threads.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ function run_ffs(ex; integrator)
3030
intr.mpert = intr.mhigh - intr.mlow + 1
3131
intr.numpert_total = intr.mpert * intr.npert
3232
metric = GeneralizedPerturbedEquilibrium.ForceFreeStates.make_metric(equil, intr.mpert)
33-
ffit = GeneralizedPerturbedEquilibrium.ForceFreeStates.make_matrix(equil, intr, metric)
34-
odet, _, _, _ = GeneralizedPerturbedEquilibrium.ForceFreeStates.eulerlagrange_integration(ctrl, equil, ffit, intr)
35-
vac = GeneralizedPerturbedEquilibrium.ForceFreeStates.free_run(odet, ctrl, equil, ffit, intr)
33+
mats = GeneralizedPerturbedEquilibrium.ForceFreeStates.build_matrix_splines(equil, intr, metric)
34+
odet, _, _, _ = GeneralizedPerturbedEquilibrium.ForceFreeStates.eulerlagrange_integration(ctrl, equil, mats, intr)
35+
vac = GeneralizedPerturbedEquilibrium.ForceFreeStates.free_run(odet, ctrl, equil, mats, intr)
3636
return real(vac.et[1]), intr.numpert_total
3737
end
3838

docs/development/architecture.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ Splines are provided by the external `FastInterpolations` package rather than by
5757
- Solves ideal MHD eigenvalue problem with force-free boundary conditions
5858
- Identifies singular surfaces where ξ·∇ψ = 0
5959
- Key files:
60-
- `ForceFreeStatesStructs.jl` - Core data structures
60+
- `CoreTypes.jl` - Module-wide types (`ForceFreeStatesControl`, `ForceFreeStatesInternal`)
6161
- `Result.jl` - `ForceFreeStatesResult`, the published solve product every downstream stage reads
62-
- `Ode.jl` - ODE solver for Euler-Lagrange equations
63-
- `Sing.jl` - Singular point handling and layer analysis
64-
- `Fourfit.jl` - Fourier fitting routines
62+
- `EulerLagrange.jl` - ODE integration of the Euler-Lagrange equations (`OdeState`, derivative kernel)
63+
- `Surfaces/` - Singular-surface finding, Frobenius asymptotics, and GGJ coefficients
64+
- `Riccati/` - Chunked fundamental-matrix (STRIDE) driver and Δ' boundary-value problem
65+
- `Galerkin/` - RDCON outer-region singular Galerkin Δ' solver
66+
- `Matching/` - Outer↔inner resistive matching (`DeltaPrimeData`, `resonant_match_rpec`)
67+
- `Fourfit.jl` - Fourier fitting routines (`MatrixSplines`)
6568
- `FixedBoundaryStability.jl` - Fixed boundary analysis
6669
- `Free.jl` - Free boundary stability
6770
- Status: Stable, core DCON functionality implemented

docs/development/contributors.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ A short list of who to suggest as a reviewer or assignee, and the GitHub handle
44

55
**This is a suggestion list, not a roster.** It names lead developers only — many more people contribute, and their absence here means nothing. It exists mainly so an AI agent has somewhere sensible to start; a human opening a pull request can simply pick from GitHub's own dropdown and does not need this file.
66

7+
**Focus names Areas, including the ones outside `src/`.** Focus values are the Area names from [`naming.md`](naming.md), so they cover repository maintenance as well as physics modules: `CI` is the workflows under `.github/`, and `Repo` is the cross-cutting plumbing — conventions, templates, the changelog, and the regression harness. Those rows exist so a tooling change has somewhere to go; without them a CI fix has no obvious reviewer and lands unassigned.
8+
79
**Focus is advisory, not ownership.** Nobody is obliged to review a change because they appear in a row, and nobody is barred from one because they do not. There is no `CODEOWNERS` file, so GitHub requests no reviewers automatically — choosing them is the author's job. When it is not obvious who should review, ask rather than guess, and open the pull request as a draft until it has a reviewer and an assignee.
810

911
This file is not published to the documentation site; `docs/development/` sits outside the Documenter source tree.
1012

1113
| Name | Handle | Focus |
1214
|---|---|---|
13-
| Nikolas Logan | `@logan-nc` | KineticForces, PerturbedEquilibrium, Equilibrium, ForceFreeStates |
14-
| Matthew Pharr | `@matt-pharr` | ForceFreeStates, InnerLayer |
15+
| Nikolas Logan | `@logan-nc` | KineticForces, PerturbedEquilibrium, Equilibrium, ForceFreeStates, CI, Repo |
16+
| Matthew Pharr | `@matt-pharr` | ForceFreeStates, InnerLayer, CI, Repo |
1517
| Jake Halpern | `@jhalpern30` | Vacuum, ForceFreeStates, Equilibrium |
1618
| Daniel Burgess | `@d-burg` | Tearing, ForceFreeStates |
1719
| Jaebeom Cho | `@JaeBeom1019` | Vacuum, Equilibrium, ForceFreeStates |

docs/development/hdf5-conventions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Mechanism: writers stay table-driven — each writer keeps a `path => (; long_na
6767

6868
- Complex quantities are stored as the native HDF5.jl compound type (readable by h5py as a compound dtype) — **never split into `*_real`/`*_imag` dataset pairs**. Sole sanctioned exception: `Input/RawInputs/ForcingTerms/amplitude_{real,imag}`, which mirrors the external forcing ingest-file format and keeps pre-existing snapshots replayable.
6969
- `NaN` is the not-computed sentinel in numeric datasets (e.g. auto-derived settings, rootless growth-rate entries).
70+
- A **zero-extent array** is the not-computed sentinel for whole datasets that a given run never produces (e.g. `SingularSurfaces/ca_left`/`ca_right` on kinetic or galerkin-matched runs, the free-boundary energies when `vac_flag=false`, the on-demand derivative stores). Never write unpopulated (`undef`) memory.
7071
- Ragged (variable-length) data uses the flat-plus-`offsets` companion pattern (`offsets[k+1] - offsets[k]` = length of row `k`) rather than HDF5 VLEN types, e.g. `KineticForces/<method>/EnergyIntegrals/` and `Tearing/Diagnostics/*`.
7172

7273
## Back-compatibility policy

docs/development/regression-harness.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,12 @@ regress --cases solovev_n1 --ref-range develop~10..develop
9898
- `--force` — re-run even if cached
9999
- `--verbose` — print GPEC subprocess output
100100
- `--no-instantiate` — skip `Pkg.instantiate()` (faster if deps are already resolved)
101-
<<<<<<< HEAD
102-
103-
GPEC subprocesses run with `-t auto` (all cores) so GPEC's threaded kernels are active; set `GPEC_REGRESS_THREADS=1` to force single-threaded runs. Tracked quantities are thread-count independent, and the count each run actually used is recorded in its environment fingerprint (shown in the report's `env:` lines). Thread count is deliberately not part of the cache key, so `Runtime (s)` rows cached from single-threaded runs are not comparable to threaded ones — re-baseline with `--force` if runtime tracking matters.
104-
=======
105101
- `--no-pin-manifest` — let each ref resolve its own package set (see below)
106102
- `--allow-env-mismatch` — reuse cached results produced in a different environment
107103
- `--fail-on-change` — exit non-zero when any tracked quantity changed
108104

105+
GPEC subprocesses run with `-t auto` (all cores) so GPEC's threaded kernels are active; set `GPEC_REGRESS_THREADS=1` to force single-threaded runs. Tracked quantities are thread-count independent, and the count each run actually used is recorded in its environment fingerprint (shown in the report's `env:` lines). Thread count is deliberately not part of the cache key, so `Runtime (s)` rows cached from single-threaded runs are not comparable to threaded ones — re-baseline with `--force` if runtime tracking matters.
106+
109107
## Making source code the only variable
110108

111109
`Manifest.toml` is untracked, so a worktree checked out at an old commit used to resolve whatever
@@ -140,11 +138,9 @@ than leaving you to infer it from the numbers.
140138
Results cached before environment fingerprinting existed carry no environment and are therefore
141139
re-run once — those are exactly the entries whose provenance cannot be established.
142140

143-
Thread counts are recorded but **not** forced: the harness does not silently change how your runs
144-
execute. If the two refs in a comparison ran under different thread counts, the report flags it.
141+
If the two refs in a comparison ran under different thread counts, the report flags it.
145142

146143
## Exit status
147144

148145
- `0` — every run completed (and, with `--fail-on-change`, nothing changed)
149146
- `1` — a run failed, or a quantity changed under `--fail-on-change`
150-
>>>>>>> performance/regression-harness-worktree-reuse

docs/src/citations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The primary reference for the `ForceFreeStates` module. Derives the Euler-Lagran
3636
> *Physics of Plasmas* **25**, 032507 (2018).
3737
> DOI: [10.1063/1.5007042](https://doi.org/10.1063/1.5007042)
3838
39-
Reformulates the DCON eigenvalue problem as a Riccati matrix ODE, enabling parallel integration across singular surfaces and faster computation. Implemented in `src/ForceFreeStates/Riccati.jl` and enabled via `integrator = "riccati"` in `[ForceFreeStates]`.
39+
Reformulates the DCON eigenvalue problem as a Riccati matrix ODE, enabling parallel integration across singular surfaces and faster computation. Implemented in `src/ForceFreeStates/Riccati/` and enabled via `integrator = "riccati"` in `[ForceFreeStates]`.
4040

4141
---
4242

0 commit comments

Comments
 (0)