ForcingTerms Module
The ForcingTerms module specifies external magnetic field perturbations for the perturbed equilibrium calculation. It supports reading from ASCII or HDF5 files, as well as computing perturbations directly from coil Biot-Savart calculations.
Normalization conventions
Two normalization tags are supported, specified inside the file (not in gpec.toml):
| Tag | Quantity | Units | Notes |
|---|---|---|---|
"normal_field_T" | Fourier modes of B·n̂ | Tesla | Default — most intuitive for users |
"sfl_flux_Wb" | Fourier modes of R×(BR ∂Z/∂θ − BZ ∂R/∂θ) | T·m² | 2π-angle SFL flux (user input) |
Angle convention
Julia's ForcingTerms use the unit-norm angle convention (θnorm ∈ [0, 1], ζnorm ∈ [0, 1]) internally, matching Fortran GPEC and the Equilibrium/ForceFreeStates modules.
The coil Biot-Savart pipeline produces mode amplitudes directly equal to Fortran's Phi_x:
Julia output ≡ Fortran Phi_xThe formula is Phi_x(θ_norm, ζ_norm) = 2π × R × (B_R ∂Z/∂θ_norm − B_Z ∂R/∂θ_norm), where the 2π comes from the toroidal Jacobian ∂r/∂ζnorm = 2π·R·êφ.
Users who provide forcing files may use the more intuitive 2π-angle Fourier amplitudes (e.g. the amplitude of cos(mθ − nζ) where θ ∈ [0, 2π]). These are tagged "sfl_flux_Wb" and multiplied by (2π)² internally to reach unit-norm convention.
Automatic conversion
Files tagged normal_field_T are automatically converted to unit-norm Phi_x convention during loading using the plasma boundary geometry. The conversion accounts for the R-variation of the Jacobian and is a mode-mixing operation.
Files tagged sfl_flux_Wb (2π-angle SFL flux) are scaled by (2π)² to reach unit-norm.
The coil Biot-Savart code (forcing_data_format = "coil") always produces unit-norm amplitudes directly — no conversion is applied.
ASCII file format
# normalization: normal_field_T
# Format: n m real(amplitude) imag(amplitude)
1 6 1.0e-4 0.0
1 7 5.0e-5 2.0e-5- The
# normalization: <tag>line is optional; default isnormal_field_T. - Lines starting with
#are comments and are ignored by the data parser. - Columns: toroidal mode n, poloidal mode m, real part of amplitude, imaginary part (optional, default 0).
- Amplitudes are in Tesla (for
normal_field_T) or T·m² (forsfl_flux_Wb).
HDF5 file format
Required datasets:
n: integer array of toroidal mode numbersm: integer array of poloidal mode numbersamplitude_real: float array of real partsamplitude_imag: float array of imaginary parts (optional)
Optional root attribute:
normalization: string, either"normal_field_T"(default) or"sfl_flux_Wb"
Example (Julia):
using HDF5
h5open("forcing.h5", "w") do f
f["n"] = [1, 1, 1]
f["m"] = [5, 6, 7]
f["amplitude_real"] = [1e-4, 2e-4, 1e-4]
f["amplitude_imag"] = [0.0, 0.0, 0.0]
HDF5.attrs(f)["normalization"] = "normal_field_T"
endCoil Biot-Savart format
Set forcing_data_format = "coil" in [ForcingTerms] TOML to compute the field directly from coil geometry. Output is always in sfl_flux_Wb convention.
The coil pipeline:
sample_boundary_grid: evaluate plasma boundary geometry at (mtheta × nzeta) gridcompute_biot_savart_boundary!: compute B at all grid points via Biot-Savart lawproject_normal_flux!: compute Phix = 2π×R×(BR ∂Z/∂θnorm − BZ ∂R/∂θ_norm)fourier_decompose_bn: Fourier decompose to get bmn in unit-norm (= Phi_x) convention
The toroidal angle for each observation point at SFL grid coordinate (θi, ζj) is:
φ_phys(i, j) = -helicity × (2π × ζ_j + ν(ψ, θ_i))where helicity = sign(Bt) × sign(Ip) and ν(ψ, θ) is the SFL toroidal coordinate correction stored in equil.rzphi_nu. This matches Fortran's phi = -helicity*(twopi*czeta + crzphi_f(3)). For DIII-D (Bt < 0, Ip > 0 → helicity = -1), ν ranges from about -3.4 to +3.4 radians at the boundary — omitting it completely scrambles the Fourier mode decomposition.
Coil geometry sources
Each [[ForcingTerms.coil_set]] block supplies one coil set from one of several sources, selected by source. Shifts and tilts (shiftx/y/z, tiltx/y/z, n_tilt, ...) apply to every source, so analytic coils can be perturbed exactly like file-based ones.
# Legacy ASCII .dat file (unchanged; resolves to {dat_dir}/{machine}_{name}.dat)
[[ForcingTerms.coil_set]]
name = "il"
currents = [1e4, 1e4, 1e4, 1e4, 1e4, 1e4]
# Modern HDF5 file; `name` selects one set from a multi-set file (omit if it holds one)
[[ForcingTerms.coil_set]]
source = "file"
h5_file = "/path/to/my_coils.h5"
name = "il"
currents = [1e4, 1e4, 1e4, 1e4, 1e4, 1e4]
# Analytic PF hoop — a horizontal circle; tilt breaks axisymmetry to give n ≠ 0 forcing
[[ForcingTerms.coil_set]]
source = "pf_hoop"
radius = 1.8
height = 0.9
currents = [5e3]
tiltx = [2.0]
# Analytic window-pane (picture-frame) array — corner mode: two opposite [R, Z] corners
[[ForcingTerms.coil_set]]
source = "window_pane"
ncoil_gen = 8
rz_corners = [[2.2, -0.6], [2.2, 0.6]]
gap_fraction = 0.15
currents = [1e3, -1e3, 1e3, -1e3, 1e3, -1e3, 1e3, -1e3]
# Analytic window-pane — standoff mode: placed by physical distance off the plasma surface.
# The frame center sits `standoff` m along the outward surface normal at cylindrical
# `poloidal_angle` (degrees); legs span `poloidal_length` m, tilted by `poloidal_tilt`
# (degrees, 0 = tangential). Requires an equilibrium (the main forcing pipeline supplies it).
[[ForcingTerms.coil_set]]
source = "window_pane"
ncoil_gen = 2
standoff = 0.25
poloidal_angle = 0.0
poloidal_length = 1.2
poloidal_tilt = 0.0
gap_fraction = 0.15
currents = [1e4, -1e4]
# Analytic helical array on a circular cross-section torus (pitch = m/n).
# theta_lo/theta_hi (poloidal extent, degrees) default to a full 0–360 turn.
[[ForcingTerms.coil_set]]
source = "helical"
R0 = 1.7
a = 0.6
m_hel = 4
n_hel = 1
n_coils = 4
currents = [1e3, 1e3, 1e3, 1e3]HDF5 coil geometry format
A coil HDF5 file stores one subgroup per coil set under a parent group (default coils), so a single file can hold many sets and the reader ignores any unrelated content elsewhere:
<group>/<set_name>/x Float64 ncoil×s×nsec Cartesian metres
<group>/<set_name>/y Float64 ncoil×s×nsec
<group>/<set_name>/z Float64 ncoil×s×nsec
<group>/<set_name>/currents Float64 ncoil optional
<group>/<set_name> attr nw Float64 winding multiplier, default 1.0Use convert_coil_dat_to_h5 / convert_coil_h5_to_dat to migrate legacy files, and read_coil_h5 / write_coil_h5 for direct I/O.
Rerun snapshot
When a coil run writes gpec.h5, the coil geometry actually used (after shifts/tilts and with currents) is captured under input/raw_inputs/coils/. The run can then be replayed with main_from_h5 even if the original .dat/.h5 files are gone:
--coil-source forcing-modes(default) recomputes the field from the stored TOML coil config (needs the original geometry files to be present).--coil-source coilsrecomputes the field from the geometry stored ingpec.h5, so no original files are required — and the equilibrium may be overridden on replay.
Comparison with Fortran GPEC
| Quantity | Julia convention | Fortran GPEC convention |
|---|---|---|
| Angle range | θnorm ∈ [0, 1], ζnorm ∈ [0, 1] | θnorm ∈ [0, 1], ζnorm ∈ [0, 1] |
| Coil forcing output | unit-norm = 2π×R×(BR ∂Z/∂θnorm − BZ ∂R/∂θnorm) | Phi_x [Wb] |
| Ratio | Julia output ≡ Fortran Phi_x (directly comparable) | — |
API Reference
GeneralizedPerturbedEquilibrium.ForcingTerms.BoundaryGrid — Type
BoundaryGridPre-computed plasma boundary grid for evaluating coil fields.
Fields
mtheta,nzeta: grid dimensionsR,Z: cylindrical coordinates[mtheta](same for all ζ, axisymmetric)phi_grid: base toroidal angle grid[nzeta]in radians =-helicity × 2π × j/nzetaphi_offset: per-θ toroidal angle correction[mtheta]from SFL coordinates:ν(ψ, θ)scaled by-helicity, so the physical toroidal angle at(i, j)isphi_grid[j] + phi_offset[i]. Matches Fortran'sphi = -helicity*(2π*ζ + dphi(ψ,θ)). Zero for axisymmetric equilibria on-axis; non-zero off-axis due to SFL coordinate transform (Hamada/SFL θ ≠ geometric θ introduces a toroidal offset).dR_dtheta,dZ_dtheta: poloidal derivatives w.r.t. unit-norm angle θnorm ∈ [0,1] `dRdtheta[i] = dR/dθnorm = 2π × dR/dθphys`
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilConfig — Type
CoilConfigTop-level coil configuration from the [ForcingTerms] TOML section when forcing_data_format = "coil".
Fields
machine: machine name prefix for .dat files (e.g. "d3d")dat_dir: directory containing .dat files; defaults to bundledcoil_geometries/mtheta_coil: poloidal grid resolution for boundary field evaluation (default: 480)nzeta_coil: toroidal grid resolution; 0 = auto (32 × n)coil_sets: parsed[[ForcingTerms.coil_set]]blocks
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilConfig — Method
CoilConfig(ft_ctrl::ForcingTermsControl)Construct a CoilConfig from a ForcingTermsControl populated from TOML.
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilSet — Type
CoilSetGeometry and current data for a single coil set (one .dat file).
Fields
name: coil set identifier (e.g. "il", "iu")ncoil: number of independent conductorss: sub-systems (strand groups) per conductornw: winding multiplier (turns per conductor element)nsec: number of Cartesian points per strandx,y,z: conductor coordinates[ncoil, s, nsec]in meters (Cartesian)currents: current per conductor[ncoil]in Amperes
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilSetConfig — Type
CoilSetConfigPer-coil-set configuration from a [[ForcingTerms.coil_set]] TOML block.
A coil set comes from one of several sources, selected by source:
""or"file": read a geometry file —.dat(legacy ASCII) or.h5(modern), chosen by extension. The file path comes fromdat_file/h5_fileor themachine+nameconvention."pf_hoop": an analytic horizontal circular loop (radius,height)."window_pane": an analytic rectangular picture-frame array, placed EITHER by explicit corners (rz_corners) OR by physical standoff from the plasma surface (standoff,poloidal_angle,poloidal_length,poloidal_tilt); shared toroidal layout viancoil_gen,gap_fraction,phi0."helical": an analytic helical array on a circular cross-section torus (R0,a,m_hel,n_hel,theta_lo,theta_hi,n_coils,gap_fraction,phi0,offset).
Shifts/tilts apply to every source (the analytic geometry is built first, then transformed).
Fields
name: coil set name; resolves to{dat_dir}/{machine}_{name}.dat, or selects a set by name from a multi-set.h5filesource: coil source discriminator (see above); empty/"file"reads a geometry filedat_file: explicit path to .dat file (overridesmachine+nameconvention)h5_file: explicit path to .h5 file (multi-set;nameselects which set, else the sole set)currents: current [A] per conductor[ncoil]; shorter arrays pad with zerosshiftx,shifty,shiftz: per-conductor translation [m][ncoil]tiltx,tilty,tiltz: per-conductor tilt in degrees (or meters iftilt_in_meters)xnom,ynom,znom: explicit rotation center [m]; defaults to arc-length-weighted center of massn_tilt: toroidal mode number for tilt/shift modulation; -1 means inherit run's ntilt_in_meters: interpret tilt as displacement [m] instead of angle [degrees]
Analytic-source parameters (used only for the matching source):
radius,height: PF hoop circle radius and Z height [m]ncoil_gen: number of generated coils (window-pane / helical arrays)rz_corners: window-pane cross-section as two opposite[R, Z]corners in m (corner mode)gap_fraction: toroidal gap between adjacent coils as a fraction of their spacingphi0: toroidal angle offset of the first generated coil [rad]R0,a: helical torus major radius and minor (circle) radius [m]m_hel,n_hel: helical poloidal/toroidal mode numbers setting the pitchtheta_lo,theta_hi: helical poloidal extent in degrees; a full turn whentheta_hi-theta_lo == 360n_coils: number of helical coils in the arrayoffset: helical coil radial offset from the torus surface [m]nsec_gen: optional override of the generated point count per coil
Window-pane standoff placement (alternative to rz_corners; needs an equilibrium):
standoff: outward-normal distance from the control surface to the frame center [m]poloidal_angle: cylindrical poloidal angle of the frame center, in degreespoloidal_length: poloidal leg span (corner-to-corner) along the surface tangent [m]poloidal_tilt: extra tilt of the legs in degrees; 0 means tangential to the surface
GeneralizedPerturbedEquilibrium.ForcingTerms.ForcingMode — Type
ForcingModeData structure for a single forcing mode.
Fields
n::Int- Toroidal mode numberm::Int- Poloidal mode numberamplitude::ComplexF64- Complex amplitude in unit-norm convention (= Fortran Phi_x, T·m² per unit-norm cell) after loading. File inputs are tagged by their input convention:"normal_field_T": B·n̂ in Tesla (2π-angle); converted to unit-norm on load"sfl_flux_Wb": SFL flux in 2π-angle convention; multiplied by (2π)² on load
GeneralizedPerturbedEquilibrium.ForcingTerms.ForcingTermsControl — Type
ForcingTermsControlUser-facing control parameters from TOML [ForcingTerms] section.
Fields
Forcing Data:
forcing_data_file::String- Path to forcing data file (n, m, complex amplitude)forcing_data_format::String- Format: "ascii", "hdf5", or "coil"
Coil settings (used when forcing_data_format = "coil"):
machine::String- Machine name prefix for .dat files (e.g. "d3d")dat_dir::String- Directory containing .dat files; defaults to bundledcoil_geometries/mtheta_coil::Int- Poloidal grid resolution for boundary field evaluation (default: 480)nzeta_coil::Int- Toroidal grid resolution; 0 = auto (32 × n)coil_sets_raw::Vector{Dict{String,Any}}- Parsed[[ForcingTerms.coil_set]]TOML blocks
GeneralizedPerturbedEquilibrium.ForcingTerms.PrecomputedSegments — Type
PrecomputedSegmentsPre-computed segment vectors and midpoints for one conductor strand, stored in contiguous arrays for cache-friendly access in the inner Biot-Savart loop.
GeneralizedPerturbedEquilibrium.ForcingTerms._accumulate_precomputed — Method
_accumulate_precomputed(ox, oy, oz, seg) -> (dbx, dby, dbz)Inner kernel: accumulate Biot-Savart field from pre-computed segments. Returns (dbx, dby, dbz) increments. Matches Fortran field_bs_psi midpoint rule.
Operates on PrecomputedSegments with contiguous arrays for maximum cache efficiency.
GeneralizedPerturbedEquilibrium.ForcingTerms._arc_length_center — Method
_arc_length_center(x, y, z) -> (x0, y0, z0)Compute the arc-length-weighted centroid of a strand (1D array of points). Matches Fortran center-of-mass convention using midpoints weighted by segment length.
GeneralizedPerturbedEquilibrium.ForcingTerms._build_raw_coil_set — Method
_build_raw_coil_set(cfg::CoilConfig, sc::CoilSetConfig) -> CoilSetProduce the un-transformed coil set for a config block, dispatching on sc.source: analytic generators (pf_hoop/window_pane/helical) or a geometry file (.h5 → read_coil_h5, else .dat → read_coil_dat). Currents and shifts/tilts are applied by the caller, so all sources share the same downstream path.
GeneralizedPerturbedEquilibrium.ForcingTerms._resolve_coil_path — Method
_resolve_coil_path(cfg::CoilConfig, set_cfg::CoilSetConfig) -> StringResolve the path to a coil geometry file. Prefers set_cfg.h5_file, then set_cfg.dat_file, otherwise constructs {dat_dir}/{machine}_{name}.dat.
GeneralizedPerturbedEquilibrium.ForcingTerms.accumulate_strand_field! — Method
accumulate_strand_field!(Bx, By, Bz, i, obs_x, obs_y, obs_z, xs, ys, zs, current_nw)Accumulate the Biot-Savart contribution from one conductor strand onto observation point i.
Uses midpoint-rule integration: each segment from point l to l+1 contributes: dB = μ₀/(4π) × current_nw × (dl⃗ × r⃗) / |r|³ where r⃗ is the displacement from the segment midpoint to the observer.
Arguments
Bx,By,Bz: pre-allocated output arrays (Cartesian field, Tesla); updated in-placei: observation point indexobs_x/y/z: observer Cartesian coordinates [m]xs,ys,zs: strand point coordinates [nsec], Cartesian [m]current_nw: effective current = I × nw [A⋅turns]
GeneralizedPerturbedEquilibrium.ForcingTerms.apply_transforms — Method
apply_transforms(cs::CoilSet, cfg::CoilSetConfig; n_tilt::Int=1) -> CoilSetApply per-conductor shifts and tilts to a coil set, returning a modified copy.
Replicates the Fortran coil_read shift/tilt logic (coil.F lines 240–340):
- Tilts are rotations around the arc-length-weighted center of mass (unless
xnom/ynom/znomspecified) n_tiltcontrols the toroidal periodicity of tilt/shift modulation- n_tilt = 0: rigid shift only (no tilts applied)
- n_tilt ≥ 1: n-fold modulated perturbations
GeneralizedPerturbedEquilibrium.ForcingTerms.compute_biot_savart_boundary! — Method
compute_biot_savart_boundary!(B_R, B_phi, B_Z, obs_R, obs_phi, obs_Z, coil_sets)Compute the total Biot-Savart field from all coil sets at every observation point.
Observation points are specified in cylindrical coordinates (R, φ, Z) and the returned field components are also cylindrical.
Uses Threads.@threads over observation points – each thread writes to a unique index so no synchronization is required.
Performance: segment vectors and midpoints are pre-computed once outside the observation-point loop, then accessed from contiguous arrays in the inner kernel.
Arguments
B_R,B_phi,B_Z: output field arrays [Tesla], lengthnobs; overwrittenobs_R,obs_phi,obs_Z: observation point cylindrical coordinates [m, rad, m]coil_sets: conductor geometry and current data
GeneralizedPerturbedEquilibrium.ForcingTerms.compute_coil_forcing_modes! — Method
compute_coil_forcing_modes!(forcing_modes, coil_sets, equil, cfg, n, m_low, m_high; verbose)Top-level entry point: compute Fourier mode amplitudes of the normal magnetic flux from all coil sets on the plasma boundary.
Pipeline
- Build boundary grid at (mtheta × nzeta) resolution, with helicity from
equil.params - Lay out observation points in (R, φ, Z) for all (θ, ζ) combinations
- Run threaded Biot-Savart summation over all coil sets (matches Fortran
field_bs_psi) - Project B field onto plasma boundary normal flux (
project_normal_flux!) - 2D Fourier decompose to get bmn amplitudes for mode range
Output amplitudes are in unit-norm convention (= Fortran Phi_x). No normalization conversion is needed when using these modes with compute_plasma_response!.
Result is appended to forcing_modes (existing content is cleared first).
GeneralizedPerturbedEquilibrium.ForcingTerms.convert_coil_dat_to_h5 — Method
convert_coil_dat_to_h5(dat_path, h5_path; group="coils") -> StringConvert a legacy ASCII .dat coil file to the modern HDF5 format (single set).
GeneralizedPerturbedEquilibrium.ForcingTerms.convert_coil_h5_to_dat — Method
convert_coil_h5_to_dat(h5_path, dat_path; group="coils", set=nothing) -> StringConvert one coil set from an HDF5 file to a legacy ASCII .dat file. If the file holds multiple sets, set selects which one by name (required when more than one is present).
GeneralizedPerturbedEquilibrium.ForcingTerms.convert_forcing_normalization! — Method
convert_forcing_normalization!(modes, from_tag, equil, n, m_low, m_high; psi, mtheta, nzeta)Convert ForcingMode amplitudes from from_tag normalization to the internal unit-norm convention (= Fortran Phi_x), expected by compute_plasma_response!.
If from_tag == "sfl_flux_Wb", the user has provided amplitudes in the 2π-angle SFL-flux convention. These are scaled by (2π)² to reach unit-norm.
If from_tag == "normal_field_T", the amplitudes represent Fourier modes of B·n̂ in Tesla (2π-angle convention). Conversion to unit-norm Phi_x:
- Inverse-Fourier reconstruct B·n̂(θ, ζ) from the input modes
- Multiply pointwise by 2π × R(θ) × |dr/dθ_norm(θ)|
- Re-Fourier transform to get unit-norm mode amplitudes
The 2π factor comes from the toroidal Jacobian ∂r/∂ζnorm = 2π·R·êφ. This conversion is a mode-mixing operation because R and |dr/dθ| vary poloidally.
Arguments
modes:Vector{ForcingMode}with amplitudes to convert (modified in place)from_tag: source normalization —"normal_field_T"or"sfl_flux_Wb"equil:PlasmaEquilibriumproviding boundary geometryn: toroidal mode numberm_low,m_high: poloidal mode range (must cover all modes inmodes)psi: flux surface for boundary geometry (default 1.0)mtheta,nzeta: grid resolution for the conversion (defaults: 256, 64)
GeneralizedPerturbedEquilibrium.ForcingTerms.fourier_decompose_bn — Method
fourier_decompose_bn(bn, grid, n, m_low, m_high) -> Vector{ForcingMode}2D Fourier decompose bn[mtheta, nzeta] to extract mode amplitudes for toroidal mode number n and poloidal range m_low:m_high.
Coefficients are computed as: bmn = (2 / (mtheta × nzeta)) × Σ_{i,j} bn[i,j] × exp(-i(m×θᵢ - n×ζⱼ))
The factor 2 matches the GPEC/DCON convention for real signals where positive and negative m modes are related by conjugation.
When called after project_normal_flux!, the returned amplitudes are in unit-norm convention equal to Fortran Phi_x (T·m² per unit-norm cell).
Uses compute_fourier_coefficients from Utilities.FourierTransforms with the 3D (mtheta×nzeta, mpert) basis matrix (npert=1, nlow=n).
GeneralizedPerturbedEquilibrium.ForcingTerms.load_coil_sets — Method
load_coil_sets(cfg::CoilConfig, n_tilt::Int; equil=nothing) -> Vector{CoilSet}Build all coil sets from config (file or analytic source), apply currents and shifts/tilts, and return ready-to-use coil data. n_tilt is the toroidal mode number for modulated perturbations (typically the run's n). equil is required only when a window_pane set uses standoff placement (it locates the surface point and normal).
GeneralizedPerturbedEquilibrium.ForcingTerms.load_coils_from_h5_group! — Method
load_coils_from_h5_group!(coil_sets::Vector{CoilSet}, group) -> Vector{CoilSet}Populate coil_sets from an already-open HDF5 group, reading every subgroup that holds x/y/z datasets as a CoilSet. Any other datasets or subgroups under group are silently ignored, so the group may live inside a larger file (e.g. gpec.h5). Mirror of save_coils_to_h5.
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_ascii! — Method
load_forcing_ascii!(forcing_modes, filepath, verbose) -> StringLoad forcing data from ASCII file. Returns normalization tag ("normal_field_T" by default).
Optional first # normalization: <tag> header line sets the normalization. Remaining #-prefixed lines and blank lines are ignored. Data columns: n, m, amplitudereal, amplitudeimag.
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_data! — Function
load_forcing_data!(forcing_modes, dir_path, forcing_data_file, forcing_data_format, verbose) -> StringLoad forcing data from ASCII or HDF5 file. Returns the normalization tag read from the file (or "normal_field_T" by default if no tag is present).
Normalization tags (set in the file, not in the TOML):
"normal_field_T"(default): Fourier modes of B·n̂ [Tesla], 2π-angle convention. Most intuitive for users; converted to unit-norm (Phi_x) on load."sfl_flux_Wb": SFL flux R×(BR·∂Z/∂θ - BZ·∂R/∂θ) [T·m²], 2π-angle convention. Multiplied by (2π)² on load to reach unit-norm (Phi_x).
ASCII format — optional # normalization: <tag> header line, then data rows:
# normalization: normal_field_T
1 2 0.5 0.1
1 3 0.3 -0.2Columns: n, m, amplitudereal, amplitudeimag
HDF5 format — optional root attribute normalization (string), plus datasets:
"n": Integer array of toroidal mode numbers"m": Integer array of poloidal mode numbers"amplitude_real": Real parts of amplitudes"amplitude_imag": Imaginary parts of amplitudes
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_from_h5_group! — Method
load_forcing_from_h5_group!(forcing_modes::Vector{ForcingMode}, group)Populate forcing_modes from an already-open HDF5 group with datasets n, m, amplitude_real, amplitude_imag. Mirror of save_forcing_to_h5 for the rerun path — deliberately accepts an open group rather than a file path so the snapshot data can live inside gpec.h5/input/raw_inputs/forcing_terms/.
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_hdf5! — Method
load_forcing_hdf5!(forcing_modes, filepath, verbose) -> StringLoad forcing data from HDF5 file. Returns normalization tag ("normal_field_T" by default).
Optional root attribute normalization (string) sets the normalization. Required datasets: n, m, amplitude_real, amplitude_imag.
GeneralizedPerturbedEquilibrium.ForcingTerms.make_helical — Method
make_helical(; R0, a, m, n, theta_lo=0.0, theta_hi=360.0, n_coils,
gap_fraction=0.0, phi0=0.0, offset=0.0, npts=720,
name="helical") -> CoilSetBuild an array of n_coils analytic helical coils wound on a circular cross-section torus of major radius R0 and minor radius a [m], offset radially outward by offset [m]. The helical pitch is set by the poloidal/toroidal mode numbers m/n via ζ = (m/n)·θ + φ.
The poloidal extent theta_lo/theta_hi is in degrees. For a full poloidal turn (theta_hi - theta_lo == 360) each coil is a single helical strand. For a partial poloidal range the coil is closed into a loop with two helical legs joined by toroidal arcs (matching OMFIT form_helical_array.py). npts sets the strand resolution. Currents are zeroed; load_coil_sets fills them.
GeneralizedPerturbedEquilibrium.ForcingTerms.make_pf_hoop — Method
make_pf_hoop(; radius, height, nsec=361, name="pf_hoop") -> CoilSetBuild a single horizontal circular loop (PF hoop) of radius [m] centered on the machine axis at vertical position height [m], discretized into nsec points.
The loop is closed (point nsec coincides with point 1). A bare hoop is axisymmetric and produces only an n=0 field; tilting or shifting it (via apply_transforms) breaks axisymmetry to produce n≠0 forcing. Currents are zeroed; load_coil_sets fills them.
GeneralizedPerturbedEquilibrium.ForcingTerms.make_window_pane — Method
make_window_pane(; ncoil, rz_corners, gap_fraction=0.0, phi0=0.0,
np_pol=20, np_tor=40, name="window_pane") -> CoilSetBuild an array of ncoil rectangular "picture-frame" (window-pane) saddle coils, evenly spaced toroidally. The two [R, Z] points in rz_corners are the endpoints of the poloidal legs; the legs run along the straight segment between them at the two toroidal edges of the coil, joined by toroidal arcs at each corner. For a vertical frame give the two corners the same R (legs run in Z); for a horizontal frame give them the same Z. Adjacent coils are separated by a toroidal gap of gap_fraction times their angular spacing; the first coil is centered at toroidal angle phi0 [rad].
Each coil is a closed loop with np_pol points per poloidal leg and np_tor per toroidal arc. Currents are zeroed; load_coil_sets fills them.
GeneralizedPerturbedEquilibrium.ForcingTerms.make_window_pane_standoff — Method
make_window_pane_standoff(equil; standoff, poloidal_angle, poloidal_length,
poloidal_tilt=0.0, ncoil, gap_fraction=0.0, phi0=0.0,
name="window_pane") -> CoilSetBuild a window-pane array placed by physical standoff instead of explicit corners. The frame center sits standoff [m] along the outward surface normal at cylindrical poloidal_angle (degrees); its poloidal legs span poloidal_length [m] along the surface tangent, rotated by poloidal_tilt (degrees, 0 = tangential). The control surface is the boundary equil.rzphi_xs[end]. Toroidal layout (ncoil, gap_fraction, phi0) and the leg/arc construction are delegated to make_window_pane.
The poloidal legs wind in the same sense as make_window_pane's c1 -> c2 ordering (lower-Z to higher-Z at the outboard midplane), so positive current produces positive b_n_x near the coil, matching the corner-built window pane.
GeneralizedPerturbedEquilibrium.ForcingTerms.precompute_segments — Method
precompute_segments(cs, j, k, current_nw) → PrecomputedSegmentsBuild contiguous segment-vector and midpoint arrays for strand (j,k) of coil set cs.
GeneralizedPerturbedEquilibrium.ForcingTerms.project_normal_flux! — Method
project_normal_flux!(bn, B_R, B_Z, grid)Project the cylindrical magnetic field (BR, BZ) onto the plasma boundary normal direction ∇ψ and store in bn[mtheta, nzeta].
Computes the unit-norm flux element Phix per (θnorm, ζnorm) cell [T·m²]: bn(θnorm, ζnorm) = 2π × R(θnorm) × (BR × ∂Z/∂θnorm - BZ × ∂R/∂θnorm)
The 2π factor comes from the toroidal Jacobian ∂r/∂ζnorm = 2π·R·êφ in the cross-product ∂r/∂θnorm × ∂r/∂ζnorm. The derivatives dR/dθnorm and dZ/dθnorm are stored in grid.dR_dtheta and grid.dZ_dtheta (unit-norm convention from sample_boundary_grid).
The output matches Fortran GPEC's Phi_x convention directly (no extra factor needed).
Arguments
bn: output array[mtheta, nzeta]; overwritten in-placeB_R,B_Z: cylindrical field components, lengthmtheta × nzeta(flat, θ-major)grid: pre-computed boundary geometry fromsample_boundary_grid
GeneralizedPerturbedEquilibrium.ForcingTerms.read_coil_dat — Method
read_coil_dat(filepath::String) -> CoilSetParse an ASCII coil geometry file.
Format:
ncoil s nsec nw
X₁ Y₁ Z₁
X₂ Y₂ Z₂
...Data rows are ordered: for each conductor 1:ncoil, for each sub-system 1:s, read nsec points. Coordinates are Cartesian in meters (lab frame).
GeneralizedPerturbedEquilibrium.ForcingTerms.read_coil_h5 — Method
read_coil_h5(filepath; group="coils", set=nothing) -> Vector{CoilSet}Read coil sets from an HDF5 file. Coil data is expected under the group path; all coil-set subgroups there are returned (or, if set names one, just that set). Unrelated content elsewhere in the file is ignored. See save_coils_to_h5 for the layout.
GeneralizedPerturbedEquilibrium.ForcingTerms.sample_boundary_grid — Method
sample_boundary_grid(equil, mtheta, nzeta; psi=1.0) -> BoundaryGridEvaluate plasma geometry at a uniform (mtheta × nzeta) grid on the flux surface psi.
Uses equil.rzphi_rsquared and equil.rzphi_offset splines at the given psi. Defaults to psi=1.0 (true plasma boundary). Use a smaller value (e.g. the Fortran psilim) to evaluate on an interior truncation surface. Poloidal derivatives dR/dθnorm and dZ/dθnorm (unit-norm θnorm ∈ [0,1]) are computed via periodic cubic splines on the resulting R(θnorm), Z(θ_norm) data.
The toroidal grid direction follows the Fortran GPEC convention: phi_j = -helicity × 2π × j/nzeta where helicity = sign(Bt) × sign(Ip). This is derived from equil.params.bt_sign and equil.params.crnt. For DIII-D (Bt < 0, Ip > 0 → helicity = -1): phi increases with j (standard direction). For positive-helicity machines (Bt > 0, Ip > 0 → helicity = +1): phi decreases with j.
GeneralizedPerturbedEquilibrium.ForcingTerms.save_coils_to_h5 — Method
save_coils_to_h5(coil_sets::Vector{CoilSet}, group)Write each coil set as a subgroup (named by CoilSet.name) under an already-open HDF5 group. Inverse of load_coils_from_h5_group!. Used by the gpec.h5 snapshot writer so a coil run can be replayed from the output file alone.
GeneralizedPerturbedEquilibrium.ForcingTerms.save_forcing_to_h5 — Method
save_forcing_to_h5(forcing_modes::Vector{ForcingMode}, group)Write forcing modes to an open HDF5 group using the same layout that load_forcing_hdf5! consumes: datasets n, m, amplitude_real, amplitude_imag. Used by the gpec.h5 snapshot writer so the replay path can re-read forcing data from the output file with no reference to the original ASCII/HDF5 ingest file.
GeneralizedPerturbedEquilibrium.ForcingTerms.surface_point_and_normal — Method
surface_point_and_normal(equil, theta_cyl; psi=equil.rzphi_xs[end]) -> (R, Z, nR, nZ)Locate the control-surface point at cylindrical poloidal angle theta_cyl (radians) and return it with the outward unit normal (nR, nZ) in the R-Z plane.
The flux-surface splines are parametrized by the SFL angle theta_sfl, related to the cylindrical angle by theta_cyl = 2π*(theta_sfl + rzphi_offset(psi, theta_sfl)). Since the offset is a small smooth perturbation, theta_sfl is recovered by a damped fixed-point iteration. The tangent is taken by central-differencing R(theta_sfl), Z(theta_sfl), and the normal (dZ/dθ, -dR/dθ) is normalized and oriented outward (positive dot with (R-ro, Z-zo)), matching the normal-flux convention of project_normal_flux!.
GeneralizedPerturbedEquilibrium.ForcingTerms.write_coil_dat — Method
write_coil_dat(filepath, cs::CoilSet)Write a single coil set to an ASCII .dat file in the legacy Fortran GPEC format (ncoil s nsec nw header, then ncoil×s×nsec rows of X Y Z).
GeneralizedPerturbedEquilibrium.ForcingTerms.write_coil_h5 — Method
write_coil_h5(filepath, coil_sets; group="coils")Write coil_sets to a new HDF5 file under the group path, one subgroup per set. Convenience file-level wrapper around save_coils_to_h5 for export and the .dat→.h5 converter.