Time steppers
Aether.TimeSteppers.SSPRK — Type
struct SSPRK{FT, N}An N-stage explicit strong-stability-preserving Runge–Kutta method in the Ketcheson (2010) two-register form. Each stage l applies
$u_1 \leftarrow u_1 + \delta_l u_0$
$u_0 \leftarrow \gamma_{0,l} u_0 + \gamma_{1,l} u_1 + \beta_l \Delta t\, L(u_0)$
with u1 written (not accumulated) at stage 1, so δ = (1, 0, …) makes the stage-1 accumulation a plain copy. L is the full right-hand side: flux divergence plus source contributions. The struct is a coefficient table; the executor is step!.
γ0::NTuple{N, FT} where {FT, N}: multipliesu0in the stage combinationγ1::NTuple{N, FT} where {FT, N}: multipliesu1in the stage combinationβ::NTuple{N, FT} where {FT, N}: multipliesΔt L(u0)in the stage combinationδ::NTuple{N, FT} where {FT, N}: per-stageu1accumulation weight; stage 1 writes insteadcfl_limit::Any: largest stable Courant number of the scheme
Aether.TimeSteppers.IMEXSSPRK — Type
struct IMEXSSPRK{FT, NE, NI}An implicit–explicit Runge–Kutta pair: the explicit tableau is an SSPRK in two-register form, the implicit tableau is a diagonally implicit method one stage ahead of the explicit scheme (NI = NE + 1; implicit stage l belongs to the implicit_update! call at stage = l - 1, with stage = 0 before the first explicit stage). Diagonal entries are the constant γ for stages that solve and 0 for stages that don't — IMEX2P's first implicit row is zero, so its pre-stage call is a no-op. The implicit combination weights b are stored explicitly. Stiff terms enter through components with isstiff(component) = true, whose implicit_update! methods consult these tables.
explicit::SSPRK: explicit tableau in two-register forma::NTuple{NI, NTuple{NI, FT}} where {FT, NI}: rows of the implicit tableau, zero-padded;a[l][l]isγor0b::NTuple{NI, FT} where {FT, NI}: implicit combination weights of the final stageγ::Any: diagonal implicit coefficient
Aether.TimeSteppers.RK2 — Function
RK2() -> SSPRK{Float64, 2}
RK2(FT) -> SSPRK{T, 2} where T
The optimal 2-stage second-order SSP scheme (Heun; Gottlieb 2009, §3).
using Aether
RK2()
# output
SSPRK{Float64}(2 stages, cfl_limit = 1.0)Aether.TimeSteppers.RK3 — Function
RK3() -> SSPRK{Float64, 3}
RK3(FT) -> SSPRK{T, 3} where T
The optimal 3-stage third-order SSP scheme (Shu & Osher; Gottlieb 2009, §3).
Aether.TimeSteppers.RK4 — Function
RK4() -> SSPRK{Float64, 4}
RK4(FT) -> SSPRK{T, 4} where T
The 4-stage fourth-order low-storage scheme RK4()4[2S] of Ketcheson (2010), Table 2. Mid-cycle δ accumulation is what buys fourth order in two registers; the Courant limit 1.3925 is the first-order-flux constraint of Colella (2011).
Aether.TimeSteppers.IMEX2P — Function
IMEX2P(; ...) -> IMEXSSPRK{Float64, 2, 3}
IMEX2P(FT; γ) -> IMEXSSPRK{_A, 2, 3} where _A
The two-solve second-order IMEX pair of Ascher, Ruuth & Spiteri (1997) — IMEX(2,2,2) with δ = 1 - 1/(2γ) — the scheme Krapp et al. (2024) build the multifluid dust drag on ("MDIRK"). The implicit part is a stiffly accurate, L-stable DIRK for either root γ = 1 ± 1/√2 and its first row is zero: there is no implicit solve before the first explicit stage. The default γ = 1 + 1/√2 is the monotone choice, whose explicit part is TVD up to the encoded Courant limit 1/γ ≈ 0.586 (Krapp et al. 2024, §2.2 and Appendix D); γ = 1 - 1/√2 has the smaller truncation error but negative explicit combination weights (no SSP property).
In the two-register form the explicit part is u* = uⁿ + γΔt L(uⁿ) at stage 1 and u0 ← (δ/γ) u0 + (1 - δ/γ) u1 + (1 - δ) Δt L(u0) at stage 2 — verified to reproduce uⁿ⁺¹ = uⁿ + δΔt L(uⁿ) + (1 - δ)Δt L(u*) exactly.
using Aether
IMEX2P()
# output
IMEXSSPRK{Float64}(2 explicit + 3 implicit stages, cfl_limit = 0.585786437626905)Aether.TimeSteppers.Clock — Type
mutable struct Clock{FT}Simulation time, current timestep, and cycle count — deliberately nothing else, so the restart payload stays exactly state + clock. The stage index is an executor argument, never clock state: cycles are atomic.
t::Any: simulation timedt::Any: current timestepcycle::Int64: completed cycles
using Aether
Clock(Float64)
# output
Clock{Float64}(t = 0.0, dt = 0.0, cycle = 0)Aether.TimeSteppers.step! — Function
step!(simulation)
Advance the simulation one cycle: hooks, the drive! injections, the cycle-seam refresh when injections are configured, the stepper's stage loop, the operator-split component updates, then the clock and the next timestep. The slot order is written only here and in the advance_stage! methods — configuration can delete steps (empty tuples, nothing hooks, protocol fallbacks compile to nothing) but never reorder them.
When cycle_seam is set, the final-stage exchange is deferred until before the next stage 1. Ghost values may be stale during that interval; consumers that need them must call refresh_primitives!.
using Aether
mesh = Mesh(CPU(); size = (4, 4, 4), extent = (1, 1, 1))
eos = IdealHydro(2.0, 1e-12, 1e-10)
stepper = RK3()
state = HydroState(mesh, eos, stepper)
state.w0[:, :, :, 1, :] .= 1; # slot 1 = ρ
state.w0[:, :, :, 5, :] .= 0.5; # slot 5 = e
primitive_to_conserved!(CPU(), eos, state.w0, state.u0,
Meshes.padded_ranges(mesh)..., 1:mesh.nblocks)
clock = Clock(Float64)
simulation = (; mesh, eos, state, reconstruction = PLM(), riemann_solver = HLLE(),
exchange = BoundaryExchange(mesh; cells = 5), stepper, clock,
cfl = 1.0, timestep_reduction = nothing, components = (),
cycle_seam = false, hooks = default_hooks())
clock.dt = new_timestep(simulation)
step!(simulation)
clock
# output
Clock{Float64}(t = 0.25, dt = 0.25, cycle = 1)Aether.TimeSteppers.gas_state — Function
gas_state(state) -> Any
Return the gas member of a composite state, or the state itself for a single-fluid state.
Aether.TimeSteppers.default_hooks — Function
default_hooks(
) -> @NamedTuple{before_cycle::Nothing, after_update::Nothing, after_stage::Nothing, after_cycle::Nothing}
The hook set with every slot empty. A simulation's hooks are a NamedTuple of nothing-or-callable over exactly these names, called as hook(simulation, stage):
before_cycle— before anything else in the cycle (stage = 0)after_update— after each stage's conserved update and sourcesafter_stage— at the end of each stage, primitives and ghosts currentafter_cycle— after the stage loop and split updates
Custom flooring after every update is hooks = (; after_stage = my_floor!) — Simulation merges user hooks over default_hooks(); an absent hook costs zero instructions.
using Aether
default_hooks()
# output
(before_cycle = nothing, after_update = nothing, after_stage = nothing, after_cycle = nothing)