From 0f2207e2ce4e3e7a37ef8ef745607637194338f3 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 11 Sep 2026 10:59:23 -0500 Subject: [PATCH 1/5] HDF5Schema - API! - Make the Euler-Lagrange matrix output optional and off by default The ForceFreeStates/EulerLagrangeMatrices group holds the ideal A-K matrices, and on a kinetic run the kinetic set, sampled on the psi grid. Each is mpert x mpert x npsi complex, so the group scales as mpert^2 times npsi and dominates the output: 98 of 104 MB on a DIII-D n=1 Riccati run, and far more at higher mode counts or finer grids. Nothing in the code reads the group back; it is a diagnostic for inspecting the coefficient system. Gate it behind a new write_el_matrices control, default false, which takes that run's gpec.h5 from 104.8 MB to 5.8 MB. Runtime is unchanged (17.52 s vs 17.50 s, min of five warm runs). Set write_el_matrices = true in [ForceFreeStates] to restore the group. Co-Authored-By: Claude Opus 5 --- docs/development/hdf5-conventions.md | 1 + src/ForceFreeStates/CoreTypes.jl | 2 + src/GeneralizedPerturbedEquilibrium.jl | 87 +++++++++++++------------- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/docs/development/hdf5-conventions.md b/docs/development/hdf5-conventions.md index 550702798..46402be6a 100644 --- a/docs/development/hdf5-conventions.md +++ b/docs/development/hdf5-conventions.md @@ -42,6 +42,7 @@ Top level (10 groups): | `Equilibrium/` | Scalars (`beta_N`, `q_axis`, `q_95`, `I_p`, …) plus `Profiles/` (1-D on `psi`: 2piF, mu0p, dVdpsi, q) and `Geometry/` (2-D on `psi`×`theta`: rcoords, offset, nu, jac) | | `ForceFreeStates/` | `Solutions/ForwardIntegration/` (u-solutions), `Solutions/GalerkinIntegration/` (closed ξ profiles in the shared layout, `Match/` diagnostics, the gal surface list, debug-gated `Basis/`), `EulerLagrangeMatrices/{Ideal,Kinetic}`, `FreeBoundaryStability/`, `EdgeScan/` | | `LocalStability/` | Mercier `D_I`, resistive interchange `D_R`, `ballooning_Delta_prime` on `psi`; the ballooning α boundary on `ballooning_psi` | +| `ForceFreeStates/EulerLagrangeMatrices/` | `Ideal/` (A-K) and, on a kinetic run, `Kinetic/`, sampled on `psi`. Written only when `write_el_matrices` is set: the group scales as mpert²·npsi and is otherwise the bulk of the file. | | `SingularSurfaces/` | Per-rational-surface data: `rational_psi`/`rational_q`/`rational_m`/`rational_n`, GGJ coefficients, `Delta_prime_matrix`/`Delta_prime_raw`/`Delta_coil`/`pest3_A`/`pest3_B`/`pest3_Gamma` (Riccati or Galerkin alike), `Kinetic/` | | `PerturbedEquilibrium/` | `ForcingModes/`, `Response/`, `ResponseMatrices/`, `SingularCoupling/`, `Energies/`, control-surface spectra | | `KineticForces/` | `/` (torque/energy profiles, `EnergyIntegrals/`, `KineticMatrices/`); multi-ion runs add `PerSpecies///` with the same per-method layout, summing to the top-level total | diff --git a/src/ForceFreeStates/CoreTypes.jl b/src/ForceFreeStates/CoreTypes.jl index 4b0d924af..3c90b519e 100644 --- a/src/ForceFreeStates/CoreTypes.jl +++ b/src/ForceFreeStates/CoreTypes.jl @@ -147,6 +147,7 @@ gpec.toml. - `diagnose::Bool` - Enable diagnostic output (not yet implemented) - `diagnose_ca::Bool` - Enable asymptotic coefficient diagnostics (not yet implemented) - `write_outputs_to_HDF5::Bool` - Write results to HDF5 format + - `write_el_matrices::Bool` - Write the Euler-Lagrange matrices (`ForceFreeStates/EulerLagrangeMatrices`: the ideal A-K and, on a kinetic run, the kinetic set) sampled on the ψ grid. Each is `mpert × mpert × npsi` complex, so the group scales as mpert²·npsi and dominates the file — 98 of 104 MB on a DIII-D n=1 Riccati run. Nothing downstream reads it back; it is a diagnostic for inspecting the coefficient system. Default `false`. - `HDF5_filename::String` - Name of HDF5 output file - `save_interval::Int` - Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. (Same as `euler_step` in the Fortran) - `force_termination::Bool` - Terminate after force-free states (skip perturbed equilibrium calculations) @@ -184,6 +185,7 @@ gpec.toml. diagnose::Bool = false diagnose_ca::Bool = false write_outputs_to_HDF5::Bool = true + write_el_matrices::Bool = false HDF5_filename::String = "gpec.h5" save_interval::Int = 3 force_termination::Bool = false diff --git a/src/GeneralizedPerturbedEquilibrium.jl b/src/GeneralizedPerturbedEquilibrium.jl index f2ed4ef65..b83857076 100755 --- a/src/GeneralizedPerturbedEquilibrium.jl +++ b/src/GeneralizedPerturbedEquilibrium.jl @@ -85,8 +85,8 @@ using .Equilibrium: PlasmaEquilibrium using .ForcingTerms: RMPField const _DEPRECATED_FFS_KEYS = ("mer_flag", "force_wv_symmetry", "ode_flag", "cyl_flag", "mat_flag", "reform_eq_with_psilim", - "use_riccati", "use_parallel", "parallel_threads", "populate_dense_xi", - "gal_flag") + "use_riccati", "use_parallel", "parallel_threads", "populate_dense_xi", + "gal_flag") const _DEPRECATED_EQUIL_KEYS = ("power_bp", "power_b", "power_r", "power_rc") # Drop deprecated keys from a parsed gpec.toml section so legacy files keep parsing @@ -740,10 +740,10 @@ runs are TOML-driven this cycle: `kinetic_factor > 0` needs the `[KineticForces] and errors here. ```julia -eq = PlasmaEquilibrium("input.geqdsk"; jac_type="hamada") +eq = PlasmaEquilibrium("input.geqdsk"; jac_type="hamada") prob = EulerLagrangeProblem(eq; nn=1, delta_mlow=8, delta_mhigh=8, vac_flag=true) -ffs = solve(prob, Riccati()) -ffs = solve(eq, Riccati(); nn=1, vac_flag=true) # equivalent one-line form +ffs = solve(prob, Riccati()) +ffs = solve(eq, Riccati(); nn=1, vac_flag=true) # equivalent one-line form ``` """ function solve(prob::EulerLagrangeProblem, alg::ForceFreeStates.AbstractIntegrator) @@ -1310,45 +1310,48 @@ function write_outputs_to_HDF5( out_h5["SurfaceGeometries/Wall/y"] = free_energies !== nothing ? free_energies.wall_pts[:, 2] : Float64[] out_h5["SurfaceGeometries/Wall/z"] = free_energies !== nothing ? free_energies.wall_pts[:, 3] : Float64[] - # Write fundamental matrices on the ψ grid - xs = equil.rzphi_xs - npsi = length(xs) - np = result.numpert_total - - # Helper: evaluate a matrix spline on the psi grid → (npsi, np, np) array - function _eval_mat_spline(spline) - arr = zeros(ComplexF64, npsi, np, np) - hint = Ref(1) - for i in 1:npsi - arr[i, :, :] .= reshape(spline(xs[i]; hint=hint), np, np) + # Write the Euler-Lagrange matrices on the ψ grid. Off by default: the group scales as + # mpert²·npsi and is the bulk of the file, and nothing downstream reads it back. + if ctrl.write_el_matrices + xs = equil.rzphi_xs + npsi = length(xs) + np = result.numpert_total + + # Helper: evaluate a matrix spline on the psi grid → (npsi, np, np) array + function _eval_mat_spline(spline) + arr = zeros(ComplexF64, npsi, np, np) + hint = Ref(1) + for i in 1:npsi + arr[i, :, :] .= reshape(spline(xs[i]; hint=hint), np, np) + end + return arr end - return arr - end - elm = "ForceFreeStates/EulerLagrangeMatrices" - out_h5["$elm/psi"] = xs - # Ideal primitive matrices (A, B, C, D, E, H) - out_h5["$elm/Ideal/A"] = _eval_mat_spline(mats.ideal.A_spline) - out_h5["$elm/Ideal/B"] = _eval_mat_spline(mats.ideal.B_spline) - out_h5["$elm/Ideal/C"] = _eval_mat_spline(mats.ideal.C_spline) - out_h5["$elm/Ideal/D"] = _eval_mat_spline(mats.ideal.D_spline_prim) - out_h5["$elm/Ideal/E"] = _eval_mat_spline(mats.ideal.E_spline_prim) - out_h5["$elm/Ideal/H"] = _eval_mat_spline(mats.ideal.H_spline) - - # Ideal derived matrices (F, K, G) - out_h5["$elm/Ideal/F"] = _eval_mat_spline(mats.ideal.F_spline_lower) - out_h5["$elm/Ideal/K"] = _eval_mat_spline(mats.ideal.K_spline) - out_h5["$elm/Ideal/G"] = _eval_mat_spline(mats.ideal.G_spline) - - # Kinetic-modified matrices - kin = mats.kinetic - if kin !== nothing - out_h5["$elm/Kinetic/A"] = _eval_mat_spline(kin.A_spline) - out_h5["$elm/Kinetic/B"] = _eval_mat_spline(kin.B_spline) - out_h5["$elm/Kinetic/C"] = _eval_mat_spline(kin.C_spline) - out_h5["$elm/Kinetic/f0"] = _eval_mat_spline(kin.F0_spline) - out_h5["$elm/Kinetic/K"] = _eval_mat_spline(kin.Kk_spline) - out_h5["$elm/Kinetic/G"] = _eval_mat_spline(kin.G_spline_adj) + elm = "ForceFreeStates/EulerLagrangeMatrices" + out_h5["$elm/psi"] = xs + # Ideal primitive matrices (A, B, C, D, E, H) + out_h5["$elm/Ideal/A"] = _eval_mat_spline(mats.ideal.A_spline) + out_h5["$elm/Ideal/B"] = _eval_mat_spline(mats.ideal.B_spline) + out_h5["$elm/Ideal/C"] = _eval_mat_spline(mats.ideal.C_spline) + out_h5["$elm/Ideal/D"] = _eval_mat_spline(mats.ideal.D_spline_prim) + out_h5["$elm/Ideal/E"] = _eval_mat_spline(mats.ideal.E_spline_prim) + out_h5["$elm/Ideal/H"] = _eval_mat_spline(mats.ideal.H_spline) + + # Ideal derived matrices (F, K, G) + out_h5["$elm/Ideal/F"] = _eval_mat_spline(mats.ideal.F_spline_lower) + out_h5["$elm/Ideal/K"] = _eval_mat_spline(mats.ideal.K_spline) + out_h5["$elm/Ideal/G"] = _eval_mat_spline(mats.ideal.G_spline) + + # Kinetic-modified matrices + kin = mats.kinetic + if kin !== nothing + out_h5["$elm/Kinetic/A"] = _eval_mat_spline(kin.A_spline) + out_h5["$elm/Kinetic/B"] = _eval_mat_spline(kin.B_spline) + out_h5["$elm/Kinetic/C"] = _eval_mat_spline(kin.C_spline) + out_h5["$elm/Kinetic/f0"] = _eval_mat_spline(kin.F0_spline) + out_h5["$elm/Kinetic/K"] = _eval_mat_spline(kin.Kk_spline) + out_h5["$elm/Kinetic/G"] = _eval_mat_spline(kin.G_spline_adj) + end end # Self-describing metadata pass (long_name/units/dims + dimension scales). From 27cea9a406fd8682c102f5331362f726ea355125 Mon Sep 17 00:00:00 2001 From: d-burg Date: Wed, 23 Sep 2026 12:32:39 -0400 Subject: [PATCH 2/5] HDF5Schema - TEST - Cover the opt-in Euler-Lagrange matrix output and document it The schema test now opts into write_el_matrices so the metadata contract still covers the group, and checks the default is off. Move the conventions-table entry into the ForceFreeStates row, flag the opt-in in workflow.md, and trim run-specific figures from the field docstring and write-site comment. Co-Authored-By: Claude Opus 5.5 --- docs/development/hdf5-conventions.md | 3 +-- docs/src/workflow.md | 2 +- src/ForceFreeStates/CoreTypes.jl | 2 +- src/GeneralizedPerturbedEquilibrium.jl | 3 +-- test/runtests_h5_schema.jl | 9 ++++++++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/development/hdf5-conventions.md b/docs/development/hdf5-conventions.md index 46402be6a..5c710f836 100644 --- a/docs/development/hdf5-conventions.md +++ b/docs/development/hdf5-conventions.md @@ -40,9 +40,8 @@ Top level (10 groups): | `Info/` | Run metadata: `git_version`, mode-number ranges (`mpert`, `mlow`, …, `mn_index`), `psilim`, `qlim` | | `Input/` | Rerun snapshot: `gpec_toml_raw`, `RawInputs/{Equilibrium, ForcingTerms, Coils/}` | | `Equilibrium/` | Scalars (`beta_N`, `q_axis`, `q_95`, `I_p`, …) plus `Profiles/` (1-D on `psi`: 2piF, mu0p, dVdpsi, q) and `Geometry/` (2-D on `psi`×`theta`: rcoords, offset, nu, jac) | -| `ForceFreeStates/` | `Solutions/ForwardIntegration/` (u-solutions), `Solutions/GalerkinIntegration/` (closed ξ profiles in the shared layout, `Match/` diagnostics, the gal surface list, debug-gated `Basis/`), `EulerLagrangeMatrices/{Ideal,Kinetic}`, `FreeBoundaryStability/`, `EdgeScan/` | +| `ForceFreeStates/` | `Solutions/ForwardIntegration/` (u-solutions), `Solutions/GalerkinIntegration/` (closed ξ profiles in the shared layout, `Match/` diagnostics, the gal surface list, debug-gated `Basis/`), `write_el_matrices`-gated `EulerLagrangeMatrices/{Ideal,Kinetic}`, `FreeBoundaryStability/`, `EdgeScan/` | | `LocalStability/` | Mercier `D_I`, resistive interchange `D_R`, `ballooning_Delta_prime` on `psi`; the ballooning α boundary on `ballooning_psi` | -| `ForceFreeStates/EulerLagrangeMatrices/` | `Ideal/` (A-K) and, on a kinetic run, `Kinetic/`, sampled on `psi`. Written only when `write_el_matrices` is set: the group scales as mpert²·npsi and is otherwise the bulk of the file. | | `SingularSurfaces/` | Per-rational-surface data: `rational_psi`/`rational_q`/`rational_m`/`rational_n`, GGJ coefficients, `Delta_prime_matrix`/`Delta_prime_raw`/`Delta_coil`/`pest3_A`/`pest3_B`/`pest3_Gamma` (Riccati or Galerkin alike), `Kinetic/` | | `PerturbedEquilibrium/` | `ForcingModes/`, `Response/`, `ResponseMatrices/`, `SingularCoupling/`, `Energies/`, control-surface spectra | | `KineticForces/` | `/` (torque/energy profiles, `EnergyIntegrals/`, `KineticMatrices/`); multi-ion runs add `PerSpecies///` with the same per-method layout, summing to the top-level total | diff --git a/docs/src/workflow.md b/docs/src/workflow.md index 05b037451..b20b5bf1e 100644 --- a/docs/src/workflow.md +++ b/docs/src/workflow.md @@ -177,7 +177,7 @@ All results are written to a single HDF5 file (default: `gpec.h5`). The top-leve | `Info/` | Run metadata: git version, mode-number ranges, ψ limit | | `Input/` | Self-contained rerun snapshot: merged TOML blob, raw equilibrium/forcing/coil inputs | | `Equilibrium/` | Equilibrium scalars (`beta_N`, `q_axis`, `q_95`, …), 1-D profiles (`Profiles/`), 2-D geometry (`Geometry/`) | -| `ForceFreeStates/` | Stability solve: `Solutions/{ForwardIntegration,GalerkinIntegration}`, `EulerLagrangeMatrices/`, `FreeBoundaryStability/`, `EdgeScan/` | +| `ForceFreeStates/` | Stability solve: `Solutions/{ForwardIntegration,GalerkinIntegration}`, `EulerLagrangeMatrices/` (opt-in: `write_el_matrices = true`), `FreeBoundaryStability/`, `EdgeScan/` | | `LocalStability/` | Mercier D_I, resistive interchange D_R, ballooning Δ' profiles | | `SingularSurfaces/` | Per-rational-surface data: ψ_s, q, m/n, GGJ coefficients, Δ'/PEST-3 matching matrices, kinetic surfaces (`Kinetic/`) | | `PerturbedEquilibrium/` | Plasma response: `ForcingModes/`, `Response/`, `ResponseMatrices/`, `SingularCoupling/`, `Energies/` | diff --git a/src/ForceFreeStates/CoreTypes.jl b/src/ForceFreeStates/CoreTypes.jl index 3c90b519e..86b286076 100644 --- a/src/ForceFreeStates/CoreTypes.jl +++ b/src/ForceFreeStates/CoreTypes.jl @@ -147,7 +147,7 @@ gpec.toml. - `diagnose::Bool` - Enable diagnostic output (not yet implemented) - `diagnose_ca::Bool` - Enable asymptotic coefficient diagnostics (not yet implemented) - `write_outputs_to_HDF5::Bool` - Write results to HDF5 format - - `write_el_matrices::Bool` - Write the Euler-Lagrange matrices (`ForceFreeStates/EulerLagrangeMatrices`: the ideal A-K and, on a kinetic run, the kinetic set) sampled on the ψ grid. Each is `mpert × mpert × npsi` complex, so the group scales as mpert²·npsi and dominates the file — 98 of 104 MB on a DIII-D n=1 Riccati run. Nothing downstream reads it back; it is a diagnostic for inspecting the coefficient system. Default `false`. + - `write_el_matrices::Bool` - Write the Euler-Lagrange matrices (`ForceFreeStates/EulerLagrangeMatrices`: the ideal A-K and, on a kinetic run, the kinetic set) sampled on the ψ grid. Each is `mpert × mpert × npsi` complex, so the group scales as mpert²·npsi and dominates the file size. Default `false`. - `HDF5_filename::String` - Name of HDF5 output file - `save_interval::Int` - Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. (Same as `euler_step` in the Fortran) - `force_termination::Bool` - Terminate after force-free states (skip perturbed equilibrium calculations) diff --git a/src/GeneralizedPerturbedEquilibrium.jl b/src/GeneralizedPerturbedEquilibrium.jl index b83857076..d6c63d25f 100755 --- a/src/GeneralizedPerturbedEquilibrium.jl +++ b/src/GeneralizedPerturbedEquilibrium.jl @@ -1310,8 +1310,7 @@ function write_outputs_to_HDF5( out_h5["SurfaceGeometries/Wall/y"] = free_energies !== nothing ? free_energies.wall_pts[:, 2] : Float64[] out_h5["SurfaceGeometries/Wall/z"] = free_energies !== nothing ? free_energies.wall_pts[:, 3] : Float64[] - # Write the Euler-Lagrange matrices on the ψ grid. Off by default: the group scales as - # mpert²·npsi and is the bulk of the file, and nothing downstream reads it back. + # Euler-Lagrange matrices on the ψ grid; opt-in since the group scales as mpert²·npsi. if ctrl.write_el_matrices xs = equil.rzphi_xs npsi = length(xs) diff --git a/test/runtests_h5_schema.jl b/test/runtests_h5_schema.jl index c743d6a7d..1155e68ce 100644 --- a/test/runtests_h5_schema.jl +++ b/test/runtests_h5_schema.jl @@ -50,6 +50,10 @@ include("h5_metadata_check.jl") @test !_group_name_ok("SingularSurfaces", "kinetic") end +@testset "gpec.h5 schema: Euler-Lagrange matrices are opt-in" begin + @test !GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl().write_el_matrices +end + @testset "gpec.h5 schema naming" begin template_dir = joinpath(@__DIR__, "test_data", "regression_solovev_ideal_example") @@ -58,7 +62,8 @@ end cp(joinpath(template_dir, name), joinpath(run_dir, name)) end toml_path = joinpath(run_dir, "gpec.toml") - write(toml_path, replace(read(toml_path, String), "write_outputs_to_HDF5 = false" => "write_outputs_to_HDF5 = true")) + # Opt into the Euler-Lagrange matrices so the metadata pass below covers that group too. + write(toml_path, replace(read(toml_path, String), "write_outputs_to_HDF5 = false" => "write_outputs_to_HDF5 = true\nwrite_el_matrices = true")) GeneralizedPerturbedEquilibrium.main([run_dir]) h5_path = joinpath(run_dir, "gpec.h5") @@ -74,6 +79,8 @@ end @test !haskey(h5, "FreeBoundaryStability") @test !haskey(h5, "EdgeScan") + @test haskey(h5, "ForceFreeStates/EulerLagrangeMatrices/Ideal/A") + # Inputs live only under Input/; spot-check the rerun-critical paths. @test haskey(h5, "Input/gpec_toml_raw") @test haskey(h5, "Info/git_version") From c98827a8192e811400ed7464e21fc0ff1ec37c85 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 25 Sep 2026 11:04:45 -0400 Subject: [PATCH 3/5] HDF5Schema - TEST - Check every Euler-Lagrange dataset shape and that a default run omits the group Co-Authored-By: Claude Opus 5.5 --- test/runtests_fullruns.jl | 2 ++ test/runtests_h5_schema.jl | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/runtests_fullruns.jl b/test/runtests_fullruns.jl index a76515bf5..da9eb2974 100644 --- a/test/runtests_fullruns.jl +++ b/test/runtests_fullruns.jl @@ -32,6 +32,8 @@ using HDF5 # emit deterministic zero-extent sentinels, not uninitialized memory. @test isempty(read(h5["SingularSurfaces/ca_left"])) @test isempty(read(h5["SingularSurfaces/ca_right"])) + # The Euler-Lagrange matrices are opt-in; a default deck writes no group. + @test !haskey(h5, "ForceFreeStates/EulerLagrangeMatrices") end rm(joinpath(ex3, "gpec.h5"); force=true) true diff --git a/test/runtests_h5_schema.jl b/test/runtests_h5_schema.jl index 1155e68ce..aa1265a1c 100644 --- a/test/runtests_h5_schema.jl +++ b/test/runtests_h5_schema.jl @@ -63,7 +63,7 @@ end end toml_path = joinpath(run_dir, "gpec.toml") # Opt into the Euler-Lagrange matrices so the metadata pass below covers that group too. - write(toml_path, replace(read(toml_path, String), "write_outputs_to_HDF5 = false" => "write_outputs_to_HDF5 = true\nwrite_el_matrices = true")) + write(toml_path, replace(read(toml_path, String), "write_outputs_to_HDF5 = false" => "write_el_matrices = true\nwrite_outputs_to_HDF5 = true")) GeneralizedPerturbedEquilibrium.main([run_dir]) h5_path = joinpath(run_dir, "gpec.h5") @@ -79,7 +79,17 @@ end @test !haskey(h5, "FreeBoundaryStability") @test !haskey(h5, "EdgeScan") - @test haskey(h5, "ForceFreeStates/EulerLagrangeMatrices/Ideal/A") + # Every ideal matrix is written as (npsi, np, np) on the ψ grid; an ideal run writes no kinetic set. + elm = "ForceFreeStates/EulerLagrangeMatrices" + @test haskey(h5, "$elm/psi") + npsi = length(read(h5["$elm/psi"])) + np = size(h5["$elm/Ideal/A"], 2) + @test np >= 1 + for name in ("A", "B", "C", "D", "E", "H", "F", "K", "G") + @test haskey(h5, "$elm/Ideal/$name") + @test size(h5["$elm/Ideal/$name"]) == (npsi, np, np) + end + @test !haskey(h5, "$elm/Kinetic") # Inputs live only under Input/; spot-check the rerun-critical paths. @test haskey(h5, "Input/gpec_toml_raw") From aaf71341d0c6cae9c107942479ddedf4319f52e5 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 25 Sep 2026 11:04:45 -0400 Subject: [PATCH 4/5] ForceFreeStates - DOCS - List write_el_matrices in the stability config reference Co-Authored-By: Claude Opus 5.5 --- docs/src/stability.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/stability.md b/docs/src/stability.md index c61ca716e..2dba52cfa 100644 --- a/docs/src/stability.md +++ b/docs/src/stability.md @@ -280,6 +280,7 @@ local_stability_flag = false # scan Mercier D_I, resistive D_R, and ballooning # Output verbose = true write_outputs_to_HDF5 = true +write_el_matrices = false # also write ForceFreeStates/EulerLagrangeMatrices (mpert² × npsi per matrix) ``` The number of Julia threads is controlled at startup via `-t N` or the `JULIA_NUM_THREADS` From ff74482e90f9bcb6ca0522682ca79a3ae03a894e Mon Sep 17 00:00:00 2001 From: d-burg Date: Tue, 6 Oct 2026 16:49:30 -0400 Subject: [PATCH 5/5] Test - TEST - Reduce the Euler-Lagrange matrix output tests to the opt-in and the default Co-Authored-By: Claude Opus 5.5 --- test/runtests_h5_schema.jl | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/test/runtests_h5_schema.jl b/test/runtests_h5_schema.jl index aa1265a1c..2a9967906 100644 --- a/test/runtests_h5_schema.jl +++ b/test/runtests_h5_schema.jl @@ -50,10 +50,6 @@ include("h5_metadata_check.jl") @test !_group_name_ok("SingularSurfaces", "kinetic") end -@testset "gpec.h5 schema: Euler-Lagrange matrices are opt-in" begin - @test !GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl().write_el_matrices -end - @testset "gpec.h5 schema naming" begin template_dir = joinpath(@__DIR__, "test_data", "regression_solovev_ideal_example") @@ -79,17 +75,8 @@ end @test !haskey(h5, "FreeBoundaryStability") @test !haskey(h5, "EdgeScan") - # Every ideal matrix is written as (npsi, np, np) on the ψ grid; an ideal run writes no kinetic set. - elm = "ForceFreeStates/EulerLagrangeMatrices" - @test haskey(h5, "$elm/psi") - npsi = length(read(h5["$elm/psi"])) - np = size(h5["$elm/Ideal/A"], 2) - @test np >= 1 - for name in ("A", "B", "C", "D", "E", "H", "F", "K", "G") - @test haskey(h5, "$elm/Ideal/$name") - @test size(h5["$elm/Ideal/$name"]) == (npsi, np, np) - end - @test !haskey(h5, "$elm/Kinetic") + # The opt-in above writes the Euler-Lagrange matrix group. + @test haskey(h5, "ForceFreeStates/EulerLagrangeMatrices/Ideal/A") # Inputs live only under Input/; spot-check the rerun-critical paths. @test haskey(h5, "Input/gpec_toml_raw")