Utilities Module
The Utilities module provides helper functions and data structures used across GeneralizedPerturbedEquilibrium.
Overview
The Utilities module currently provides:
FourierCoefficients: Lightweight FFT-based Fourier decomposition for periodic data- Helper functions for accessing Fourier coefficients at grid points
API Reference
GeneralizedPerturbedEquilibrium.Utilities — Module
UtilitiesShared mathematical and computational utilities for GPEC modules.
This module provides common functionality used across multiple GPEC modules, including efficient Fourier transforms, numerical integration, and other mathematical utilities.
Submodules
FourierTransforms: Efficient Fourier transforms with pre-computed basis functions
GeneralizedPerturbedEquilibrium.Utilities.FourierCoefficients — Type
FourierCoefficientsLightweight container for Fourier coefficients without spline interpolation. Use this when you only need to access coefficients at original grid points.
Fields
xs::Vector{Float64}: Radial coordinatesmband::Int: Number of Fourier modes (0 to mband inclusive)nqty::Int: Number of quantitiescos_coeffs::Array{Float64,3}: Cosine coefficients (npsi × nmodes × nqty)sin_coeffs::Array{Float64,3}: Sine coefficients (npsi × nmodes × nqty)
GeneralizedPerturbedEquilibrium.Utilities.FourierCoefficients — Method
FourierCoefficients(xs, ys, fs, mband)Compute Fourier coefficients via FFT without creating splines.
Arguments
xs::Vector{Float64}: Radial coordinatesys::Vector{Float64}: Poloidal coordinates (periodic domain)fs::Array{Float64,3}: Function values (npsi × ntheta × nqty)mband::Int: Number of Fourier modes to retain
GeneralizedPerturbedEquilibrium.Utilities.empty_FourierCoefficients — Method
empty_FourierCoefficients()Create an empty FourierCoefficients for initialization purposes.
GeneralizedPerturbedEquilibrium.Utilities.get_complex_coeff — Method
get_complex_coeff(fc::FourierCoefficients, ipsi, mode, qty) -> ComplexF64Get normalized complex FFT coefficient at grid point.
The FourierCoefficients internally stores:
cos_coeffs = 2 * real(FFT) / ntheta(for m > 0)sin_coeffs = -2 * imag(FFT) / ntheta(for m > 0)
This function returns the normalized FFT coefficient (FFT/ntheta):
c[0] = cos_coeffs[0](DC component)c[m] = cos_coeffs[m]/2 - i*sin_coeffs[m]/2(for m > 0)
GeneralizedPerturbedEquilibrium.Utilities.get_complex_coeffs! — Method
get_complex_coeffs!(out, fc::FourierCoefficients, ipsi, qty)Fill vector with normalized complex FFT coefficients for modes 0:mband.
GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms — Module
FourierTransformsUtility module for efficient Fourier transforms using pre-computed basis functions.
Provides a functor-based interface for Fourier transforms between theta-space and mode-space representations. Supports both simple harmonic transforms (cos(mθ), sin(mθ)) and phase-shifted transforms (cos(mθ + nqaδ), sin(mθ + nqaδ)) used in vacuum field calculations.
Features
- Pre-computes trigonometric basis functions for efficiency
- Direct complex number support (no manual real/imaginary splitting)
- Type-stable functor pattern for high performance
- Supports different grids (mthvac, mtheta) with different mode ranges
Example
using GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms
# For PerturbedEquilibrium (no phase shift)
ft = FourierTransform(mtheta, mpert, mlow)
# For Vacuum (with n*qa*delta phase)
ft_vac = FourierTransform(mthvac, mpert, mlow; n=1, qa=2.5, delta=delta_array)
# Forward transform: theta-space → Fourier modes
theta_data = randn(mtheta, 10) # 10 different theta-space functions
modes = ft(theta_data) # Complex modes [mpert, 10]
# Inverse transform: Fourier modes → theta-space
reconstructed = inverse(ft, modes)GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.FourierTransform — Type
FourierTransformCallable struct for efficient Fourier transforms with pre-computed basis functions.
Fields
mtheta::Int: Number of theta grid pointsmpert::Int: Number of Fourier modesmlow::Int: Lowest mode numbercslth::Matrix{Float64}: Cosine basis functions [mtheta, mpert]snlth::Matrix{Float64}: Sine basis functions [mtheta, mpert]
Usage
Create once with appropriate grid parameters, then use repeatedly:
# Create transform (coefficients computed once)
ft = FourierTransform(mtheta, mpert, mlow)
# Forward transform (callable): theta → modes
modes = ft(theta_data)
# Inverse transform: modes → theta
theta_reconstructed = inverse(ft, modes)See also: compute_fourier_coefficients, inverse
GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.FourierTransform — Method
(ft::FourierTransform)(data::AbstractVecOrMat{<:Complex})Forward Fourier transform for complex-valued theta-space data.
Transforms complex data at theta grid points into complex Fourier mode coefficients.
Arguments
data::AbstractVecOrMat{ComplexF64}: Complex data at theta points
Returns
- Complex Fourier coefficients with same shape as real input case
Formula
For complex input data = data_real + im*data_imag, computes:
Re{mode[l]} = Σᵢ (data_real[i]*cos - data_imag[i]*sin)
Im{mode[l]} = Σᵢ (data_real[i]*sin + data_imag[i]*cos)where cos and sin are the basis functions at theta point i for mode l.
GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.FourierTransform — Method
(ft::FourierTransform)(data::AbstractVecOrMat{<:Real})Forward Fourier transform: theta-space → mode-space (callable functor).
Transforms real-valued data at theta grid points into complex Fourier mode coefficients.
Arguments
data::AbstractVecOrMat{Float64}: Data at theta points- If
Vector{Float64}with lengthmtheta: single function to transform - If
Matrix{Float64}with size(mtheta, n): n functions to transform simultaneously
- If
Returns
- Complex Fourier coefficients
- If input is
Vector: returnsVector{ComplexF64}of lengthmpert - If input is
Matrix: returnsMatrix{ComplexF64}of size(mpert, n)
- If input is
Formula
For each mode l (corresponding to mode number m = mlow + l - 1):
mode[l] = Σᵢ data[i] * (cos(m*θᵢ + phase) + im*sin(m*θᵢ + phase))This is equivalent to computing:
real_part[l] = Σᵢ data[i] * cos(m*θᵢ + phase)
imag_part[l] = Σᵢ data[i] * sin(m*θᵢ + phase)
mode[l] = complex(real_part[l], imag_part[l])Examples
ft = FourierTransform(480, 40, -20)
# Transform a single function
f_theta = sin.(theta_grid)
f_modes = ft(f_theta) # Vector{ComplexF64} of length 40
# Transform multiple functions at once
data = randn(480, 10) # 10 different functions
modes = ft(data) # Matrix{ComplexF64} of size (40, 10)GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.FourierTransform — Method
FourierTransform(mtheta, mpert, mlow; n=0, qa=0.0, delta=zeros(mtheta))Construct a FourierTransform object with pre-computed basis functions.
Arguments
mtheta::Int: Number of poloidal grid pointsmpert::Int: Number of Fourier modesmlow::Int: Lowest mode number
Keyword Arguments
n::Int=0: Toroidal mode numberqa::Float64=0.0: Safety factor at boundarydelta::Vector{Float64}=zeros(mtheta): Toroidal phase shift array
Returns
FourierTransform object ready for forward and inverse transforms.
Examples
# Simple case: no phase shift (for PerturbedEquilibrium)
ft = FourierTransform(480, 40, -20)
# With toroidal phase (for Vacuum calculations)
ft_vac = FourierTransform(480, 40, -20; n=1, ν=ν_array)GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.compute_fourier_coefficients — Method
compute_fourier_coefficients(mtheta, mpert, mlow, nzeta, npert, nlow; n=nothing, ν=zeros(mtheta))Compute Fourier basis function coefficients for transforms between physical-space and mode-space. Supports both 2D and 3D geometries. In 2D, we only use one toroidal mode at a time, so we can just use the n argument. In 3D, we need to compute the basis for all modes and grid points.
Arguments
mtheta::Int: Number of poloidal grid points (theta resolution)mpert::Int: Number of Fourier modes (spectral resolution)mlow::Int: Lowest mode number (mode numbering starts here)nzeta::Int: Number of toroidal grid pointsnpert::Int: Number of toroidal modesnlow::Int: Lowest toroidal mode number
Keyword Arguments
n_2D::Union{Nothing, Int}=nothing: Toroidal mode number for 2D (default: nothing)ν::Vector{Float64}=zeros(mtheta): Toroidal angle offset array (default: no offset, n*ν = 0)
Returns
- 2D
cos_mn_basis::Matrix{Float64}: Cosine coefficientscos(m*θ - n*ν)[mtheta, mpert]sin_mn_basis::Matrix{Float64}: Sine coefficientssin(m*θ - n*ν)[mtheta, mpert]
- 3D
cos_mn_basis::Matrix{Float64}: Cosine coefficientscos(m*θ - n*ν - n*ϕ)[mtheta * nzeta, mpert * npert]sin_mn_basis::Matrix{Float64}: Sine coefficientssin(m*θ - n*ν - n*ϕ)[mtheta * nzeta, mpert * npert]
Notes
The theta and phi grids are uniform: θᵢ = 2π*i/mtheta for i = 0:mtheta-1 and ϕⱼ = 2π*j/nzeta for j = 0:nzeta-1
When n=0, ν=0 (default), this reduces to simple harmonic basis:
cos_mn_basis[i,l] = cos(m*θᵢ)sin_mn_basis[i,l] = sin(m*θᵢ)
GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.fourier_inverse_transform! — Method
fourier_inverse_transform!(gll::Matrix{Float64}, gil::Matrix{Float64}, cs::Matrix{Float64}, m00::Int, l00::Int)Low-level inverse Fourier transform with offset support for Vacuum module.
Performs the inverse Fourier transform of gil onto gll using pre-computed Fourier coefficients cs, with support for block offsets in the input matrix.
This is used by the Vacuum module for inverse transforming Green's function matrices back to mode space from theta space.
Arguments
gll::Matrix{Float64}: Output matrix (mpert × mpert), updated in-placegil::Matrix{Float64}: Input matrix containing Fourier-transformed datacs::Matrix{Float64}: Fourier coefficient matrix (mtheta × mpert), eithercslthorsnlthm00::Int: Row offset ingilmatrix (0-based indexing convention)l00::Int: Column offset ingilmatrix (0-based indexing convention)
Operation
Computes: gll[l2, l1] = dθdζ * Σᵢ cs[i, l2] * gil[m00+i, l00+l1]
Notes
- The normalization factor
4π^2 / size(cs, 1)is 2π / mtheta * 2π / nzeta (nzeta = 1 for 2D) - This function uses 0-based offset convention (add 1 for Julia indexing)
- The
csmatrix should be eithercslthorsnlthfrom aFourierTransformobject
Example
ft = FourierTransform(mtheta, mpert, mlow; n=n, qa=qa, delta=delta)
gil = ... # Transformed Green's function data
arr = zeros(mpert, mpert)
# Inverse transform from real part block using cosine coefficients
fourier_inverse_transform!(arr, gil, ft.cslth)GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.fourier_transform! — Method
fourier_transform!(gil::AbstractMatrix{Float64}, gij::AbstractMatrix{Float64}, cs::Matrix{Float64}; row_offset::Int=0, col_offset::Int=0)Low-level Fourier transform with offset support for Vacuum module.
Performs a truncated Fourier transform of gij onto gil using pre-computed Fourier coefficients cs, with support for block offsets in the output matrix.
This is used by the Vacuum module for transforming Green's function matrices that have specific block structure (e.g., plasma and wall contributions packed together).
Arguments
gil::AbstractMatrix{Float64}: Output matrix, updated in-place at offset blockgij::AbstractMatrix{Float64}: Input matrix (mtheta × mtheta) containing theta-space datacs::Matrix{Float64}: Fourier coefficient matrix (mtheta × mpert)row_offset::Int: Row offset ingilmatrixcol_offset::Int: Column offset ingilmatrix
Operation
Computes: gil[row_offset+i, col_offset+l] = Σⱼ gij[i, j] * cs[j, l] for i ∈ 1:size(cs, 1), l ∈ 1:size(cs, 2)
Notes
- This function uses 0-based offset convention (add 1 for Julia indexing)
- Used for packing real and imaginary parts in separate blocks of
gil
Example
ft = FourierTransform(mtheta, mpert, mlow; n=n, qa=qa, delta=delta)
gil = zeros(2*mtheta, 2*mpert) # Packed real/imag structure
gij = ... # Some theta-space data
# Transform using cosine coefficients, real part block (offset 0, 0)
fourier_transform!(gil, gij, ft.cslth; row_offset=0, col_offset=0)
# Transform using sine coefficients, imaginary part block (offset 0, mpert)
fourier_transform!(gil, gij, ft.snlth; row_offset=0, col_offset=mpert)GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.inverse — Method
inverse(ft::FourierTransform, modes::AbstractVecOrMat{<:Complex})Inverse Fourier transform: mode-space → theta-space.
Reconstructs theta-space data from complex Fourier mode coefficients.
Arguments
modes::AbstractVecOrMat{ComplexF64}: Fourier mode coefficients- If
Vector{ComplexF64}with lengthmpert: single mode expansion to reconstruct - If
Matrix{ComplexF64}with size(mpert, n): n mode expansions to reconstruct
- If
Returns
- Reconstructed data at theta points
- If input is
Vector: returnsVector{ComplexF64}of lengthmtheta - If input is
Matrix: returnsMatrix{ComplexF64}of size(mtheta, n)
- If input is
Formula
For each theta point i:
data[i] = (2π/mtheta) * Σₗ modes[l] * (cos(m*θᵢ + phase) + im*sin(m*θᵢ + phase))This is equivalent to:
data[i] = (2π/mtheta) * Σₗ [Re{modes[l]}*cos - Im{modes[l]}*sin +
im*(Re{modes[l]}*sin + Im{modes[l]}*cos)]Notes
The normalization factor 2π/mtheta ensures proper Fourier series reconstruction.
For real-valued theta data, the result will have negligible imaginary parts (machine precision). Use real.(inverse(ft, modes)) to extract the real part if needed.
Examples
ft = FourierTransform(480, 40, -20)
# Reconstruct from modes
modes = randn(ComplexF64, 40)
theta_data = inverse(ft, modes) # Vector{ComplexF64} of length 480
# For real reconstruction
theta_real = real.(inverse(ft, modes))GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.inverse — Method
inverse(ft::FourierTransform, modes::AbstractVecOrMat{<:Real})Inverse Fourier transform for real mode coefficients (pure cosine expansion).
Special case where all modes are real-valued, corresponding to a cosine-only Fourier series.
Arguments
modes::AbstractVecOrMat{Float64}: Real Fourier coefficients
Returns
- Real-valued data at theta points with normalization factor
2π/mtheta
Formula
data[i] = (2π/mtheta) * Σₗ modes[l] * cos(m*θᵢ + phase)Notes
This is a special case and less commonly used. Most applications use complex modes.
GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.inverse_transform! — Method
inverse_transform!(output::AbstractVecOrMat{ComplexF64}, ft::FourierTransform, modes::AbstractVecOrMat{<:Complex})In-place inverse Fourier transform: mode-space → theta-space.
More efficient than the allocating version inverse(ft, modes) when called repeatedly.
Arguments
output::AbstractVecOrMat{ComplexF64}: Pre-allocated output array for theta-space data- For vector: must have length
mtheta - For matrix: must have size
(mtheta, n)wheren = size(modes, 2)
- For vector: must have length
ft::FourierTransform: Fourier transform objectmodes::AbstractVecOrMat{ComplexF64}: Complex Fourier mode coefficients- For vector: length must be
mpert - For matrix: first dimension must be
mpert
- For vector: length must be
Returns
output: The modified output array (for chaining)
Formula
data[i] = (2π/mtheta) * Σₗ [Re{modes[l]}*cos - Im{modes[l]}*sin +
im*(Re{modes[l]}*sin + Im{modes[l]}*cos)]Example
ft = FourierTransform(480, 40, -20)
# Pre-allocate output buffer
theta_data = zeros(ComplexF64, 480)
# Reuse buffer in loop
for i in 1:1000
modes = get_modes(i)
inverse_transform!(theta_data, ft, modes) # No allocations
process_theta_data(theta_data)
endGeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.transform! — Method
transform!(output::AbstractVecOrMat{ComplexF64}, ft::FourierTransform, data::AbstractVecOrMat{<:Complex})In-place forward Fourier transform for complex-valued theta-space data.
Arguments
output::AbstractVecOrMat{ComplexF64}: Pre-allocated output array for Fourier modesft::FourierTransform: Fourier transform objectdata::AbstractVecOrMat{ComplexF64}: Complex-valued data at theta points
Returns
output: The modified output array
Formula
For complex input data = data_real + im*data_imag:
Re{mode[l]} = Σᵢ (data_real[i]*cos - data_imag[i]*sin)
Im{mode[l]} = Σᵢ (data_real[i]*sin + data_imag[i]*cos)GeneralizedPerturbedEquilibrium.Utilities.FourierTransforms.transform! — Method
transform!(output::AbstractVecOrMat{ComplexF64}, ft::FourierTransform, data::AbstractVecOrMat{<:Real})In-place forward Fourier transform for real-valued theta-space data.
More efficient than the allocating version ft(data) when called repeatedly, as it reuses the pre-allocated output array.
Arguments
output::AbstractVecOrMat{ComplexF64}: Pre-allocated output array for Fourier modes- For vector input: must have length
mpert - For matrix input: must have size
(mpert, n)wheren = size(data, 2)
- For vector input: must have length
ft::FourierTransform: Fourier transform object with pre-computed coefficientsdata::AbstractVecOrMat{Float64}: Real-valued data at theta points- For vector: length must be
mtheta - For matrix: first dimension must be
mtheta
- For vector: length must be
Returns
output: The modified output array (for chaining)
Performance
This function uses in-place matrix multiplication (mul!) to avoid allocations, making it suitable for tight loops and performance-critical code.
Example
ft = FourierTransform(480, 40, -20)
# Pre-allocate output buffer
modes = zeros(ComplexF64, 40)
# Reuse buffer in loop
for i in 1:1000
data = get_theta_data(i)
transform!(modes, ft, data) # No allocations
process_modes(modes)
endExample Usage
using GeneralizedPerturbedEquilibrium
# Create sample periodic data
xs = range(0, 1; length=100) |> collect
ys = range(0, 2π; length=64) |> collect
fs = zeros(100, 64, 2)
# Fill with periodic function
for i in 1:100, j in 1:64
fs[i, j, 1] = exp(-xs[i]) * cos(3*ys[j])
fs[i, j, 2] = exp(-xs[i]) * sin(5*ys[j])
end
# Compute Fourier coefficients, keeping 10 modes
fc = GeneralizedPerturbedEquilibrium.Utilities.FourierCoefficients(xs, ys, fs, 10)
# Access individual coefficient
# Get mode 3 at radial index 50 for quantity 1
c = GeneralizedPerturbedEquilibrium.Utilities.get_complex_coeff(fc, 50, 3, 1)
# Get all coefficients for a given radial index and quantity
coeffs = Vector{ComplexF64}(undef, fc.mband + 1)
GeneralizedPerturbedEquilibrium.Utilities.get_complex_coeffs!(coeffs, fc, 50, 1)