Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 7 additions & 6 deletions examples/DIIID-like_gal_resistive_pe_example/gpec.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# DIII-D-like H-mode DRIVEN resistive example: gal Δ′ + inner-layer matching, with DIII-D
# C-coil forcing (n=1 cosine phasing) staged for PerturbedEquilibrium. The PE stage currently
# warns and skips: it requires the free-boundary δW, which the Galerkin formalism does not
# yet produce — the gal-matched resistive ξ and penetrated field are carried on the result
# for when gal-side δW lands. Same equilibrium and Galerkin settings as the sibling
# C-coil forcing (n=1 cosine phasing) staged for PerturbedEquilibrium. The response and the
# singular coupling are switched off: they require the free-boundary δW, which the Galerkin
# formalism does not yet produce, and PE refuses them on a Galerkin or matched result. The
# gal-matched resistive ξ and penetrated field are carried on the result for when gal-side
# δW lands. Same equilibrium and Galerkin settings as the sibling
# DIIID-like_gal_resistive_example; differences start at [ForcingTerms].
[Equilibrium]
eq_filename = "TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file
Expand Down Expand Up @@ -94,8 +95,8 @@ currents = [1000.0, 500.0, -500.0, -1000.0, -500.0, 500.0] # n=1 cosine phasing
[PerturbedEquilibrium]
fixed_boundary = false # Use fixed boundary conditions
output_eigenmodes = true # Output eigenmode fields as b-fields
compute_response = true # Compute plasma response to forcing
compute_singular_coupling = true # Compute singular layer coupling metrics
compute_response = false # Compute plasma response to forcing
compute_singular_coupling = false # Compute singular layer coupling metrics
verbose = true # Enable verbose logging
write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5
regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled)
2 changes: 2 additions & 0 deletions regression-harness/cases/gal_resistive_pe.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# fed into PerturbedEquilibrium with DIII-D C-coil forcing. Tracks the finite
# per-rational-surface singular-coupling scalars (island half-widths, Chirikov
# overlap, resonant flux, penetrated field, Δ′).
# Expected N/A: the example switches the PE response and coupling off until Galerkin and
# matched results publish the free-boundary δW, so none of these datasets are written yet.
# Each [quantities.*] block names an HDF5 path in the run output, how to extract it,
# and the noise floor below which a difference is treated as zero.
[case]
Expand Down
8 changes: 3 additions & 5 deletions src/ForceFreeStates/Result.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ once this has been built.
Optional fields are `Union{Nothing,T}` and follow a presence-equals-capability rule: a
consumer that needs one gates on [`require`](@ref) (or [`require_solution`](@ref) for the
ξ profiles) and warns-and-skips rather than erroring, so a result from an integrator that
cannot supply a given product still flows through the pipeline.
cannot supply a given product still flows through the pipeline. PerturbedEquilibrium refuses
Galerkin and `:matched` results instead, since they publish no `wp` or `free_boundary` yet.

A jump condition at the rational surfaces is always applied before a final result is built,
so bpen and closure are always present.
Expand Down Expand Up @@ -126,10 +127,7 @@ that produced `result`. Warns naming the calculation being skipped otherwise.
"""
function require(result::ForceFreeStatesResult, field::Symbol, calc::AbstractString)
getfield(result, field) === nothing || return true
why = result.closure === :matched && field in (:wp, :free_boundary) ?
"a :matched result carries no δW (the resistive δW is not implemented)" :
"`$field` was not produced by the $(result.integrator) integrator"
@warn "Skipping $calc: $why"
@warn "Skipping $calc: `$field` was not produced by the $(result.integrator) integrator"
return false
end

Expand Down
5 changes: 3 additions & 2 deletions src/GeneralizedPerturbedEquilibrium.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1008,8 +1008,9 @@ Compute the plasma response to the external field `rmp` on top of a force-free-s
arguments are `PerturbedEquilibrium.PerturbedEquilibriumControl` fields.

Products the producing integrator could not supply gate the corresponding calculation: the
step warns and is skipped rather than erroring, so a Riccati- or Galerkin-fed result still
flows through.
step warns and is skipped rather than erroring, so a Riccati-fed result still flows through.
Galerkin and `:matched` results publish no free-boundary δW yet and are refused when the
response or the singular coupling is requested.

`coil_sets` injects already-built coil geometry (the gpec.h5 replay path), bypassing the
geometry read. A `runtimes` collector, when given, receives the forcing-mode materialization's wall-clock seconds as `"forcing_terms" => dt`.
Expand Down
10 changes: 9 additions & 1 deletion src/PerturbedEquilibrium/PerturbedEquilibrium.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Computes plasma response to external forcing and calculates singular layer
coupling metrics. Every ForceFreeStates input — the equilibrium, the mode space, the
metric and matrix fits, the free-boundary energies and the ξ solution — is read off `ffs`.
Products the producing integrator could not supply gate the corresponding calculation:
the step warns and is skipped instead of erroring.
the step warns and is skipped instead of erroring. Galerkin and `:matched` results publish
no free-boundary δW yet, so asking for the response or the coupling on one is an error.

## Arguments

Expand Down Expand Up @@ -78,6 +79,13 @@ function compute_perturbed_equilibrium(
mats = ffs.mats
mtheta_vacuum = ffs.control.mtheta_vacuum

# Galerkin and matched results publish no plasma edge response wp, so neither step could run.
if (ctrl.compute_response || ctrl.compute_singular_coupling) && ffs.free_boundary === nothing &&
(ffs.integrator === :galerkin || ffs.closure === :matched)
error("PerturbedEquilibrium needs the free-boundary δW, which a $(ffs.integrator) result with $(ffs.closure) closure " *
"does not publish yet; set compute_response = false and compute_singular_coupling = false, or use a forward solve")
end

# Step 0: Initialize mode arrays for convenient indexing
initialize_mode_arrays!(intr, ffs)

Expand Down
9 changes: 9 additions & 0 deletions test/runtests_solve_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ using TOML
@test size(dp.matrix) == (msing, msing)
@test dp.matrix ≈ FFS.pest3_decompose(dp.raw).Δ
@test dp.A !== nothing # the parity blocks the galerkin solve persists

# With no free-boundary δW, PE refuses the response rather than returning an empty state.
forcing = joinpath(@__DIR__, "..", "examples", "Solovev_ideal_example", "forcing.dat")
@test_throws ErrorException perturbed_equilibrium(api, RMPField(forcing); compute_response=true, write_outputs_to_HDF5=false)
end
end

Expand Down Expand Up @@ -270,6 +274,11 @@ using TOML
@test matched.match !== nothing && matched.bpen == matched.match.bpen
@test matched.wp === nothing && matched.free_boundary === nothing
@test matched.solution === nothing # no Galerkin basis to build a matched ξ from
# PE refuses the response and coupling on it, but still materializes the forcing alone.
forcing = joinpath(@__DIR__, "..", "examples", "Solovev_ideal_example", "forcing.dat")
@test_throws ErrorException perturbed_equilibrium(matched, RMPField(forcing); compute_singular_coupling=true, write_outputs_to_HDF5=false)
idle = perturbed_equilibrium(matched, RMPField(forcing); compute_response=false, compute_singular_coupling=false, write_outputs_to_HDF5=false)
@test idle isa GPEC.PerturbedEquilibrium.PerturbedEquilibriumState
# A matched result is a new solution, not an outer solve to match again.
@test_throws ErrorException MatchProblem(matched; ideal=true)
end
Expand Down
Loading