Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ce6d0a6
ForceFreeStates - NEW FEATURE - Dual Riccati reformulation of EL ODE …
logan-nc Feb 28, 2026
0385e7f
ForceFreeStates - NEW FEATURE - Parallel FM integration + Δ' output
logan-nc Feb 28, 2026
1d2a863
ForceFreeStates - IMPROVEMENT - Fix Δ' computation and add Riccati/pa…
logan-nc Mar 1, 2026
11f394b
ForceFreeStates - CLEANUP - Correct explanation for why standard path…
logan-nc Mar 1, 2026
0ca20e2
ForceFreeStates - IMPROVEMENT - Off-diagonal Δ' column + parallel FM …
logan-nc Mar 1, 2026
5a7b756
ForceFreeStates - NEW FEATURE - STRIDE global BVP inter-surface Δ' ma…
logan-nc Mar 1, 2026
af7f359
ForceFreeStates - NEW FEATURE - Bidirectional parallel FM integration…
logan-nc Mar 2, 2026
9961fbd
ForceFreeStates - NEW FEATURE - Thread-scaling benchmark script
logan-nc Mar 8, 2026
7bb3942
ForceFreeStates - BUG FIX - Address Claude code review of perf/riccati
logan-nc Mar 8, 2026
88448fc
ForceFreeStates - NEW FEATURE - Sanity-check benchmarks for riccati_d…
logan-nc Mar 8, 2026
86b60a2
ForceFreeStates - CLEANUP - Clarify Riccati integration strategy docs…
logan-nc Mar 9, 2026
cb4c2bf
ForceFreeStates - IMPROVEMENT - Refactor runtests_riccati.jl: shared …
logan-nc Mar 9, 2026
c7dfa41
ForceFreeStates - IMPROVEMENT - Remove dead parallel_threads field; a…
logan-nc Mar 9, 2026
2eb3c26
ForceFreeStates - MERGE - Merge origin/develop (GPEC rename) into per…
logan-nc Mar 9, 2026
2f494c9
ForceFreeStates - CLEANUP - Update JPEC→GeneralizedPerturbedEquilibri…
logan-nc Mar 9, 2026
142a79c
ForceFreeStates - NEW FEATURE - Add stability analysis documentation …
logan-nc Mar 9, 2026
1515591
ForceFreeStates - BUG FIX - Fix CI failures (Random stdlib + docs mar…
logan-nc Mar 9, 2026
725a527
ForceFreeStates - IMPROVEMENT - Thread safety, psilim guard, consiste…
logan-nc Mar 9, 2026
a63dc69
Merge develop into perf/riccati
matt-pharr Apr 8, 2026
6431392
TESTING - FIX - fixed failing tests post merge
matt-pharr Apr 8, 2026
0d72584
TESTING - FIX - Re-add comments to gpec.toml accidentally removed in …
matt-pharr Apr 8, 2026
dc2b44b
BENCH - NEW - integration paths benchmark script
matt-pharr Apr 8, 2026
8dc4dc4
Merge remote-tracking branch 'origin/develop' into perf/riccati
matt-pharr Apr 8, 2026
290cfc5
EQUIL - NEW FEATURE - TJ analytic model (tj_run inverse + tj_run_direct)
d-burg Apr 18, 2026
5be4c98
ForceFreeStates - NEW FEATURE - Parallel FM Δ' BVP with inter-surface…
d-burg Apr 18, 2026
97a6826
BENCH - NEW - TJ pole-approach scans, regression case, and unit tests
d-burg Apr 18, 2026
d67cabd
CLEANUP - Drop unused deps, fix stale comments and docs
d-burg Apr 18, 2026
b9c177e
CLEANUP - Drop brittle Fortran/TJ source line-number citations from c…
d-burg Apr 18, 2026
57db4e7
Merge origin/develop into perf/riccati
d-burg Apr 18, 2026
5d5b8ee
ForceFreeStates - NEW FEATURE - Decouple edge-dW scan from integratio…
d-burg Apr 18, 2026
685a92a
EXAMPLES - BUG FIX - Remove stale kin_flag/con_flag from LAR/TJ scan …
d-burg Apr 18, 2026
defcec8
CI - BUG FIX - Restore Random stdlib dep and refresh test regression …
d-burg Apr 19, 2026
1dfc3ae
CI - BUG FIX - Loosen Solovev Δ'(surface 2) regression test
d-burg Apr 19, 2026
c6c845f
CI - BUG FIX - Handle maxthreadid() and decouple DIIID cross-path test
d-burg Apr 19, 2026
2fdae82
SLAYER - NEW FEATURE - Add SLAYERParameters and dimensional builder (…
d-burg Apr 19, 2026
e0c7397
SLAYER - NEW FEATURE - Add Fitzpatrick Riccati inner-layer Δ solver (…
d-burg Apr 19, 2026
61d844a
Dispersion - NEW FEATURE - Add SurfaceCoupling residual building bloc…
d-burg Apr 19, 2026
9d089be
Dispersion - CLEANUP - Remove leftover Newton root-finder files
d-burg Apr 19, 2026
71d69c5
Dispersion - NEW FEATURE - Add MultiSurfaceCoupling determinant resid…
d-burg Apr 19, 2026
dba61ca
Dispersion - NEW FEATURE - Brute-force Q-plane scan + find_growth_rat…
d-burg Apr 19, 2026
6cd5a5c
Dispersion - NEW FEATURE - AMR scan + triangulation-based growth-rate…
d-burg Apr 19, 2026
7a0f507
SLAYER - NEW FEATURE - KineticProfiles + LayerInputs builders (PR 7/9)
d-burg Apr 19, 2026
b170b49
SLAYER - NEW FEATURE - SLAYERRunner orchestration module (PR 8/9)
d-burg Apr 19, 2026
43c3b1d
SLAYER - NEW FEATURE - main() integration + Solovev example + regress…
d-burg Apr 19, 2026
8bfe74f
REFACTOR - Group tearing-mode modules under src/Tearing/ umbrella
d-burg Apr 19, 2026
3f82b7d
GGJ - NEW FEATURE - Per-surface E, F, G, H, K, M coefficients + build…
d-burg Apr 19, 2026
3ddc6f5
ForceFreeStates - IMPROVEMENT - Tighten defaults + use_parallel for d…
d-burg Apr 21, 2026
0a91a46
Utilities - NEW FEATURE - NeoclassicalResistivity module + switchable…
d-burg Apr 21, 2026
ede6fe2
ForceFreeStates - NEW FEATURE - Expose full 2m×2m D' matrix via delta…
d-burg Apr 21, 2026
ded86fe
InnerLayer - BUG FIX - InnerLayerResponse{tearing,interchange} + fix …
d-burg Apr 21, 2026
6410cd7
Dispersion - NEW FEATURE - CoupledFull 2m×2m det(D'−D(γ)) dispersion …
d-burg Apr 21, 2026
2172518
Dispersion - NEW FEATURE - CoupledFull 2m×2m det(D'−D(γ)) dispersion …
d-burg Apr 22, 2026
f3fe71a
Dispersion - IMPROVEMENT - CoupledFortranMatch inner_kwargs pass-through
d-burg Apr 22, 2026
ec00846
SLAYER - BUG FIX - Align Julia coupled-SLAYER dispersion with Fortran
d-burg Apr 23, 2026
2573553
Dispersion / GGJ - PERFORMANCE - Parallel amr_scan + preallocated Gal…
d-burg Apr 23, 2026
dd39a49
GGJ - BUG FIX - Remove erroneous Δ_crit offset from 4m×4m Pletzer-Dew…
d-burg Apr 24, 2026
568e431
WIP - SLAYER + GGJ - BUG FIX - Equilibrium-derived per-surface dr_val…
d-burg Apr 25, 2026
cce935a
SLAYER - NEW FEATURE - Adaptive pole_threshold = |mean(Δ)| for find_g…
d-burg Apr 26, 2026
c45a634
ForceFreeStates - BUG FIX - Wire ctrl.parallel_threads into BVP path;…
d-burg Apr 28, 2026
48f433d
ForceFreeStates - PERFORMANCE - parallel_threads default 1 → 2 (≈20% …
d-burg Apr 28, 2026
c49d86b
SLAYER - PERFORMANCE - Convert Riccati ODE to scalar state (~30-40% f…
d-burg Apr 28, 2026
9ec12a0
SLAYER - DOCS - Document solver-AMR-topology coupling in Riccati docs…
d-burg Apr 28, 2026
adf27aa
SLAYER - PERFORMANCE - Pre-compute x-independent Riccati constants (~…
d-burg Apr 28, 2026
e7ce1c1
Dispersion - NEW FEATURE - amr_scan: snapshot_callback + max_cells_ac…
d-burg Apr 28, 2026
0fb5d75
Dispersion - NEW FEATURE - convergence_amr_resolution.jl: γ vs (nre0,…
d-burg Apr 28, 2026
5fd3a83
Dispersion - NEW FEATURE - multi_box_amr_scan: stripe scan with pre-s…
d-burg Apr 28, 2026
8bcd7f2
Dispersion - NEW FEATURE - find_growth_rates: spurious-root detection…
d-burg Apr 29, 2026
e97225c
Dispersion - IMPROVEMENT - find_growth_rates: polyline-walk concavity…
d-burg Apr 29, 2026
4c6fbe3
Dispersion - REFACTOR - find_growth_rates: drop :density flag, keep :…
d-burg Apr 29, 2026
33d791f
Dispersion - REFACTOR - find_growth_rates: remove dead angle_threshol…
d-burg Apr 29, 2026
af76269
Tearing.Runner - IMPROVEMENT - multi-box stripe scan + median-based p…
d-burg Apr 29, 2026
fda6597
EQUIL - BUG FIX - find_separatrix_crossing tolerates fixed-boundary e…
d-burg May 1, 2026
528062f
[WIP] Tearing.Dispersion - chooser_overrides warn-not-discard policy
d-burg May 9, 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
6 changes: 6 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ version = "0.1.0"
[deps]
AdaptiveArrayPools = "4f381ef7-9af0-4cbe-99d4-cf36d7b0f233"
Contour = "d38c429a-6771-53c6-b99e-75d170b6e991"
DelaunayTriangulation = "927a84f5-c5f4-47a5-9785-b46e178433df"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
FastInterpolations = "9ea80cae-fc13-4c00-8066-6eaedb12f34b"
Expand All @@ -23,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 All @@ -34,9 +37,11 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[compat]
AdaptiveArrayPools = "0.3.5"
Contour = "0.6.3"
DelaunayTriangulation = "1.6.6"
DelimitedFiles = "1.9.1"
DiffEqCallbacks = "4.9.0"
Documenter = "1.14.1"
DoubleFloats = "1.6.2"
FFTW = "1.9.0"
FastGaussQuadrature = "1.1.0"
FastInterpolations = "0.4"
Expand All @@ -50,6 +55,7 @@ PlotlyJS = "0.18.17"
Plots = "1.40.15"
Printf = "1"
QuadGK = "2.11.3"
Random = "1"
Roots = "2.2.13"
SparseArrays = "1"
SpecialFunctions = "2.5.1"
Expand Down
95 changes: 95 additions & 0 deletions benchmarks/benchmark_delta_prime_methods.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Sanity check: compute_delta_prime_from_ca! vs inline Δ' from riccati_cross_ideal_singular_surf!
#
# riccati_cross_ideal_singular_surf! computes Δ' inline at each singular surface crossing
# using the diagonal formula (no Gaussian reduction permutation):
# Δ'[s] = (ca_r[ipert_res, ipert_res, 2, s] - ca_l[ipert_res, ipert_res, 2, s]) / (4π²·ψ₀)
#
# compute_delta_prime_from_ca! applies the identical formula post-hoc from the stored
# ca_l/ca_r arrays. Since both operate on the same data with the same formula, results
# should match to floating-point precision (not just approximately — exactly).
#
# This verifies that compute_delta_prime_from_ca! is a correct standalone implementation
# of the Δ' formula that can be used for testing or alternative integration drivers.
#
# Usage (from JPEC_main root):
# julia --project=. benchmarks/benchmark_delta_prime_methods.jl

using LinearAlgebra, Printf, TOML
using GeneralizedPerturbedEquilibrium

const FFS = GeneralizedPerturbedEquilibrium.ForceFreeStates

function setup_and_run_solovev()
ex = joinpath(@__DIR__, "..", "test", "test_data", "regression_solovev_ideal_example")
inputs = TOML.parsefile(joinpath(ex, "gpec.toml"))
inputs["ForceFreeStates"]["verbose"] = false
inputs["ForceFreeStates"]["use_riccati"] = true
intr = FFS.ForceFreeStatesInternal(; dir_path=ex)
ctrl = FFS.ForceFreeStatesControl(;
(Symbol(k) => v for (k, v) in inputs["ForceFreeStates"])...)
eq_config = GeneralizedPerturbedEquilibrium.Equilibrium.EquilibriumConfig(inputs["Equilibrium"], ex)
equil = GeneralizedPerturbedEquilibrium.Equilibrium.setup_equilibrium(eq_config)
intr.wall_settings = GeneralizedPerturbedEquilibrium.Vacuum.WallShapeSettings(;
(Symbol(k) => v for (k, v) in inputs["Wall"])...)
FFS.sing_lim!(intr, ctrl, equil)
intr.nlow = ctrl.nn_low; intr.nhigh = ctrl.nn_high; intr.npert = 1
FFS.sing_find!(intr, equil)
intr.mlow = min(intr.nlow * equil.params.qmin, 0) - 4 - ctrl.delta_mlow
intr.mhigh = trunc(Int, intr.nhigh * equil.params.qmax) + ctrl.delta_mhigh
intr.mpert = intr.mhigh - intr.mlow + 1
intr.mband = intr.mpert - 1
intr.numpert_total = intr.mpert * intr.npert
metric = FFS.make_metric(equil; mband=intr.mband, fft_flag=ctrl.fft_flag)
ffit = FFS.make_matrix(equil, intr, metric)
odet = FFS.riccati_eulerlagrange_integration(ctrl, equil, ffit, intr)
return ctrl, equil, ffit, intr, odet
end

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

ctrl, equil, ffit, intr, odet = setup_and_run_solovev()
msing = intr.msing

# Capture Δ' values set inline by riccati_cross_ideal_singular_surf! during integration
delta_prime_inline = [copy(intr.sing[s].delta_prime) for s in 1:msing]

# Now call compute_delta_prime_from_ca! — it reads the same ca_l/ca_r arrays and
# overwrites intr.sing[s].delta_prime using the identical diagonal formula
FFS.compute_delta_prime_from_ca!(odet, intr, equil)

println(" N=$(intr.numpert_total) modes, $msing singular surfaces\n")
@printf(" %6s %4s %4s %22s %22s %12s\n",
"Surf", "m", "n", "Δ' (inline)", "Δ' (from_ca)", "abs diff")
println(" " * "-"^76)

max_absdiff = let max_absdiff = 0.0
for s in 1:msing
sing = intr.sing[s]
dp_from_ca = intr.sing[s].delta_prime
for i in eachindex(delta_prime_inline[s])
dp_il = delta_prime_inline[s][i]
dp_fc = dp_from_ca[i]
absdiff = abs(dp_fc - dp_il)
max_absdiff = max(max_absdiff, absdiff)
@printf(" %6d %4d %4d %22.6f%+.6fi %22.6f%+.6fi %12.4e\n",
s, sing.m[i], sing.n[i],
real(dp_il), imag(dp_il),
real(dp_fc), imag(dp_fc),
absdiff)
end
end
max_absdiff
end

println()
if max_absdiff == 0.0
println("PASSED — Δ' values are bit-for-bit identical (max abs diff = 0.0)")
elseif max_absdiff < 1e-14
@printf("PASSED — max abs diff = %.2e (floating-point rounding only)\n", max_absdiff)
else
@printf("FAILED — max abs diff = %.2e (expected exact agreement)\n", max_absdiff)
exit(1)
end
println()
148 changes: 148 additions & 0 deletions benchmarks/benchmark_integration_paths.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env julia
"""
Benchmark the three integration paths (standard, riccati, parallel) on Solovev and DIIID examples.
Runs in a single Julia process to avoid measuring compilation overhead.
Produces accuracy and performance tables similar to PR #178.

Usage:
julia --project=. -t4 benchmarks/benchmark_integration_paths.jl
"""

using GeneralizedPerturbedEquilibrium
using HDF5, Printf, TOML

const PROJECT_ROOT = abspath(joinpath(@__DIR__, ".."))

struct BenchResult
example::String
path::String
et1::Float64
nsteps::Int
runtime::Float64
end

function run_one(example_dir::String, path_name::String; num_warm::Int=2)
abs_dir = abspath(example_dir)
gpec_toml = joinpath(abs_dir, "gpec.toml")

# Read and modify config
config = TOML.parsefile(gpec_toml)
ffs = get(config, "ForceFreeStates", Dict{String,Any}())
if path_name == "standard"
ffs["use_riccati"] = false
ffs["use_parallel"] = false
elseif path_name == "riccati"
ffs["use_riccati"] = true
ffs["use_parallel"] = false
elseif path_name == "parallel"
ffs["use_riccati"] = false
ffs["use_parallel"] = true
end
config["ForceFreeStates"] = ffs

# Write modified config in-place, restore after
original_toml = read(gpec_toml, String)

try
open(gpec_toml, "w") do f
TOML.print(f, config)
end

# JIT warmup
println(" [$path_name] JIT warmup...")
GeneralizedPerturbedEquilibrium.main([abs_dir])

# Timed runs
runtimes = Float64[]
for i in 1:num_warm
println(" [$path_name] Warm run $i/$num_warm...")
t0 = time()
GeneralizedPerturbedEquilibrium.main([abs_dir])
push!(runtimes, time() - t0)
@printf(" %.2f s\n", runtimes[end])
end

# Read results
gpec_h5 = joinpath(abs_dir, "gpec.h5")
et1, nsteps = h5open(gpec_h5, "r") do h5
et = read(h5["vacuum/et"])
ns = read(h5["integration/nstep"])
(real(et[1]), ns)
end

avg_t = sum(runtimes) / length(runtimes)
return BenchResult(basename(example_dir), path_name, et1, nsteps, avg_t)
finally
write(gpec_toml, original_toml)
end
end

function main()
examples = [
joinpath(PROJECT_ROOT, "examples", "Solovev_ideal_example"),
joinpath(PROJECT_ROOT, "examples", "DIIID-like_ideal_example"),
]
paths = ["standard", "riccati", "parallel"]

results = BenchResult[]
for ex in examples
println("\n" * "="^60)
println("Example: $(basename(ex))")
println("="^60)
for p in paths
r = run_one(ex, p)
push!(results, r)
@printf(" → et[1]=%.5f steps=%d time=%.2fs\n", r.et1, r.nsteps, r.runtime)
end
end

# Print Accuracy table
println("\n\n## Accuracy\n")
println("| Example | Path | et[1] | Error vs std |")
println("|---------|------|-------|--------------|")
for ex in unique(r.example for r in results)
group = filter(r -> r.example == ex, results)
std_et1 = group[1].et1
N = 0
toml_path = joinpath(PROJECT_ROOT, "examples", ex, "gpec.toml")
if isfile(toml_path)
cfg = TOML.parsefile(toml_path)
ffs_cfg = get(cfg, "ForceFreeStates", Dict())
mlow = get(ffs_cfg, "delta_mlow", 8)
mhigh = get(ffs_cfg, "delta_mhigh", 8)
N = mlow + mhigh
end
for r in group
err_str = r.path == "standard" ? "—" : @sprintf("%.3f%%", 100*abs(r.et1 - std_et1)/abs(std_et1))
short_ex = startswith(r.example, "Solovev") ? "Solovev N=$N" : "DIIID N=$N"
@printf("| %s | %s | %.5f | %s |\n", short_ex, r.path, r.et1, err_str)
end
end

# Print Performance table
nthreads = Threads.nthreads()
println("\n## Performance ($nthreads threads)\n")
println("| Example | Path | Time | Speedup |")
println("|---------|------|------|---------|")
for ex in unique(r.example for r in results)
group = filter(r -> r.example == ex, results)
std_time = group[1].runtime
N = 0
toml_path = joinpath(PROJECT_ROOT, "examples", ex, "gpec.toml")
if isfile(toml_path)
cfg = TOML.parsefile(toml_path)
ffs_cfg = get(cfg, "ForceFreeStates", Dict())
mlow = get(ffs_cfg, "delta_mlow", 8)
mhigh = get(ffs_cfg, "delta_mhigh", 8)
N = mlow + mhigh
end
for r in group
speedup = std_time / r.runtime
short_ex = startswith(r.example, "Solovev") ? "Solovev N=$N" : "DIIID N=$N"
speedup_str = r.path == "standard" ? "1.00×" : @sprintf("**%.2f×**", speedup)
@printf("| %s | %s | %.2fs | %s |\n", short_ex, r.path, r.runtime, speedup_str)
end
end
end

main()
Loading