Skip to content
Open
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
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ Roster, the recommended review pipeline (physics fidelity → readability → pe
### Plotting
Spectrum-plot (`seriestype=:steppre` / `step_series`) and figure-saving conventions are in **[`docs/development/plotting.md`](docs/development/plotting.md)** — read before writing any plotting code.

**Adding a plot.** Every plot in `src/Analysis/` takes a result in memory or a `gpec.h5` path and is registered in `src/Analysis/Registry.jl`, which `available_plots` and the REPL summaries read:
- Read the data through one `_x_data(src)` method per source (the result's fields, and the same datasets from the file), and draw from the returned NamedTuple only, so both sources go through one plotting body.
- Start the public `plot_x(src; ...)` with `_require(plot_x, src)`, which throws the registry's reason when the source lacks the data; never draw a "No data" placeholder or return `nothing`. Composite summaries skip the panels they cannot draw (`_can`) and throw only when none can.
- Call `register!(plot_x, sources, needs, description)` after the definition. `needs` names capabilities; a new one gets a `capabilities` predicate for each result type that can hold it, a dataset in `_H5_CAPABILITIES`, and a `REASONS` sentence saying what to change in the run.
- `test/runtests_analysis_plots.jl` checks every registered plot from both sources automatically; add an extractor to its list when the plot reads new data.

**REPL summaries.** `src/Display.jl` gives every result type a one-line `show` and a short `text/plain` summary. A new result type gets both, plus an entry in `_DISPLAY_FIELDS`; a new field on a summarized type is classified there as shown or omitted, which `test/display_check.jl` enforces.

### Subagent Consultations

When delegating to specialized agents (julia-performance-optimizer, fast-interpolations-optimizer, fortran-physics-reviewer, clean-code-reviewer, etc.), every prompt **must include an explicit budget** because runaway agents silently consume the user's daily token quota. A single consultation that explores instead of editing has been measured at 167 tool calls / 55 minutes / no return — that is a session-killer. Defaults:
Expand Down
9 changes: 8 additions & 1 deletion docs/src/analysis.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Analysis

`Analysis` holds the plotting and post-processing functions for GPEC results, grouped by the stage they read; each works on the in-memory result or on `gpec.h5`.
`Analysis` holds the plotting and post-processing functions for GPEC results, grouped by the stage they read. Each registered plot takes the in-memory result or the path of the `gpec.h5` it was saved to; `available_plots` lists them with what each source can draw (see [Plotting a result](api.md#Plotting-a-result)).

## Submodules

Expand All @@ -18,6 +18,13 @@ Figure conventions for spectrum plots are in `docs/development/plotting.md` of t
GeneralizedPerturbedEquilibrium.Analysis
```

## Plot registry

```@autodocs
Modules = [GeneralizedPerturbedEquilibrium.Analysis]
Pages = ["Registry.jl"]
```

## ForceFreeStates

```@autodocs
Expand Down
21 changes: 21 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,31 @@ ForceFreeStatesResult Riccati, ideal closure
Δ′_ii (ψ_N) 2/1: 0.1781, 3/1: -215.7
Solution not computed (Riccati stores no ξ profiles)
Wall ConformalWall, standoff = 0.2415 a
Plots 11 of 13 available (see available_plots)
```

The fields themselves are documented in each type's docstring.

## Plotting a result

Every plot in [`Analysis`](analysis.md) takes the result in memory or the path of the `gpec.h5` it was saved to. `available_plots` lists the plots for a result, a path, or the named tuple `main` returns, and for each one the run cannot draw, what to change:

```julia-repl
julia> available_plots(ffs)
Plots for ForceFreeStatesResult (riccati): 11 of 13 available
✓ ForceFreeStates.plot_ffs_summary the stability panels the run supplies
✗ ForceFreeStates.plot_mode_displacement needs stored ξ profiles: use the Forward integrator or a matched Galerkin solve (Riccati and unmatched Galerkin store none)
✓ ForceFreeStates.plot_fixed_boundary_stability_criterion critical determinant against ψ_N
⋮
```

A plot the source cannot draw throws an `ArgumentError` with the same reason, and a summary leaves out the panels it cannot draw:

```julia
Analysis.ForceFreeStates.plot_ffs_summary(ffs) # the result in memory
Analysis.ForceFreeStates.plot_ffs_summary("gpec.h5") # or the file to which it was saved
```

## Saving and rerunning

`save_to_hdf5(target, result)` saves any stage result; `target` is a path, which opens or creates the file, or an open HDF5 file or group, which stays owned by the caller.
Expand Down
11 changes: 5 additions & 6 deletions examples/Solovev_ideal_example/analyze_example.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ using GeneralizedPerturbedEquilibrium, Plots
using GeneralizedPerturbedEquilibrium: Analysis
isinteractive() ? plotlyjs() : gr()

h5path = joinpath(@__DIR__, "gpec.h5")
# Run the deck and plot its results in memory; every plot also takes the path of the gpec.h5 the run wrote.
ret = GeneralizedPerturbedEquilibrium.main([@__DIR__])
display(available_plots(ret))

# Summary plots
p_eq = Analysis.Equilibrium.plot_equilibrium_summary(h5path)
p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(h5path)
p_eq = Analysis.Equilibrium.plot_equilibrium_summary(ret.equil)
p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(ret.ffs)

display(p_eq);
Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png"));
display(p_ffs);
Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png"));
display(p_pe);
Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png"));
7 changes: 7 additions & 0 deletions src/Analysis/Analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

Post-processing and visualization utilities for GPEC simulation outputs.

Every registered plot takes a result in memory or the path of the `gpec.h5` it was saved to, and throws an
`ArgumentError` naming what to change in the run when that source lacks the data it draws. `available_plots(src)`
lists the plots for a result, a path, or the named tuple `main` returns.

## Submodules

- `ForceFreeStates`: Plotting functions for ForceFreeStates (DCON-style ideal MHD stability) results
Expand All @@ -14,11 +18,14 @@ Post-processing and visualization utilities for GPEC simulation outputs.
"""
module Analysis

include("Registry.jl")
include("ForceFreeStates.jl")
include("Equilibrium.jl")
include("CoilForcing.jl")
include("PerturbedEquilibrium.jl")
include("PerturbedEquilibriumModes.jl")
include("ErrorFields.jl")

export available_plots

end # module Analysis
29 changes: 29 additions & 0 deletions src/Analysis/CoilForcing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ module CoilForcing

using Plots
import ...Utilities.FourierTransforms: FourierTransform, inverse
import ...PerturbedEquilibrium: PerturbedEquilibriumState
using ..Analysis: register!, _require

"""
plot_coil_geometry_3d(coil_sets; save_path=nothing, kwargs...)
Expand Down Expand Up @@ -453,6 +455,33 @@ function plot_surface_3d(equil, modes, n::Int; coil_sets=nothing, mtheta::Int=18
psi=psi, colorbar_title=colorbar_title, save_path=save_path, kwargs...)
end

"""
plot_coil_geometry_3d(pe::PerturbedEquilibriumState; kwargs...)
plot_coil_geometry_rz(pe::PerturbedEquilibriumState; kwargs...)
plot_mode_spectrum(pe::PerturbedEquilibriumState; kwargs...)

The coil plots of the coil sets a perturbed equilibrium was forced by: the (R, Z) cross-sections over its plasma
boundary, and the forcing spectrum across its poloidal mode range.
"""
function plot_coil_geometry_3d(pe::PerturbedEquilibriumState; kwargs...)
_require(plot_coil_geometry_3d, pe)
return plot_coil_geometry_3d(pe.coil_sets; kwargs...)
end

function plot_coil_geometry_rz(pe::PerturbedEquilibriumState; kwargs...)
_require(plot_coil_geometry_rz, pe)
return plot_coil_geometry_rz(pe.coil_sets; equil=pe.response.ffs.equil, kwargs...)
end

function plot_mode_spectrum(pe::PerturbedEquilibriumState; kwargs...)
_require(plot_mode_spectrum, pe)
return plot_mode_spectrum(pe.forcing_modes; mlow=pe.response.ffs.mlow, mhigh=pe.response.ffs.mhigh, kwargs...)
end

register!(plot_coil_geometry_3d, (PerturbedEquilibriumState,), (:coil_sets,), "3D coil conductors")
register!(plot_coil_geometry_rz, (PerturbedEquilibriumState,), (:coil_sets,), "coil cross-sections over the plasma boundary")
register!(plot_mode_spectrum, (PerturbedEquilibriumState,), (:forcing_modes,), "|Φ_x(m, n)| forcing spectrum")

export plot_coil_geometry_3d, plot_coil_geometry_rz
export plot_bn_contour, plot_mode_spectrum
export plot_surface_3d, control_surface_scalar
Expand Down
Loading
Loading