Stochastic Forcing API
Types
Abstract Types
abstract type Forcing end
abstract type StochasticForcingType <: Forcing end
abstract type DeterministicForcingType <: Forcing endStochasticForcing
Tarang.StochasticForcing — Type
StochasticForcing{T, N, A<:AbstractArray}Stochastic forcing in Fourier space with white-noise temporal correlation. Supports both CPU and GPU architectures.
Mathematical Properties
The forcing F̂(k,t) satisfies:
- ⟨F̂(k,t)⟩ = 0 (zero mean)
- ⟨F̂(k,t) F̂*(k',t')⟩ = Q̂(k) δ(k-k') δ(t-t')/(dt) (white noise)
Implementation
At each timestep, the forcing is computed as: F̂(k) = √(Q̂(k)) · ξ(k) / √(dt)
where ξ(k) is complex white noise with |ξ| = 1 and random phase.
Fields
forcing_spectrum::A: √Q̂(k) - square root of power spectrumenergy_injection_rate::T: Target energy injection rate εk_forcing::T: Central forcing wavenumber k_fdk_forcing::T: Forcing bandwidth δ_fdt::T: Current timestep (for proper scaling)domain_size::NTuple{N,T}: Domain size (Lx, Ly, ...)field_size::NTuple{N,Int}: Grid size (Nx, Ny, ...)wavenumbers::NTuple{N,Vector{T}}: Wavenumber arrays (kx, ky, ...)cached_forcing::AbstractArray{Complex{T},N}: Cached forcing (constant within timestep)prevsol::Union{Nothing,AbstractArray{Complex{T},N}}: Previous solution (for Stratonovich work)rng::AbstractRNG: Random number generator (default: fresh MersenneTwister per instance for thread/parallel safety)random_phases::AbstractArray{T,N}: Pre-allocated random phase buffer (on target architecture)last_update_time::T: Time of last forcing updatespectrum_type::Symbol: Type of forcing spectrumenforce_hermitian::Bool: Enforce Hermitian symmetry for real-valued fieldsarchitecture::AbstractArchitecture: CPU() or GPU() architecture
Stochastic forcing in Fourier space with white-noise temporal correlation.
Type signature:
mutable struct StochasticForcing{T<:AbstractFloat, N} <: StochasticForcingTypeFields:
| Field | Type | Description |
|---|---|---|
forcing_spectrum | Array{T, N} | √Q̂(k) - amplitude spectrum |
energy_injection_rate | T | Target ε |
k_forcing | T | Central forcing wavenumber |
dk_forcing | T | Forcing bandwidth |
dt | T | Current timestep |
domain_size | NTuple{N, T} | Domain extent (Lx, Ly, ...) |
field_size | NTuple{N, Int} | Grid size (Nx, Ny, ...) |
wavenumbers | NTuple{N, Vector{T}} | Wavenumber arrays (kx, ky, ...) |
cached_forcing | Array{Complex{T}, N} | Cached F̂ (constant within timestep) |
prevsol | Union{Nothing, Array{Complex{T}, N}} | Previous solution for Stratonovich work |
rng | AbstractRNG | Random number generator |
last_update_time | T | Time of last forcing update |
spectrum_type | Symbol | Type of forcing spectrum |
Constructor:
StochasticForcing(;
field_size::NTuple{N, Int}, # Required
domain_size::NTuple{N, Real} = ntuple(i -> 2π, N),
energy_injection_rate::Real = 1.0,
k_forcing::Real = 4.0,
dk_forcing::Real = 1.0,
dt::Real = 0.01,
spectrum_type::Symbol = :ring,
rng::AbstractRNG = Random.GLOBAL_RNG,
dtype::Type{T} = Float64
) where {T<:AbstractFloat, N}Spectrum types:
| Symbol | Description | Formula |
|---|---|---|
:ring | Gaussian ring around k_f | exp(-( |
:band | Sharp band [kf - δf, kf + δf] | 1 if |
:lowk | Low wavenumber forcing | 1 if |
:kolmogorov | Large-scale Kolmogorov | Smooth large-scale forcing |
DeterministicForcing
Tarang.DeterministicForcing — Type
DeterministicForcing{T, N}Deterministic (non-random) forcing.
Example
# Sinusoidal forcing
forcing = DeterministicForcing(
(x, y, t, p) -> p[:A] * sin(p[:k] * x) * cos(p[:ω] * t),
(64, 64);
parameters = Dict(:A => 1.0, :k => 4.0, :ω => 1.0)
)Deterministic (non-random) forcing.
Type signature:
mutable struct DeterministicForcing{T<:AbstractFloat, N, A<:AbstractArray{T, N}} <: DeterministicForcingTypeFields:
| Field | Type | Description |
|---|---|---|
forcing_function | Function | f(x, y, ..., t, params) → forcing |
field_size | NTuple{N, Int} | Grid size |
cached_forcing | A | Cached forcing values on the chosen architecture |
parameters | Dict{Symbol, Any} | Parameters for forcing function |
architecture | AbstractArchitecture | CPU() or GPU() backend |
Constructor:
DeterministicForcing(
forcing_function::Function,
field_size::NTuple{N, Int};
parameters::Dict{Symbol, Any} = Dict{Symbol, Any}(),
dtype::Type{T} = Float64,
architecture::AbstractArchitecture = CPU()
)Forcing Generation
generate_forcing!
Tarang.generate_forcing! — Function
generate_forcing!(forcing::StochasticForcing, t::Real, substep::Int=1)Generate stochastic forcing at time t. Works on both CPU and GPU.
Key Points
Forcing is regenerated only on substep 1 - ensures forcing stays constant within a timestep for IMEX and multi-stage methods.
Scaling: F̂(k) = √Q̂(k) · exp(i·φ) / √dt, where φ ∼ Uniform[0, 2π) (generated by
_fill_random_phases!)Zero mean: The k=0 mode is always set to zero.
Hermitian symmetry (optional): F̂(-k) = F̂(k)* so that ifft produces real fields.
GPU support: Random phases are generated on the target architecture when possible, then combined with the spectrum using broadcasted operations.
Arguments
forcing: StochasticForcing configurationt: Current simulation timesubstep: Current substep (1 for first substep)
Returns
The cached forcing array (modified in-place).
generate_forcing!(forcing::StochasticForcing, t::Real)Generate forcing without substep tracking. Equivalent to substep=1.
generate_forcing!(forcing::DeterministicForcing, grid, t::Real)Evaluate deterministic forcing at time t on the given grid.
Generate forcing realization. Returns cached value for substeps > 1.
Signatures:
# Stochastic forcing
generate_forcing!(forcing::StochasticForcing, t::Real, substep::Int=1)
generate_forcing!(forcing::StochasticForcing, t::Real)
# Deterministic forcing
generate_forcing!(forcing::DeterministicForcing, grid, t::Real)Key behavior:
substep == 1: Generates new random forcingsubstep > 1: Returns cached forcing (same as substep 1)- Time check: Won't regenerate if already updated at this time
Returns: The cached forcing array forcing.cached_forcing
apply_forcing!
Tarang.apply_forcing! — Function
apply_forcing!(rhs::AbstractArray, forcing::StochasticForcing, t::Real, substep::Int=1)Add stochastic forcing to the RHS in spectral space. Works on both CPU and GPU.
Arguments
rhs: Right-hand side array (modified in-place)forcing: StochasticForcing configurationt: Current simulation timesubstep: Current substep number
Add forcing to a field in spectral space.
Signature:
apply_forcing!(
rhs::AbstractArray{Complex{T}, N},
forcing::StochasticForcing{T, N},
t::Real,
substep::Int=1
) where {T, N}Equivalent to: rhs .+= generate_forcing!(forcing, t, substep)
Configuration
set_dt!
Tarang.set_dt! — Function
set_dt!(forcing::StochasticForcing, dt::Real)Update the timestep for Stratonovich scaling.
Call this when dt changes (e.g., adaptive timestepping).
Update the timestep used for Stratonovich scaling.
set_dt!(forcing::StochasticForcing{T, N}, dt::Real) where {T, N}Call this when dt changes (e.g., adaptive timestepping).
reset_forcing!
Tarang.reset_forcing! — Function
reset_forcing!(forcing::StochasticForcing)Reset the forcing cache, causing regeneration on next call. Works on both CPU and GPU.
Reset forcing cache. Forces regeneration on next generate_forcing! call.
reset_forcing!(forcing::StochasticForcing{T, N}) where {T, N}Work Calculation
store_prevsol!
Tarang.store_prevsol! — Function
store_prevsol!(forcing::StochasticForcing, sol::AbstractArray)Store the current solution for Stratonovich work calculation. Works on both CPU and GPU arrays.
Call this at the beginning of each timestep, before advancing.
Store the current solution for Stratonovich work calculation.
store_prevsol!(forcing::StochasticForcing{T, N}, sol::AbstractArray{Complex{T}, N})Call at the beginning of each timestep, before advancing.
work_stratonovich
Tarang.work_stratonovich — Function
work_stratonovich(forcing::StochasticForcing, sol::AbstractArray)Compute work done by forcing using Stratonovich interpretation. Works on both CPU and GPU arrays.
Formula
W = Re⟨(ψⁿ + ψⁿ⁺¹)/2 · ΔF̂*⟩where ΔF̂ = √Q̂ · ξ · √dt is the forcing increment over dt.
This correctly accounts for the correlation between forcing and response.
Arguments
forcing: StochasticForcing with prevsol storedsol: Current solution ψⁿ⁺¹
Returns
Work done during this timestep (scalar, units of energy).
Compute work done using Stratonovich interpretation.
work_stratonovich(forcing::StochasticForcing{T, N}, sol::AbstractArray{Complex{T}, N}) -> TFormula:
\[W = -\text{Re}\left\langle \frac{\psi^n + \psi^{n+1}}{2} \cdot \hat{F}^* \right\rangle\]
Uses midpoint evaluation (requires store_prevsol! called first).
work_ito
Tarang.work_ito — Function
work_ito(forcing::StochasticForcing, sol::AbstractArray)Compute work done by forcing using Itô interpretation. Works on both CPU and GPU arrays.
Formula
W_Itô = Re⟨ψⁿ · ΔF̂*⟩ + ε · dtwhere ΔF̂ = √Q̂ · ξ · √dt is the forcing increment.
The drift correction ε · dt accounts for the Itô-Stratonovich conversion. In Itô calculus, ψⁿ is independent of Fⁿ⁺¹, so ⟨ψⁿ · ΔF̂⟩ = 0. The drift ensures ⟨WItô⟩ = ⟨WStratonovich⟩ = ε · dt.
Compute work done using Itô interpretation.
work_ito(forcing::StochasticForcing{T, N}, sol_prev::AbstractArray{Complex{T}, N}) -> TFormula:
\[W_{\text{Itô}} = -\text{Re}\langle \psi^n \cdot \hat{F}^* \rangle \cdot dt + \varepsilon \cdot dt\]
Uses initial value plus drift correction.
Diagnostics
meanenergyinjection_rate
Tarang.mean_energy_injection_rate — Function
mean_energy_injection_rate(forcing::StochasticForcing)Return the target (mean) energy injection rate ε.
This is the ensemble average of work done per unit time.
Return the target (mean) energy injection rate ε.
mean_energy_injection_rate(forcing::StochasticForcing) -> Tinstantaneous_power
Tarang.instantaneous_power — Function
instantaneous_power(forcing::StochasticForcing, sol::AbstractArray)Compute instantaneous power input P = Re⟨ψ · F̂*⟩ / A. Works on both CPU and GPU arrays.
This is the correlation between the solution and forcing at a given instant. For white noise forcing, the expected value depends on which solution is passed:
- ⟨P⟩ = 0 if sol is the solution BEFORE the forcing was applied (independent)
- ⟨P⟩ = ε if sol is the MIDPOINT (ψⁿ + ψⁿ⁺¹)/2
- ⟨P⟩ = 2ε if sol is the solution AFTER forcing (includes full response)
Note: For the Stratonovich-consistent time-averaged power over the timestep, use work_stratonovich(forcing, sol) / forcing.dt instead.
Returns
Instantaneous power (energy per unit time).
Compute instantaneous power input P(t) = dW/dt.
instantaneous_power(forcing::StochasticForcing{T, N}, sol::AbstractArray{Complex{T}, N}) -> TThis fluctuates around the mean ε due to randomness.
forcingenstrophyinjection_rate
Tarang.forcing_enstrophy_injection_rate — Function
forcing_enstrophy_injection_rate(forcing::StochasticForcing)Compute mean enstrophy injection rate (for 2D turbulence).
η = ∑_k |k|² Q̂(k) / (2 · domain_area)Note: This assumes the forcing spectrum Q̂(k) corresponds to direct field forcing. For vorticity forcing, enstrophy injection is simply ε. For streamfunction forcing, this formula gives the enstrophy injection rate.
Compute mean enstrophy injection rate (for 2D vorticity forcing).
forcing_enstrophy_injection_rate(forcing::StochasticForcing{T, N}) -> TFormula:
\[\eta = \sum_k |k|^2 \hat{Q}(k) / 2\]
getforcingspectrum
Tarang.get_forcing_spectrum — Function
get_forcing_spectrum(forcing::StochasticForcing)Return the forcing amplitude spectrum √Q̂(k).
Return the forcing amplitude spectrum √Q̂(k).
get_forcing_spectrum(forcing::StochasticForcing) -> Array{T, N}getcachedforcing
Tarang.get_cached_forcing — Function
get_cached_forcing(forcing::StochasticForcing)Return the current cached forcing F̂(k).
get_cached_forcing(state::TimestepperState)Get the cached forcing array. Returns nothing if no forcing is configured.
Return the current cached forcing F̂(k).
get_cached_forcing(forcing::StochasticForcing) -> Array{Complex{T}, N}Internal Functions
build_wavenumbers
Build wavenumber arrays for each dimension.
build_wavenumbers(
field_size::NTuple{N, Int},
domain_size::NTuple{N, Real},
dtype::Type{T}
) -> NTuple{N, Vector{T}}computeforcingspectrum
Compute the forcing amplitude spectrum √Q̂(k).
compute_forcing_spectrum(
wavenumbers::NTuple{N, Vector{T}},
k_f::Real,
dk_f::Real,
ε::Real,
domain_size::NTuple{N, Real},
spectrum_type::Symbol,
dtype::Type{T}
) -> Array{T, N}The spectrum is normalized such that energy injection rate equals ε.
Exports
export Forcing, StochasticForcingType, DeterministicForcingType
export StochasticForcing, DeterministicForcing
export generate_forcing!, apply_forcing!
export reset_forcing!, set_dt!
export store_prevsol!, work_stratonovich, work_ito
export mean_energy_injection_rate, instantaneous_power
export forcing_enstrophy_injection_rate
export get_forcing_spectrum, get_cached_forcingIndex
Tarang.DeterministicForcingTarang.StochasticForcingTarang.apply_forcing!Tarang.forcing_enstrophy_injection_rateTarang.generate_forcing!Tarang.get_cached_forcingTarang.get_forcing_spectrumTarang.instantaneous_powerTarang.mean_energy_injection_rateTarang.reset_forcing!Tarang.set_dt!Tarang.store_prevsol!Tarang.work_itoTarang.work_stratonovich