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):

TagQuantityUnitsNotes
"normal_field_T"Fourier modes of B·n̂TeslaDefault — 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 conventionnorm ∈ [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_x

The formula is Phi_x(θ_norm, ζ_norm) = 2π × R × (B_R ∂Z/∂θ_norm − B_Z ∂R/∂θ_norm), where the 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 is normal_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² (for sfl_flux_Wb).

HDF5 file format

Required datasets:

  • n: integer array of toroidal mode numbers
  • m: integer array of poloidal mode numbers
  • amplitude_real: float array of real parts
  • amplitude_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"
end

Coil 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:

  1. sample_boundary_grid: evaluate plasma boundary geometry at (mtheta × nzeta) grid
  2. compute_biot_savart_boundary!: compute B at all grid points via Biot-Savart law
  3. project_normal_flux!: compute Phix = 2π×R×(BR ∂Z/∂θnorm − BZ ∂R/∂θ_norm)
  4. 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.0

Use 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 coils recomputes the field from the geometry stored in gpec.h5, so no original files are required — and the equilibrium may be overridden on replay.

Comparison with Fortran GPEC

QuantityJulia conventionFortran GPEC convention
Angle rangeθnorm ∈ [0, 1], ζnorm ∈ [0, 1]θnorm ∈ [0, 1], ζnorm ∈ [0, 1]
Coil forcing outputunit-norm = 2π×R×(BR ∂Z/∂θnorm − BZ ∂R/∂θnorm)Phi_x [Wb]
RatioJulia output ≡ Fortran Phi_x (directly comparable)

API Reference

GeneralizedPerturbedEquilibrium.ForcingTerms.BoundaryGridType
BoundaryGrid

Pre-computed plasma boundary grid for evaluating coil fields.

Fields

  • mtheta, nzeta: grid dimensions
  • R, Z: cylindrical coordinates [mtheta] (same for all ζ, axisymmetric)
  • phi_grid: base toroidal angle grid [nzeta] in radians = -helicity × 2π × j/nzeta
  • phi_offset: per-θ toroidal angle correction [mtheta] from SFL coordinates: ν(ψ, θ) scaled by -helicity, so the physical toroidal angle at (i, j) is phi_grid[j] + phi_offset[i]. Matches Fortran's phi = -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`
source
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilConfigType
CoilConfig

Top-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 bundled coil_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
source
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilSetType
CoilSet

Geometry and current data for a single coil set (one .dat file).

Fields

  • name: coil set identifier (e.g. "il", "iu")
  • ncoil: number of independent conductors
  • s: sub-systems (strand groups) per conductor
  • nw: winding multiplier (turns per conductor element)
  • nsec: number of Cartesian points per strand
  • x, y, z: conductor coordinates [ncoil, s, nsec] in meters (Cartesian)
  • currents: current per conductor [ncoil] in Amperes
source
GeneralizedPerturbedEquilibrium.ForcingTerms.CoilSetConfigType
CoilSetConfig

Per-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 from dat_file/h5_file or the machine+name convention.
  • "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 via ncoil_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 .h5 file
  • source: coil source discriminator (see above); empty/"file" reads a geometry file
  • dat_file: explicit path to .dat file (overrides machine+name convention)
  • h5_file: explicit path to .h5 file (multi-set; name selects which set, else the sole set)
  • currents: current [A] per conductor [ncoil]; shorter arrays pad with zeros
  • shiftx, shifty, shiftz: per-conductor translation [m] [ncoil]
  • tiltx, tilty, tiltz: per-conductor tilt in degrees (or meters if tilt_in_meters)
  • xnom, ynom, znom: explicit rotation center [m]; defaults to arc-length-weighted center of mass
  • n_tilt: toroidal mode number for tilt/shift modulation; -1 means inherit run's n
  • tilt_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 spacing
  • phi0: 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 pitch
  • theta_lo, theta_hi: helical poloidal extent in degrees; a full turn when theta_hi-theta_lo == 360
  • n_coils: number of helical coils in the array
  • offset: 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 degrees
  • poloidal_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
source
GeneralizedPerturbedEquilibrium.ForcingTerms.ForcingModeType
ForcingMode

Data structure for a single forcing mode.

Fields

  • n::Int - Toroidal mode number

  • m::Int - Poloidal mode number

  • amplitude::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
source
GeneralizedPerturbedEquilibrium.ForcingTerms.ForcingTermsControlType
ForcingTermsControl

User-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 bundled coil_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
source
GeneralizedPerturbedEquilibrium.ForcingTerms._accumulate_precomputedMethod
_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.

source
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-place
  • i: observation point index
  • obs_x/y/z: observer Cartesian coordinates [m]
  • xs, ys, zs: strand point coordinates [nsec], Cartesian [m]
  • current_nw: effective current = I × nw [A⋅turns]
source
GeneralizedPerturbedEquilibrium.ForcingTerms.apply_transformsMethod
apply_transforms(cs::CoilSet, cfg::CoilSetConfig; n_tilt::Int=1) -> CoilSet

Apply 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/znom specified)
  • n_tilt controls the toroidal periodicity of tilt/shift modulation
  • n_tilt = 0: rigid shift only (no tilts applied)
  • n_tilt ≥ 1: n-fold modulated perturbations
source
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], length nobs; overwritten
  • obs_R, obs_phi, obs_Z: observation point cylindrical coordinates [m, rad, m]
  • coil_sets: conductor geometry and current data
source
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).

source
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: PlasmaEquilibrium providing boundary geometry
  • n: toroidal mode number
  • m_low, m_high: poloidal mode range (must cover all modes in modes)
  • psi: flux surface for boundary geometry (default 1.0)
  • mtheta, nzeta: grid resolution for the conversion (defaults: 256, 64)
source
GeneralizedPerturbedEquilibrium.ForcingTerms.fourier_decompose_bnMethod
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).

source
GeneralizedPerturbedEquilibrium.ForcingTerms.load_coil_setsMethod
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).

source
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_ascii!Method
load_forcing_ascii!(forcing_modes, filepath, verbose) -> String

Load 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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_data!Function
load_forcing_data!(forcing_modes, dir_path, forcing_data_file, forcing_data_format, verbose) -> String

Load 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.2

Columns: 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
source
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/.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.load_forcing_hdf5!Method
load_forcing_hdf5!(forcing_modes, filepath, verbose) -> String

Load 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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.make_helicalMethod
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") -> CoilSet

Build 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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.make_pf_hoopMethod
make_pf_hoop(; radius, height, nsec=361, name="pf_hoop") -> CoilSet

Build 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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.make_window_paneMethod
make_window_pane(; ncoil, rz_corners, gap_fraction=0.0, phi0=0.0,
                   np_pol=20, np_tor=40, name="window_pane") -> CoilSet

Build 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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.make_window_pane_standoffMethod
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") -> CoilSet

Build 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.

source
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 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-place
  • B_R, B_Z: cylindrical field components, length mtheta × nzeta (flat, θ-major)
  • grid: pre-computed boundary geometry from sample_boundary_grid
source
GeneralizedPerturbedEquilibrium.ForcingTerms.read_coil_datMethod
read_coil_dat(filepath::String) -> CoilSet

Parse 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).

source
GeneralizedPerturbedEquilibrium.ForcingTerms.sample_boundary_gridMethod
sample_boundary_grid(equil, mtheta, nzeta; psi=1.0) -> BoundaryGrid

Evaluate 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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.save_forcing_to_h5Method
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.

source
GeneralizedPerturbedEquilibrium.ForcingTerms.surface_point_and_normalMethod
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!.

source