Skip to content

Operators API ​

For narrative coverage of the operator hierarchy, the apply! contract, and the Strang palindrome, see Operators. For per-scheme advection properties, Advection schemes.

Operators (top-level) ​

AtmosTransport.Operators Module
julia
Operators

Physics operators for the basis-explicit transport architecture.

Provides:

  • Abstract operator types (AbstractOperator, AbstractDiffusion, etc.)

  • Advection hierarchy: AbstractAdvectionScheme → AbstractConstantScheme, AbstractLinearScheme, AbstractQuadraticScheme with concrete UpwindScheme, SlopesScheme, and structured-grid PPMScheme

  • Strang splitting orchestrator: strang_split!, apply!

source
AtmosTransport.Operators.AbstractConvection Type
julia
AbstractConvection <: AbstractOperator

Root type for convective-transport operators. Concrete subtypes live in src/Operators/Convection/ (NoConvection, CMFMCConvection, TM5Convection).

source
AtmosTransport.Operators.AbstractDiffusion Type
julia
AbstractDiffusion <: AbstractOperator

Root type for vertical diffusion operators. Concrete subtypes live in src/Operators/Diffusion/operators.jl (NoDiffusion, ImplicitVerticalDiffusion, …).

source
AtmosTransport.Operators.AbstractOperator Type
julia
AbstractOperator

Root type for all physics operators in the transport model.

source
AtmosTransport.Operators.AbstractStepRecorder Type
julia
AbstractStepRecorder

Observer of one model step. Production passes NoStepRecorder; the cubed-sphere adjoint passes a recorder that writes its tape from the same calls. Hooks (record_halo!, record_sweep!, record_center!, record_diffusion!, record_emission!, record_linrood_horizontal!, record_convection!, record_air_mass_reset!, record_chemistry!) read their arguments and never modify model state.

source
AtmosTransport.Operators.NoStepRecorder Type
julia
NoStepRecorder()

The production recorder: every hook is an inlined no-op.

source

Advection ​

AtmosTransport.Operators.Advection Module
julia
Advection

Advection operators for the basis-explicit transport architecture.

Provides:

Scheme hierarchy:

  • UpwindScheme <: AbstractConstantScheme — first-order upwind via generic kernels

  • SlopesScheme <: AbstractLinearScheme — van Leer slopes (limiter-dispatched)

  • PPMScheme <: AbstractQuadraticScheme — structured-grid PPM (not yet an official real-data reference path)

  • LinRoodPPMScheme <: AbstractAdvectionScheme — cubed-sphere FV3/Lin-Rood PPM path

  • AbstractLimiter subtypes: NoLimiter, MonotoneLimiter, PositivityLimiter, CW84Limiter (PPM only)

Multi-tracer optimization:

  • TracerView — zero-cost 3D slice adapter for 4D tracer arrays

  • Multi-tracer kernel shells fuse the N-tracer loop into GPU kernels, reducing launches from 6N to 6 per Strang split

Infrastructure:

  • AdvectionWorkspace + strang_split! — Strang splitting orchestrator

  • CFL utilities for subcycling decisions

source
AtmosTransport.Operators.Advection.AbstractAdvectionScheme Type
julia
AbstractAdvectionScheme

Root abstract type for all advection operators in the mass-flux transport core.

Every concrete scheme belongs to one of three reconstruction families:

julia
AbstractAdvectionScheme
├── AbstractConstantScheme    (order 0: donor-cell / upwind)
├── AbstractLinearScheme      (order 1: van Leer slopes / MUSCL)
└── AbstractQuadraticScheme   (order 2: PPM / Prather moments)

This hierarchy enables orthogonal dispatch:

  • Reconstruction order selects the face-flux @inline function

  • Limiter type (carried as a type parameter) selects the slope/moment limiter

  • Grid topology is handled by the kernel shell (structured vs face-indexed)

  • CS execution style is handled separately (strang_split_cs! sweep shell vs Lin-Rood / FV3 horizontal update)

  • Backend (CPU/GPU) is handled by KernelAbstractions.jl

Implementing a new scheme

  1. Subtype one of the three families

  2. Implement _xface_tracer_flux, _yface_tracer_flux, _zface_tracer_flux (see reconstruction.jl)

  3. The universal kernel shells in structured_kernels.jl will automatically dispatch to your face-flux functions at compile time

Example

julia
scheme = SlopesScheme(MonotoneLimiter())
strang_split!(state, fluxes, grid, scheme; workspace=ws)
source
AtmosTransport.Operators.Advection.AbstractBinaryCFLCheck Type
julia
AbstractBinaryCFLCheck

Whether a cubed-sphere split-sweep step that takes its subcycle count from the transport binary's substep schedule recomputes the runtime CFL budget and compares. Set from [advection] check_binary_cfl; carried by CSAdvectionWorkspace.

source
AtmosTransport.Operators.Advection.AbstractConstantScheme Type
julia
AbstractConstantScheme <: AbstractAdvectionScheme

Piecewise-constant (order 0) reconstruction family.

The face value equals the donor cell mean — the cell upwind of the mass flux. This is the simplest conservative finite-volume scheme and the reference implementation for the generic kernel shells.

Concrete subtypes: UpwindScheme

source
AtmosTransport.Operators.Advection.AbstractLimiter Type
julia
AbstractLimiter

Policy object controlling slope and moment limiting in linear and quadratic advection schemes.

Limiters are carried as type parameters on scheme structs (e.g., SlopesScheme{MonotoneLimiter}), enabling compile-time specialization with zero runtime branches on GPU.

Available limiters:

See limiters.jl for the @inline implementations.

source
AtmosTransport.Operators.Advection.AbstractLinearScheme Type
julia
AbstractLinearScheme <: AbstractAdvectionScheme

Piecewise-linear (order 1) reconstruction family (van Leer 1977, MUSCL).

The subcell profile in each cell is q(x)=q¯+sx(x−xc) where sx is a limited slope. The face flux is the Courant-fraction weighted integral of this profile over the swept volume (see _slopes_face_flux in reconstruction.jl).

Concrete subtypes: SlopesScheme

source
AtmosTransport.Operators.Advection.AbstractQuadraticScheme Type
julia
AbstractQuadraticScheme <: AbstractAdvectionScheme

Piecewise-quadratic (order 2) reconstruction family.

Includes PPM (Colella & Woodward 1984; Putman & Lin 2007) and Prather second-moment schemes. The subcell profile is a parabola constrained by the cell mean and (limited) edge values.

Concrete subtypes: PPMScheme

source
AtmosTransport.Operators.Advection.AbstractVerticalReconstruction Type
julia
AbstractVerticalReconstruction

How PPMScheme reconstructs the tracer profile in the vertical sweep. The horizontal sweeps are unaffected.

source
AtmosTransport.Operators.Advection.AdvectionWorkspace Type
julia
AdvectionWorkspace{FT, A, V1, A4}

Pre-allocated buffers for mass-flux Strang splitting. Eliminates all array allocations from the inner time-stepping loop.

The workspace provides TWO complete buffer pairs ((rm_A, m_A) and (rm_B, m_B)) used as ping-pong source/destination by strang_split!. Each directional sweep reads from one pair and writes to the other; the palindrome's six sweeps flip parity an even number of times, so the caller's home arrays receive the final result naturally.

Kernels always write to a DIFFERENT array than they read from — this is the double-buffer contract, and in-place updates would violate the stencil's read-before-write assumption and break mass conservation by ~10% per step.

Fields

  • rm_A::A, rm_B::A — 3D tracer-mass ping-pong pair (same size as rm)

  • m_A::A, m_B::A — 3D air-mass ping-pong pair (same size as m)

  • cluster_sizes::V1 — per-latitude clustering factors for reduced grids (Int32[Ny]; all ones for uniform grids; empty for face-indexed meshes)

  • face_left::V1, face_right::V1 — face connectivity for face-indexed meshes

  • rm_4d_A::A4, rm_4d_B::A4 — 4D tracer-mass ping-pong pair for the multi-tracer fused path ((Nx, Ny, Nz, Nt)). Both are allocated to size 0×0×0×0 when n_tracers == 0.

Constructors

julia
AdvectionWorkspace(m::AbstractArray{FT,3}; cluster_sizes_cpu=nothing, n_tracers=0)

Create workspace for a 3D structured grid, allocating both buffer pairs matching the size of m. If cluster_sizes_cpu is nothing, defaults to uniform (all ones).

julia
AdvectionWorkspace(m::AbstractArray{FT,2}; cluster_sizes_cpu=nothing, mesh=nothing)

Create workspace for a 2D face-indexed grid (cell × level layout).

source
AtmosTransport.Operators.Advection.AdvectionWorkspace Method
julia
AdvectionWorkspace(state::CellState; cluster_sizes_cpu=nothing, mesh=nothing)

Construct a workspace sized for state: infers n_tracers from ntracers(state) so the 4D ping-pong buffers match the packed tracer storage. This is the preferred form; the raw AdvectionWorkspace(m; n_tracers=…) is kept for low-level callers.

source
AtmosTransport.Operators.Advection.BinaryCFLCheck Type
julia
BinaryCFLCheck()

Recompute the palindrome CFL subcycle count at every step and throw an ArgumentError when the binary's schedule asks for fewer subcycles. One extra reduction over all panels per step; a diagnostic for binaries of unknown origin.

source
AtmosTransport.Operators.Advection.CSAdvectionWorkspace Type
julia
CSAdvectionWorkspace{FT, A, P3, A4, P4, C}

Pre-allocated cubed-sphere transport workspace.

  • rm_A, m_A are the halo-padded single-tracer advection ping-pong buffers shared across panels.

  • rm_4d_A is the packed-tracer panel buffer used by the production split-sweep path so CS follows the same packed tracers_raw paradigm as structured grids.

  • m_pp_buf, rm_4d_pp_buf are full-panel spare buffers for the packed ping-pong path, avoiding the per-sweep copy-back kernels.

  • seam_flux holds one air and N-tracer transfer per physical panel edge; its storage scales with edge length, not panel area. Lin–Rood workspaces disable this split-sweep cache (seam_transport=false).

  • column_scratch is the per-column, per-tracer working storage of the FV3 vertical profile (Nc × Nc × (Nz+1) × 3 max(Nt_s, 1) G, Nt_s = column_scratch_tracers, the packed tracer count by default, and G = column_scratch_panels the panels one packed sweep launch covers, by default _fv3_sweep_panel_group(Nc, Nt_s)); it is empty unless the workspace is built with column_scratch=true.

  • max_subcycles tracks this workspace's high-water mark for CFL diagnostics; keeping it with the workspace prevents unrelated simulations sharing state.

  • binary_cfl_check (NoBinaryCFLCheck() by default, or BinaryCFLCheck()) decides whether a subcycle count from the binary is checked against the runtime CFL budget.

source
AtmosTransport.Operators.Advection.CW84Limiter Type
julia
CW84Limiter <: AbstractLimiter

The complete Colella & Woodward (1984) PPM, for PPMScheme only.

Three changes from PPMScheme(MonotoneLimiter()):

  1. Edge values use van Leer-limited slopes (CW84 eqs. 1.7–1.8), χi+1/2=(χi+χi+1)/2−(δχi+1−δχi)/6 with δχi=minmod((χi+1−χi−1)/2,2(χi+1−χi),2(χi−χi−1)), so each edge lies between its two neighbouring cell means. In smooth regions this is the fourth-order edge of the default.

  2. The profile is limited with the same monotonicity conditions (eq. 1.10).

  3. The face flux integrates the donor parabola over the swept fraction (eq. 1.12) instead of using the outflow edge as a linear slope.

Every limited parabola lies between neighbouring cell means, so a sweep keeps a non-negative tracer non-negative as long as no cell exports more than its air mass (the fractions swept out through its two faces sum to at most 1). In uniform one-dimensional flow with Courant number up to 1 the scheme is monotone. The stencil is the same six cells as the default, so halo widths do not change.

source
AtmosTransport.Operators.Advection.FV3ScalarProfile Type
julia
FV3ScalarProfile(; positive_definite = true) <: AbstractVerticalReconstruction

The vertical profile of FV3's tracer remapping, scalar_profile with kord = 8 (fv_mapz.F90), which GEOS-Chem High Performance uses for tracers (kord_tr = 8, positive definite, iv = 0). With positive_definite = false it is FV3's profile for signed fields (iv = 1): the three non-negativity steps below are omitted and the profile is symmetric under q → −q, except where neighbouring layer means are exactly equal (FV3 resolves such ties with its local-minimum branch whatever the sign). Use it for tracers that become negative, such as flux anomalies; the positive-definite profile flattens layers with a non-positive mean. Each layer carries the parabola

julia
q(s) = q_L + s [(q_R − q_L) + q_6 (1 − s)],   0 ≤ s ≤ 1 from layer top to bottom,

whose mean is the layer mean. The edge values q_L, q_R solve FV3's compact tridiagonal system, weighted by layer air mass, and are then limited: large-scale constraints at every edge (non-negative at local minima), Huynh's second constraint and positivity in layers 3 … Nz−2, the standard PPM monotonicity limiter in layers 2 and Nz−1, and a monotone, non-negative profile in the top and bottom layers.

The flux through an interface is the air-mass flux times the mean of the donor layer's parabola over the swept fraction α = |F| / m_donor (Courant number ≤ 1): the bottom fraction of the layer above for downward flow, the top fraction of the layer below for upward flow. Each interface flux is evaluated once and enters both layers with opposite signs, so only the rounding of the cell updates changes the column tracer mass, in every precision.

Available on cubed-sphere grids. See docs/src/theory/vertical_transport.md.

source
AtmosTransport.Operators.Advection.LinRoodPPMScheme Type
julia
LinRoodPPMScheme{ORD, Z} <: AbstractAdvectionScheme
LinRoodPPMScheme(ORD = 5; vertical = UpwindScheme())

Cubed-sphere Lin-Rood / FV3-style cross-term PPM advection with compile-time edge-value family ORD (not a global spatial or temporal accuracy order).

This is distinct from PPMScheme: PPMScheme participates in the standard Strang split implemented by strang_split_cs!, while LinRoodPPMScheme selects the FV3-style horizontal Lin-Rood update (fv_tp_2d_cs!). Its vertical sweeps use vertical: first-order UpwindScheme() (default) or PPMScheme(; vertical = FV3ScalarProfile()), FV3's kord = 8 profile (TOML vertical = "fv3_kord8"). The low-level Julia constructor retains its API default of upwind; the TOML runtime defaults configured Lin–Rood runs to fv3_kord8 for the closest GCHP match.

Supported orders currently match the implemented PPM edge-value families in ppm_subgrid_distributions.jl:

  • ORD = 5 — Colella–Woodward fourth-order edge interpolation with extremum flattening

  • ORD = 7 — ORD=5 interior with special cubed-sphere face treatment

Examples

julia
LinRoodPPMScheme()    # default ORD=5, upwind vertical
LinRoodPPMScheme(7)   # ORD=7 cubed-sphere boundary treatment
LinRoodPPMScheme(7; vertical = PPMScheme(; vertical = FV3ScalarProfile()))
source
AtmosTransport.Operators.Advection.MonotoneLimiter Type
julia
MonotoneLimiter <: AbstractLimiter

Van Leer minmod limiter (van Leer 1977; Sweby 1984).

Limits the centered slope against the two one-sided differences scaled by 2, using the three-argument minmod function:

s=minmod(ci+1−ci−12,2(ci+1−ci),2(ci−ci−1))

This is TVD (total variation diminishing) and preserves monotonicity. TM5 does not use this limiter: its slopes advection carries the slope as a prognostic moment and only clamps it to |rxm|≤rm.

source
AtmosTransport.Operators.Advection.NoAdvection Type
julia
NoAdvection <: AbstractAdvectionScheme

Identity scheme — apply! is a no-op. Default for runs that need to isolate other operators (convection, chemistry, diagnostics) from the advective transport block, e.g. a "convection-alone" timing experiment or a synthetic-state regression.

When NoAdvection is selected:

  • The advection sweeps are skipped on all three topologies (LatLonMesh, ReducedGaussianMesh, CubedSphereMesh).

  • Diffusion is allowed: with no advection to wrap, the V(dt) diffusion step is applied directly through the mass-flux VMR kernels, which are mass-conserving on their own and need no Strang palindrome. This is the natural "diffusion-only" experimental setup.

  • Surface emissions are rejected with an actionable ArgumentError. They are integrated as Strang half-steps wrapping the advection block, so running them without advection silently drops 2nd-order accuracy. To run a convection-only configuration omit [tracers.*.surface_flux] blocks.

  • The CS workspace is auto-allocated by the TransportModel constructor when diffusion is active (and is nothing when diffusion is also off).

Example

julia
scheme = NoAdvection()
source
AtmosTransport.Operators.Advection.NoBinaryCFLCheck Type
julia
NoBinaryCFLCheck()

Trust the binary's substep schedule (the default). The preprocessor already checks it when it writes the binary.

source
AtmosTransport.Operators.Advection.NoLimiter Type
julia
NoLimiter <: AbstractLimiter

No limiting applied. The slope is the full centered difference s=(ci+1−ci−1)/2. Second-order accurate but may produce new extrema (oscillations) near sharp gradients.

source
AtmosTransport.Operators.Advection.PPMScheme Type
julia
PPMScheme{L <: AbstractLimiter, V <: AbstractVerticalReconstruction} <: AbstractQuadraticScheme

Piecewise Parabolic Method (Colella & Woodward 1984; Putman & Lin 2007).

Reconstructs a parabolic subcell profile constrained by the cell mean and limited edge values. With the default limiter, in every sweep that uses the shared face flux, the limited edge only sets a linear slope toward the outflow face (Russell–Lerner form), so the update is second order with a PPM-informed slope; it is not monotone. With CW84Limiter the edges are van Leer-limited and the flux integrates the parabola over the swept fraction: the complete Colella–Woodward scheme, which keeps non-negative tracers non-negative in every sweep in which no cell exports more than its air mass.

Implemented for structured latitude-longitude and cubed-sphere grids. The TOML runner selects the monotone variant with scheme = "ppm" and the complete CW84 scheme with limiter = "cw84"; it does not accept ppm_order for this scheme. Reduced-Gaussian face-indexed transport does not support PPM.

Kernel tests and real-input V100 conservation/performance experiments cover this path. These do not establish full-model TM5/GCHP parity or positivity of the complete cubed-sphere update: small negative column means have been observed.

Fields

  • limiter::L — parabolic profile limiting policy (default: MonotoneLimiter(); CW84Limiter() for the complete Colella–Woodward scheme)

  • vertical::V — vertical reconstruction: SameAsHorizontal (default) or FV3ScalarProfile (cubed sphere; TOML vertical = "fv3_kord8", or "fv3_kord8_signed" for signed tracers)

Example

julia
PPMScheme()                                    # monotone-limited PPM
PPMScheme(CW84Limiter())                       # complete CW84 PPM
PPMScheme(NoLimiter())                         # unlimited (may oscillate)
PPMScheme(; vertical = FV3ScalarProfile())     # FV3 kord = 8 vertical profile
source
AtmosTransport.Operators.Advection.PositivityLimiter Type
julia
PositivityLimiter <: AbstractLimiter

Limits the slope to keep the reconstructed face values non-negative: the centered slope keeps its sign and is clipped to |s|≤2ci (s=sign(sc)min(|sc|,2ci)), so ci±s/2≥0.

Weaker than MonotoneLimiter (it does not bound new maxima) but sufficient for species that must remain positive (e.g., tracer mixing ratios). Requires a non-negative tracer; not for signed (anomaly) tracers.

source
AtmosTransport.Operators.Advection.SameAsHorizontal Type
julia
SameAsHorizontal <: AbstractVerticalReconstruction

The vertical sweep uses the same face flux as the horizontal sweeps: a uniform-index fourth-order edge value, limited with the Colella–Woodward (1984) conditions, sets a linear slope toward the outflow face, and the flux takes the Russell–Lerner (1981) form. The parabola is never integrated, so the vertical sweep is second order. The top two and bottom two layers use upwind fluxes. This is the default.

source
AtmosTransport.Operators.Advection.SlopesScheme Type
julia
SlopesScheme{L <: AbstractLimiter} <: AbstractLinearScheme

Van Leer slopes advection with the Russell–Lerner face flux (van Leer 1977; Russell & Lerner 1981).

Reconstructs a piecewise-linear subcell profile in each cell using a limited slope, then integrates the Courant-fraction swept volume to compute the face flux. Second-order accurate in smooth regions.

The slope is re-diagnosed from the neighbouring cell means at every sweep; the scheme carries no moment state. This is not TM5's slopes scheme. TM5 uses the same face flux, but carries the first moments (rxm, rym, rzm) as prognostic tracer state with their own update equations and limits them only for positivity (|rxm|≤rm). With its moment re-diagnosed by the MonotoneLimiter minmod rule, the TM5 update reproduces SlopesScheme() to rounding (test/core/test_slopes_tm5_reference.jl); with the prognostic moment, TM5 is markedly less diffusive. A prognostic-moment scheme is not implemented (see Advection schemes).

The face tracer flux for positive mass flux F>0 (left donor):

Fq=α(rm,L+(1−α)sx,L)

where α=F/mL is the Courant fraction, rm,L is the donor cell tracer mass, and sx,L is the limited first moment sx=m⋅slope(ci−1,ci,ci+1).

See _slopes_face_flux in reconstruction.jl for the full derivation and _limited_slope in limiters.jl for the limiter implementations.

Fields

  • limiter::L — slope/moment limiting policy (default: MonotoneLimiter())

Examples

julia
SlopesScheme()                       # monotone-limited (default)
SlopesScheme(NoLimiter())            # unlimited (2nd order, may oscillate)
SlopesScheme(PositivityLimiter())    # positivity-preserving
source
AtmosTransport.Operators.Advection.TracerView Type
julia
TracerView{FT, A}

Lightweight wrapper around a 4D array (Nx, Ny, Nz, Nt) that presents it as a 3D array (Nx, Ny, Nz) for a fixed tracer index t.

This enables the multi-tracer kernels to call the existing @inline face flux functions (which expect 3D rm arrays) without any code duplication. Julia's compiler inlines the getindex call and eliminates the wrapper entirely — zero overhead on both CPU and GPU.

Example

julia
rm_4d = zeros(Float64, 36, 18, 4, 50)  # Nx, Ny, Nz, Nt
rm_t = TracerView(rm_4d, Int32(3))       # view of tracer 3
rm_t[10, 5, 2]  # equivalent to rm_4d[10, 5, 2, 3]
source
AtmosTransport.Operators.Advection.UpwindScheme Type
julia
UpwindScheme <: AbstractConstantScheme

First-order donor-cell (Godunov) upwind scheme.

The tracer flux through a face is simply the mass flux times the mixing ratio of the upstream cell:

Fq={F⋅cLif F≥0F⋅cRif F<0

where F is the mass flux [kg/s] and c=rm/m is the mixing ratio.

Properties: conservative, monotone, first-order accurate. Strongly diffusive but useful as a reference and for positivity-critical applications.

Example

julia
scheme = UpwindScheme()
source
AtmosTransport.Operators.Advection.apply_divergence_damping_cs! Method
julia
apply_divergence_damping_cs!(rm_panels, m_panels, mesh, ws, damp_coeff)

Conservative del-2 divergence damping on tracer panels. Mass-conserving flux-form Laplacian diffusion on mixing ratio (c = rm/m). Typical damp_coeff values: 0.02-0.05 for mild panel-boundary noise.

source
AtmosTransport.Operators.Advection.apply_linrood_update_adjoint! Method
julia
apply_linrood_update_adjoint!(lambda_rm, lambda_m,
                               lambda_fx_in, lambda_fx_out,
                               lambda_fy_in, lambda_fy_out,
                               lambda_rm_new, lambda_m_new,
                               am, bm, mesh)

Apply the discrete transpose of _linrood_update_kernel! for one panel: accumulate the adjoint of (rm_new, m_new) into the adjoint inputs (rm, m, fx_in, fx_out, fy_in, fy_out) for fixed velocities (am, bm).

All lambda_* adjoint accumulators are read-and-modified (atomically for face arrays) — callers are responsible for initialising them to zero before the call. The kernel only touches interior (i, j) indices 1..Nc; halo cells of lambda_rm/lambda_m and face cells outside 1..Nc+1 are left untouched.

source
AtmosTransport.Operators.Advection.apply_ppm_x_face_adjoint! Method
julia
apply_ppm_x_face_adjoint!(lambda_rm, lambda_m, lambda_fx_face, rm, m, am,
                            mesh, ::Val{ORD})

Discrete transpose of _ppm_x_face_kernel! (LinRood.jl:270) at ORD=5 or ORD=7. Folds _safe_mixing_ratio into the d6-AD chain and includes the donor-mass α = F / m_donor contribution. Atomic accumulation on shared cells.

ORD=7 uses the per-face cube-edge values of _ppm_edge_values_ord7 (seam value at faces 1 and Nc + 1, FV3 one-sided values at faces 0, 2, Nc and Nc + 2) and recomputes the donor α-contribution against the same limited (q_L, q_R). Faces whose stencil is clear of those faces are bit-equal to ORD=5.

source
AtmosTransport.Operators.Advection.apply_ppm_x_face_from_q_adjoint! Method
julia
apply_ppm_x_face_from_q_adjoint!(lambda_q, lambda_fx_face, q, am, m,
                                   mesh, ::Val{ORD})

Discrete transpose of _ppm_x_face_from_q_kernel! (LinRood.jl:299) for one panel at ORD=5 or ORD=7 (LinRoodPPMScheme). The donor-mass denominator m_l/m_r in _ppm_face_value and the velocity am are treated as fixed parameters from the tape — the adjoint propagates only the q-stencil sensitivity. Atomic writes on lambda_q because multiple faces share each cell.

ORD=7 dispatches to a kernel that evaluates the forward per-face cube-edge values (_ppm_edge_values_ord7) on dual numbers before the monotonicity step; faces whose stencil is clear of the faces at and next to the panel edges are bit-equal to ORD=5.

source
AtmosTransport.Operators.Advection.apply_ppm_y_face_adjoint! Method
julia
apply_ppm_y_face_adjoint!(lambda_rm, lambda_m, lambda_fy_face, rm, m, bm,
                            mesh, ::Val{ORD})

Discrete transpose of _ppm_y_face_kernel! (LinRood.jl:241) at ORD=5 or ORD=7. See apply_ppm_x_face_adjoint! for the contract.

source
AtmosTransport.Operators.Advection.apply_ppm_y_face_from_q_adjoint! Method
julia
apply_ppm_y_face_from_q_adjoint!(lambda_q, lambda_fy_face, q, bm, m,
                                   mesh, ::Val{ORD})

Discrete transpose of _ppm_y_face_from_q_kernel! (LinRood.jl:325) for one panel at ORD=5 or ORD=7. See apply_ppm_x_face_from_q_adjoint! for the contract.

source
AtmosTransport.Operators.Advection.apply_pre_advect_x_adjoint! Method
julia
apply_pre_advect_x_adjoint!(lambda_rm, lambda_m, lambda_fx_face,
                              lambda_q_j, rm, m, am, fx_face, mesh)

Discrete transpose of _pre_advect_x_kernel! for one panel. See apply_pre_advect_y_adjoint! for the contract — same structure with X substituted for Y.

source
AtmosTransport.Operators.Advection.apply_pre_advect_y_adjoint! Method
julia
apply_pre_advect_y_adjoint!(lambda_rm, lambda_m, lambda_fy_face,
                              lambda_q_i, rm, m, bm, fy_face, mesh)

Discrete transpose of _pre_advect_y_kernel! for one panel: accumulate lambda_q_i into the adjoint accumulators of (rm, m, fy_face) for fixed velocity bm. The small-m_new zeroing exactly mirrors _safe_mixing_ratio (the _linrood_mass_floor threshold, 100·eps).

All lambda_* accumulators are read-modified; callers initialise them to zero before the call. Face writes use @atomic for shared neighbour-cell accumulation along Y.

source
AtmosTransport.Operators.Advection.copy_corners! Method
julia
copy_corners!(panels, mesh, dir)

Standalone corner fill for 6-panel fields. Rotates corner halo cells according to sweep direction dir (1 = X-sweep, 2 = Y-sweep); with Hp > 0 any other dir throws an ArgumentError.

source
AtmosTransport.Operators.Advection.fill_panel_halos! Method
julia
fill_panel_halos!(panels::NTuple{6}, mesh::CubedSphereMesh; dir=0, sync=true)

Fill halo regions of a 6-panel cubed-sphere field by copying interior data from neighboring panels with correct edge-to-edge orientation mapping.

Each panels[p] must be (Nc + 2Hp) × (Nc + 2Hp) × ... (3-D fields or packed (x, y, z, tracer) storage) with interior at [Hp+1:Hp+Nc, Hp+1:Hp+Nc, ...].

If dir is 1 or 2, corner fill is performed for the given sweep direction (1=X, 2=Y) using the FV3 tp_core rotation formulas.

On a GPU with the default fusion policy (CUDA) this is one launch for all panel edges, a second for the corners when dir is 1 or 2, and one synchronization; on Metal the 24 edges and 6 corner blocks are launched one by one with their panel arrays bound, then synchronized once. With sync = false a device fill is only queued (later work on the same queue sees its result).

source
AtmosTransport.Operators.Advection.fv_tp_2d_cs! Method
julia
fv_tp_2d_cs!(rm_panels, m_panels, am_panels, bm_panels,
              mesh, ::Val{ORD}, ws, ws_lr; damp_coeff=0.0)

Lin-Rood horizontal advection for cubed-sphere grids. Averages X-first and Y-first PPM orderings, then shares the final seam mixing-ratio estimates between neighboring panels before their flux updates. The transverse formulation follows FV3; the explicit seam projection is AtmosTransport's conservative interface coupling.

recorder observes the phases (record_linrood_horizontal!); the production NoStepRecorder() makes every hook a no-op. The cubed-sphere adjoint builds its _CSLinRoodHorizRecord from these hooks.

source
AtmosTransport.Operators.Advection.fv_tp_2d_cs_q! Method
julia
fv_tp_2d_cs_q!(q_panels, m_panels, am_panels, bm_panels,
                 mesh, ::Val{ORD}, ws, ws_lr; damp_coeff=0.0)

Q-space Lin-Rood horizontal advection. Evolves q (mixing ratio) and m (air mass, same role as pressure thickness) in-place. m_panels is both read (for CFL fraction in PPM face values) and written (mass divergence update).

source
AtmosTransport.Operators.Advection.reconstruction_order Method
julia
reconstruction_order(scheme) → Int

Return the polynomial order of the subcell reconstruction:

  • 0 for constant (upwind)

  • 1 for linear (slopes)

  • 2 for quadratic (PPM)

Useful for diagnostics and for selecting stencil widths in multi-tracer kernel fusion.

source
AtmosTransport.Operators.Advection.required_halo_width Method
julia
required_halo_width(scheme) -> Int

Return the minimum cubed-sphere halo width needed by scheme's horizontal stencil. This is a capability query, not a reconstruction-order query: several schemes can share the same polynomial family while using different CS execution paths.

source
AtmosTransport.Operators.Advection.strang_split! Method
julia
strang_split!(state, fluxes, grid, scheme; workspace)

Perform one full Strang-split advection step on a structured mesh.

Splitting sequence

julia
  X → Y → Z → Z → Y → X
  ─────────────────────────
  half    half   full  half    half

When the palindrome outflow budget needs n > 1 subcycles, the step runs (X → Y → Z)^n → midpoint → (Z → Y → X)^n with every flux divided by n (see strang_split_mt!).

All Nt = ntracers(state) tracers are advanced together in a single multi-tracer kernel launch per direction. The mass update is computed once per cell; tracer fluxes are evaluated per-tracer inside the kernel. The per-tracer Julia loop has been eliminated in favour of strang_split_mt! on the packed state.tracers_raw buffer.

Arguments

  • state::CellState — contains air_mass and tracers_raw

  • fluxes::StructuredFaceFluxState — mass fluxes (am, bm, cm)

  • grid::AtmosGrid{<:LatLonMesh} — structured lat-lon grid

  • scheme — advection scheme (AbstractAdvectionScheme)

  • workspace::AdvectionWorkspace — pre-allocated double buffers; use AdvectionWorkspace(state) so the 4D ping-pong buffers are sized for ntracers(state).

source
AtmosTransport.Operators.Advection.strang_split_cs! Method
julia
strang_split_cs!(panels_rm, panels_m, panels_am, panels_bm, panels_cm,
                 mesh, scheme, workspace; flux_scale=1,
                 cfl_limit=DEFAULT_CFL_LIMIT)

Perform one Strang-split advection step on a 6-panel cubed-sphere field with automatic CFL-based subcycling.

Splitting sequence

With n subcycles and every flux divided by n:

julia
repeat n times:                ← forward half, (X Y Z)^n
    [fill_panel_halos!(dir=1)] ← from the second pass on: Z changed the interiors
    X sweep
    → fill_panel_halos!(dir=1) ← exchange halos between panels (X direction)
    → Y sweep
    → fill_panel_halos!(dir=2) ← exchange halos between panels (Y direction)
    → Z sweep
midpoint!()                    ← diffusion / surface flux, once
repeat n times:                ← reverse half, (Z Y X)^n
    Z sweep
    → fill_panel_halos!(dir=2)
    → Y sweep
    → fill_panel_halos!(dir=2)
    → fill_panel_halos!(dir=1)
    → X sweep
    → fill_panel_halos!(dir=1)

The first X sweep reads the halos as the caller leaves them: production strang_split! and the adjoint replays refresh them with fill_cs_step_start_halos! at every step start.

At n = 1 this is Strang's X-Y-Z-Z-Y-X palindrome. For n > 1 the composition stays symmetric (the reverse half is the adjoint of the forward half), so it keeps the palindrome's second-order splitting with the same 6n sweeps. Second-order splitting requires sufficiently accurate subflows; the composition alone does not prove the transport's temporal order. Horizontal groups pair physical seam transfers before halo exchange supplies neighboring values for the next reconstruction.

Subcycling

The count comes from the palindrome budget 2 * (out_x + out_y + out_z) / (n min(m_start, m_end)) ≤ cfl_limit, m_end the cell's mass after the step (_cs_static_palindrome_subcycle_count). A supplied subcycle_count uses the binary's schedule; a workspace built with binary_cfl_check = BinaryCFLCheck() ([advection] check_binary_cfl = true) checks it against the runtime budget.

Panel array layout

Each panel's rm and m arrays are (Nc+2Hp, Nc+2Hp, Nz) with Hp-wide halos. Interior cells are at indices [Hp+1:Hp+Nc, Hp+1:Hp+Nc, :]. The sweep kernels only update interior cells; halo regions are filled by fill_panel_halos! from adjacent panels.

Arguments

  • panels_rm, panels_m: NTuple{6} of 3D arrays (Nc+2Hp, Nc+2Hp, Nz) — tracer mass and air mass. Modified in-place.

  • panels_am, panels_bm, panels_cm: NTuple{6} of flux arrays. am[Nc+2Hp+1, Nc+2Hp, Nz], bm[Nc+2Hp, Nc+2Hp+1, Nz], cm[Nc+2Hp, Nc+2Hp, Nz+1]. Read-only.

  • mesh: CubedSphereMesh with Nc, Hp, and panel connectivity.

  • scheme: advection scheme — UpwindScheme() uses gamma-clamped upwind for tracer face transfers. SlopesScheme() and PPMScheme() use the generic KA kernels with _xface_tracer_flux dispatch. Higher-order schemes require mesh.Hp ≥ 2 (Slopes) or mesh.Hp ≥ 3 (PPM).

  • workspace: pre-allocated CSAdvectionWorkspace buffers.

  • flux_scale: overall scaling applied to all fluxes (default 1.0).

  • cfl_limit: palindrome outflow budget per subcycle, as a fraction of the smaller of the cell's masses at the start and end of the step (default DEFAULT_CFL_LIMIT, 0.95).

source
AtmosTransport.Operators.Advection.strang_split_linrood_ppm! Method
julia
strang_split_linrood_ppm!(rm_panels, m_panels, am_panels, bm_panels, cm_panels,
                           mesh, ::Val{ORD}, ws, ws_lr; cfl_limit=DEFAULT_CFL_LIMIT,
                           damp_coeff=0.0)

Full 3D advection: Horizontal(LR) → Z → Z → Horizontal(LR).

source
AtmosTransport.Operators.Advection.strang_split_mt! Method
julia
strang_split_mt!(rm_4d, m, am, bm, cm, scheme, ws)

Multi-tracer Strang-split advection on a packed 4D tracer array.

This is the performance-optimized path: all Nt = size(rm_4d, 4) tracers are processed in a SINGLE kernel launch per sweep direction (6 total), rather than 6 × Nt launches in the per-tracer path.

The mass update (m_new = m + flux_in - flux_out) is computed ONCE per cell per sweep, shared across all tracers.

Subcycling

One count n comes from the palindrome outflow budget 2 (out_x + out_y + out_z) / (n min(m, m_end)) ≤ cfl_limit (m_end the cell's mass after the step) that the cubed sphere uses (_structured_palindrome_subcycle_count, default cfl_limit = DEFAULT_CFL_LIMIT). The step then runs the symmetric composition

julia
(X Y Z)^n → midpoint operators → (Z Y X)^n,     every pass with flux / n,

6n sweeps, second order, and exactly X Y Z | midpoint | Z Y X at n = 1.

Arguments

  • rm_4d::AbstractArray{FT,4} — tracer mass (Nx, Ny, Nz, Nt), mutated

  • m::AbstractArray{FT,3} — air mass (Nx, Ny, Nz), mutated

  • am, bm, cm — mass fluxes (x, y, z directions)

  • scheme::AbstractAdvectionScheme — advection scheme

  • ws::AdvectionWorkspace — workspace with 4D buffer allocated

source

Convection ​

AtmosTransport.Operators.Convection Module
julia
Convection

Convective transport operators.

This submodule ships the convection-operator hierarchy:

  • AbstractConvection — supertype for all convective transport operators.

  • NoConvection — identity no-op; default for configurations without active convection. Dispatch is a compile-time dead branch in TransportModel.step! so the presence of the convection block has zero cost for users who don't opt in.

Operators:

  • ConvectionForcing in ..MetDrivers carries the convective forcing arrays (with copy_convection_forcing!, allocate_convection_forcing_like, and window-struct integration).

  • CMFMCConvection (GCHP path, CMFMC+DTRAIN kernel with mandatory CFL sub-cycling and well-mixed sub-cloud).

  • TM5Convection (TM5 column solver) with TM5Workspace, running on all three topologies (LL / RG / CS).

The step!-level runtime block wires convection across all three topologies (LL / RG / CS).

apply! contract

julia
apply!(state::CellState{B},
       forcing::ConvectionForcing,
       grid::AtmosGrid,
       op::AbstractConvection,
       dt::Real;
       workspace) where {B <: AbstractMassBasis}

The operator takes ConvectionForcing directly (not a transport window or driver). _refresh_forcing! populates model.convection_forcing at the first substep of each window by copying from sim.window.convection. TransportModel.step! executes the convection block between transport and chemistry. No meteo kwarg — the forcing arrays are the time information; the operator does not call current_time.

Face-indexed scope

CMFMCConvection now supports structured LatLon, face-indexed ReducedGaussian, and panel-native CubedSphere state layouts. The CS path keeps forcing panel-native too: the driver loads cmfmc / dtrain as per-panel tuples and the operator applies the same column-local logic on each panel interior.

TM5Convection ships the same three-topology scope.

The no-op NoConvection path accepts any state shape — it's a pure dead branch.

source
AtmosTransport.Operators.Convection.AbstractCloudBase Type
julia
CMFMCEdgeCloudBase()
ArchivedCloudBase()

Where CMFMCConvection puts the convective cloud base, below which the column is mixed before the updraft starts. CMFMCEdgeCloudBase (default) takes the lowest layer with updraft inflow through its bottom edge. ArchivedCloudBase takes it from the met forcing (forcing.cloud_base): GEOS-Chem's lowest layer with convective rain production DQRCU > 0 (convection_mod.F90, DO_RAS_CLOUD_CONVECTION). Cubed sphere only.

source
AtmosTransport.Operators.Convection.CMFMCConvection Type
julia
CMFMCConvection(; clamp=false, cloud_base=CMFMCEdgeCloudBase())

Explicit, conservative convection driven by per-layer convective mass flux and detrainment from ConvectionForcing. Supported on lat-lon, reduced-Gaussian, and cubed-sphere states. Setting clamp=true applies the positivity correction and conservative column rescaling used for strong-CFL forcing. cloud_base selects the cloud-base rule (see AbstractCloudBase).

source
AtmosTransport.Operators.Convection.CMFMCMatrixConvection Type
julia
CMFMCMatrixConvection(; tile_workspace_gib = 1.0,
                        use_collab_lu = false,
                        lmax_conv = 0,
                        n_merge = 1)

GEOS-derived rates through the TM5 LU solver. The kwarg-tuned knobs all pass through to the inner TM5Convection that performs the column solve — see its docstring for tile_workspace_gib, use_collab_lu, lmax_conv, and n_merge semantics.

Forcing requirements

forcing.cmfmc and forcing.dtrain must be populated on the GEOS binary's basis (whatever state.air_mass is — dry for our production runtime). forcing.tm5_fields is ignored. The operator runs once-per-met-window derivation to convert (cmfmc, dtrain) → (entu, detu, 0, 0), caching the result in CMFMCMatrixWorkspace for reuse across all substeps.

Conservation contract

Σ(m·q) is preserved to floating-point roundoff for any inert tracer: the backward-Euler matrix has unit column sums once Σ entu = Σ detu, which the derivation kernels guarantee by absorbing any boundary residual at the surface layer. The solve therefore preserves the sum of its right-hand side up to rounding.

Adjoint

The derivation is constant in the state vector — it depends only on forcings — so the state-space adjoint is exactly the inner TM5 LU adjoint applied to the cached derived rates. See Adjoints/ConvectionAdjoint.jl.

source
AtmosTransport.Operators.Convection.CMFMCMatrixWorkspace Type
julia
CMFMCMatrixWorkspace{FT, TS, EU, ED, V}

Per-sim pre-allocated workspace for CMFMCMatrixConvection.

Fields

  • tm5_workspace :: TS — the underlying TM5Workspace carrying the column-tile matrix slab, pivots, and TM5 cell-area metrics.

  • derived_entu :: EU — derived updraft entrainment rate, same shape and basis as forcing.dtrain (LL: (Nx,Ny,Nz), RG: (ncells,Nz), CS: NTuple{6, (Nc,Nc,Nz)}). Refreshed on every met-window advance via invalidate_cmfmc_matrix_cache!; reused bit-exact across all substeps within a window.

  • derived_detu :: EU — derived updraft detrainment rate, same shape as derived_entu. Includes the explicit GEOS dtrain plus any negative-entrainment residual folded in to keep entu ≥ 0.

  • zero_entd :: ED — immutable zeros, same shape as derived_entu. Passed as the TM5 downdraft entrainment field (CMFMC has no downdraft in our binary contract).

  • zero_detd :: ED — immutable zeros, same shape (TM5 downdraft detrainment).

  • derived_valid :: V — Base.RefValue{Bool} flagging whether the cached derivation matches the current met window. Cleared via invalidate_cmfmc_matrix_cache! on window advance.

Lifecycle

The derived rates are computed once per met-window from forcing.cmfmc + forcing.dtrain by _launch_cmfmc_matrix_derivation! (see cmfmc_matrix_kernels.jl). Within a window, apply! reuses the cached arrays directly — the LU solve sees the same (entu, detu, 0, 0) quadruplet every substep.

source
AtmosTransport.Operators.Convection.CMFMCMatrixWorkspace Method
julia
CMFMCMatrixWorkspace(air_mass; tile_workspace_gib = nothing,
                      tile_columns = nothing,
                      cell_metrics = nothing,
                      halo_width = 0) -> CMFMCMatrixWorkspace

Construct a fresh workspace from an air-mass payload. air_mass may be a single array (LL / RG) or a panel tuple (CS). The inner TM5Workspace is built with the same tile-budget rules as a bare TM5Workspace; the derived-rate slabs are sized to mirror the layout of forcing.dtrain (LL/RG: same as air_mass; CS: halo-stripped from air_mass).

cell_metrics is the topology cell-area metric (mandatory in production — the TM5 matrix needs it to convert kg-per-cell air mass to the kg/m² basis the convective fluxes share).

halo_width is the per-panel halo half-width Hp for cubed-sphere payloads (the production CS factory passes grid.horizontal.Hp). LL and RG leave it at 0.

source
AtmosTransport.Operators.Convection.CMFMCWorkspace Type
julia
CMFMCWorkspace{FT, QC, CA, L}

Per-sim pre-allocated workspace for CMFMCConvection.

Fields

  • qc_scratch :: QC — updraft concentration buffer, one entry per cell, shape matching air_mass ((Nx, Ny, Nz) for structured grids, (ncells, Nz) for face-indexed grids). Reused across all substeps and all tracers.

  • cell_metrics :: CA — pre-adapted cell-area metric vector used by the CFL scan and the kernel: cell_areas_by_latitude(mesh) for structured grids, per-cell cell_area(mesh, c) for face-indexed grids.

  • cached_n_sub :: Base.RefValue{Int} — the CFL-derived sub-step count for the current met window. Stays valid as long as cache_valid[] == true; re-computed from the current CMFMC / air-mass state on the next apply! call when invalid. Int (not Float) because it's the integer sub-step count.

  • cache_valid :: Base.RefValue{Bool} — sentinel; cleared via invalidate_cmfmc_cache! when the met window advances (DrivenSimulation._maybe_advance_window!).

  • ledger_carry :: Base.RefValue{L} — persistent state of the column mass ledger, as in TM5Workspace: per column and tracer, the part of the last update's rounding residual that the column's largest layer could not hold. Empty until the first apply! sizes it with zeros; not stored in restarts.

Usage

Constructed once at DrivenSimulation setup time and reused for the whole run. Adapt.adapt_structure preserves the cached scalar state on the host while adapting qc_scratch to the requested backend.

source
AtmosTransport.Operators.Convection.NoConvection Type
julia
NoConvection()

Identity operator — apply! is a no-op. Default for configurations without active convection. Dispatch is a compile-time dead branch in TransportModel.step!, so the convection block collapses to zero floating-point work when no operator is installed (bit-exact backward- compatible with the no-op behavior).

source
AtmosTransport.Operators.Convection.TM5Convection Type
julia
TM5Convection(; tile_workspace_gib=1.0, use_collab_lu=false,
                lmax_conv=0, n_merge=1)

TM5-style convective transport operator. Four-field mass-flux scheme following Tiedtke (1989) as implemented in TM5-4DVAR: two entrainment and two detrainment fields (updraft + downdraft). The backward-Euler transport matrix conv1 = I - dt·D is dense within the cloud window and identity above; the solver assembles and factorizes only the active lower-right cloud block. The legacy workspace retains its pivot vector for supported adjoint replay; collaborative factors live only in shared memory.

use_collab_lu selects the workgroup-collaborative solver. lmax_conv=0 uses all vertical levels; a positive value limits the active lower atmosphere. n_merge optionally aggregates adjacent active layers before the solve (see Level merging below). Float32 GPU matrices support effective depths 1..85. Float64 CUDA supports unmerged (n_merge=1) depths 1..73, using Float64 shared arrays and arithmetic. CPU and unsupported Float64 configurations retain the legacy solver with a warning. Tracers use batches of six; total tracer count does not set this depth limit. Defaults remain the full-column legacy solve. CS adjoint footprints still require the full-column, unmerged legacy variant.

The forcing arrays (entu, detu, entd, detd) arrive via TransportModel.convection_forcing.tm5_fields, populated at the first substep of each window by DrivenSimulation._refresh_forcing! from sim.window.convection.

Level merging (n_merge > 1)

An AtmosTransport approximation; TM5 itself merges levels once, in its met preprocessing, and never reconstructs a fine profile. Each group s of n_merge adjacent fine layers k becomes one super-layer with tracer mass R_s = Σ_k r_k, air mass M_s = Σ_k m_k, and summed forcings. After the backward-Euler solve on the super grid gives R'_s, each fine layer receives a share of the super-layer's change:

julia
r'_k = r_k + (R'_s - R_s) * w_k,   w_k = |r_k| / Σ_{j∈s} |r_j|

with w_k = m_k / M_s if Σ_{j∈s} |r_j| = 0, and w_k = 1 / n_merge if M_s = 0 as well.

The weights lie in [0, 1] and sum to one. The split conserves the column total, keeps a uniform mixing ratio, leaves columns and layers without convection unchanged bit for bit, and changes no cell by more than |R'_s - R_s|. If a super-layer's fine values share one sign, it equals the proportional split r'_k = R'_s (r_k / R_s) and is evaluated in that form, so non-negative tracers stay non-negative in floating point as well. A column mass ledger over the whole merged operation returns the split's rounding to the column (see the ledger_carry of TM5Workspace). Only super-layers holding values of both signs can increase the column's Σ|r|: by at most 2 Σ_{s mixed} |R'_s - R_s| in total. The split is not linear in the tracer; merged solves have no adjoint (CS footprints require n_merge = 1).

Memory budget

tile_workspace_gib (binary GiB) is the per-topology target for the TM5 column-tile workspace. _convection_workspace_for(::TM5Convection, ...) reads this field and derives a tile size B via derive_tile_columns; the TM5Workspace uses a single (Nz, Nz, B) matrix slab plus matching pivot, cloud-dimension, and flux-vector slabs. A larger budget means fewer legacy kernel launches at the cost of more memory. When collaborative LU is requested, global scratch is deferred because the kernels use shared memory. Unsupported configurations allocate the configured tile on first use and reuse it. The default legacy budget is 1.0 GiB.

Basis convention

TM5Convection is basis-polymorphic, identical to CMFMCConvection. The four forcing fields must be on the same basis as state.air_mass. ECMWF and TM5 archive convection fields are total-air (moist) fluxes upstream; maintained preprocessors convert them to the binary's declared basis before writing and record that basis as tm5_convection_mass_basis. Opening a binary rejects a recorded mismatch. Legacy dry binaries without that key warn because their unconverted moist fluxes make rates about 1 % too large.

Fields required on ConvectionForcing

  • forcing.tm5_fields :: NamedTuple{(:entu, :detu, :entd, :detd)} with all four arrays at layer centers in AtmosTransport orientation (k=1=TOA, k=Nz=surface). Units kg / m² / s. Shapes per topology:
    • Structured LatLon: (Nx, Ny, Nz) per field.

    • Face-indexed ReducedGaussian: (ncells, Nz) per field.

    • Panel-native CubedSphere: NTuple{6, AbstractArray{FT, 3}} per field, with per-panel shape (Nc, Nc, Nz).

Orientation conversion + sign flip on entd happen in the preprocessor (src/Preprocessing/tm5_convection_conversion.jl). The operator performs zero runtime orientation gymnastics.

Solver class

Partial-pivot Gaussian elimination on the active lower-right block per column (see test/core/test_tm5_sparsity_above_icltop.jl for the structure survey). Identity rows above the effective cloud top and the lower-left zero quadrant are skipped by both the factorization and the tracer solve.

Pivoting is kept even though the matrix is diagonally dominant by construction (upstream Fortran comment says pivoting "not needed"). The pivot vector is stored in TM5Workspace so the adjoint can replay the same factorization with trans='T'.

CFL sub-cycling

None. The backward-Euler matrix solve is unconditionally stable for any dt, unlike CMFMCConvection's forward-Euler two-pass update which requires sub-cycling when the CMFMC profile is strong. The kernel launches once per tile and calls synchronize(backend) once per apply!.

source
AtmosTransport.Operators.Convection.TM5Workspace Type
julia
TM5Workspace{FT, M, P, C, F, A, CA, L}

Per-sim pre-allocated workspace for TM5Convection.

Fields

  • conv1 :: M — conv1 = I - dt·D matrix slab, one (Nz, Nz) block per column. Parametric on array type so Adapt.adapt_structure can swap CPU ↔ GPU without changing the TM5Workspace type constructor. Shapes per topology:

    • Structured LatLon: (Nz, Nz, Nx, Ny) — 4D.

    • Face-indexed ReducedGaussian: (Nz, Nz, ncells) — 3D.

    • Panel-native CubedSphere: NTuple{6, AbstractArray{FT, 4}} with per-panel shape (Nz, Nz, Nc, Nc).

  • pivots :: P — permutation vector from partial-pivot LU, one Nz-length Int slice per column. Preserved so the adjoint can replay the same factorization with transposed back-substitution. Shapes strip the leading Nz from conv1 shape: (Nz, Nx, Ny) / (Nz, ncells) / NTuple{6, (Nz, Nc, Nc)}.

  • cloud_dims :: C — per-column (icltop, iclbas, icllfs) triple in AtmosTransport indexing (k=1=TOA, k=Nz=surface; the preprocessor delivers forcings in this orientation so the solver has zero orientation logic). Shape (3, Nx, Ny) / (3, ncells) / NTuple{6, (3, Nc, Nc)}.

  • f_scratch :: F — per-column intermediate matrix for the matrix build (TM5 f(0:lmx, 1:lmx), with the updraft merged into f). Pre-allocated so _tm5_build_conv1! runs without heap allocation inside KA kernels (mandatory on GPU; same contract on CPU for parity). f_scratch aliases conv1 in the production workspace because conv1 is only needed after f has been converted into I - dt*D; this saves one dense (Nz, Nz) slab per column. There is no standalone fu_scratch field — the updraft and downdraft passes write disjoint index ranges, so the builder writes directly into f.

  • amu_scratch :: A, amd_scratch :: A — length-(Nz+1) per-column boundary-aware mass-flux vectors (TM5 amu(0:lmx) / amd(0:lmx)). Same allocation policy as the scratch matrices.

  • cell_metrics :: CA — pre-adapted cell-area metrics used to convert runtime air_mass from kg per grid cell to the kg/m² column mass expected by the TM5 matrix. nothing is allowed only for standalone unit-area column tests; topology-level apply! requires this field to be populated.

  • ledger_carry :: L — persistent state of the column mass ledger: per column and tracer, the part of the last solve's rounding residual that the column's largest cell could not hold (about half its ulp at most; see _ledger_deposit). Shape (Nx, Ny, Nt) / (ncells, Nt) / NTuple{6, (Nc, Nc, Nt)}. The constructor allocates it empty, with the topology's storage type; the first apply! sizes it with zeros, and a call with another tracer layout starts from zero again. Restarts do not store it: a restarted run drops about half an ulp of each column's largest cell, once.

Usage

Constructed once at DrivenSimulation setup time via _convection_workspace_for(::TM5Convection, state, grid). Adapt.adapt_structure adapts each array to the requested backend without reallocating on the host.

source
AtmosTransport.Operators.Convection.TM5Workspace Method
julia
TM5Workspace(air_mass; tile_columns = …,
                        tile_workspace_gib = nothing,
                        cell_metrics = nothing,
                        defer_scratch = false) -> TM5Workspace

Construct a fresh workspace from an air-mass payload. air_mass may be a single array (structured (Nx, Ny, Nz) or face-indexed (ncells, Nz)) or a cubed-sphere panel tuple — the workspace itself is topology-agnostic. The per-column element type of conv1 matches eltype(air_mass).

The per-launch column count B is set by exactly one of:

  • tile_columns::Integer (explicit). Default _tm5_total_cells_per_launch(air_mass) — one tile covers the whole launch and the workspace is bit-equal to the earlier per-cell allocator. Production paths use this branch when they already know B.

  • tile_workspace_gib::Real (budget). Picks B via derive_tile_columns from TM5Convection's tile_workspace_gib field. Bypassed if tile_columns is also passed (explicit wins).

  • cell_metrics carries topology cell areas. Production _convection_workspace_for(::TM5Convection, ...) supplies this; leaving it nothing is only for unit-area solver tests.

Specifying both tile settings is an error. defer_scratch=true retains the configured tile size but allocates no legacy matrix payload until a legacy solve actually needs it. Adaptation drops this recomputable scratch while preserving cell metrics; each backend allocates its own tile on demand.

source
AtmosTransport.Operators.Convection.apply_convection! Method
julia
apply_convection!(q_raw, air_mass, forcing::ConvectionForcing,
                   ::NoConvection, dt, workspace, grid) -> nothing

Array-level no-op, parallels the the diffusion and surface-flux apply! pattern. Accepts any shape of q_raw / air_mass — NoConvection doesn't inspect them. Returns nothing.

The structured apply! flow goes through the state-level method above. apply_convection! is reserved for the future case where the convection block is called from inside a palindrome or another composed setting — same signature contract as the diffusion and surface-flux array entry points.

source
AtmosTransport.Operators.Convection.apply_convection! Method
julia
apply_convection!(q_raw, air_mass, forcing::ConvectionForcing,
                   op::TM5Convection, dt, workspace::TM5Workspace,
                   grid::AtmosGrid) -> nothing

Array-level entry point — parallels the CMFMC contract at operators.jl:70-89. Dispatches on grid mesh type and launches the matching KA kernel from tm5_kernels.jl. Single synchronize(backend) at the end (TM5 matrix solve is unconditionally stable; no sub-cycling).

source
AtmosTransport.Operators.Convection.derive_tile_columns Method
julia
derive_tile_columns(::Type{FT}, Nz, budget_gib, total_cells) -> Int

Pick a tile size B from a per-cell memory cost and a target budget. The cost model accounts for every per-cell field that TM5Workspace tiles:

  • conv1 : Nz² × sizeof(FT)

  • amu_scratch + amd_scratch : 2(Nz+1) × sizeof(FT)

  • pivots : Nz × sizeof(Int)

  • cloud_dims : 3 × sizeof(Int)

f_scratch is a structural alias for conv1 (saves one matrix slab per cell). B is clamped between 256 (avoid pathological launches) and total_cells (one tile covers the whole topology when the budget allows it).

source
AtmosTransport.Operators.Convection.invalidate_cmfmc_cache! Method
julia
invalidate_cmfmc_cache!(ws::CMFMCWorkspace) -> nothing

Mark the cached CFL n_sub as stale. Called on met-window advance by DrivenSimulation._maybe_advance_window!. The next apply! recomputes n_sub from the fresh CMFMC array.

source
AtmosTransport.Operators.Convection.invalidate_cmfmc_matrix_cache! Method
julia
invalidate_cmfmc_matrix_cache!(ws::CMFMCMatrixWorkspace) -> nothing
invalidate_cmfmc_matrix_cache!(::Any) -> nothing

Mark the derived (entu, detu) cache stale so the next apply! re-derives from forcing.cmfmc + forcing.dtrain. Called on met-window advance from DrivenSimulation._maybe_advance_window! via the shared invalidate_cmfmc_cache! dispatch.

source

Diffusion ​

AtmosTransport.Operators.Diffusion Module
julia
Diffusion

Vertical-diffusion operator hierarchy and solver infrastructure.

Public surface:

  • NoDiffusion — identity no-op; the default when no [diffusion] config section is present.

  • ImplicitVerticalDiffusion — Backward-Euler implicit diffusion driven by an AbstractTimeVaryingField Kz. Wired into the Strang palindrome via apply_vertical_diffusion_vmr! and installed into TransportModel.diffusion by the runtime recipe when [diffusion] kind = "constant".

Both subtype the global AbstractDiffusion declared in src/Operators/AbstractOperators.jl; concrete operator structs live in operators.jl. The column-level Thomas solve (solve_tridiagonal!) is exposed as the numerical reference. Generic Kz kernels name the tridiagonal coefficients (a, b, c) explicitly. Precomputed Dkg mass transport uses column-conservative bidiagonal factors in conservative_dkg.jl; both matching transposes live in src/Adjoints/DiffusionAdjoint.jl.

source
AtmosTransport.Operators.Diffusion.AbstractSurfaceFluxCoupling Type
julia
AbstractSurfaceFluxCoupling

Typed policy for where configured surface fluxes enter relative to a vertical diffusion/mixing operator.

  • SplitSurfaceFluxCoupling: existing Strang-center composition, V(dt/2) -> S(dt) -> V(dt/2).

  • DiffusiveSurfaceFluxBoundary: GCHP/VDIFF-style lower-boundary placement, S(dt) -> V(dt), so fresh surface flux is included in the implicit vertical mixing solve.

source
AtmosTransport.Operators.Diffusion.DiffusiveSurfaceFluxBoundary Type
julia
DiffusiveSurfaceFluxBoundary()

Inject surface mass immediately before one full implicit vertical diffusion solve, so the new surface mass participates in that solve.

source
AtmosTransport.Operators.Diffusion.ImplicitVerticalDiffusion Type
julia
ImplicitVerticalDiffusion(; kz_field)

Backward-Euler vertical diffusion driven by either a cell-centered Kz field or an exact precomputed TM5 interface dkg field. Two spatial layouts are supported:

  • structured: AbstractTimeVaryingField{FT, 3} over (Nx, Ny, Nz)

  • face-indexed: AbstractTimeVaryingField{FT, 2} over (ncells, Nz)

Concrete examples:

  • ConstantField{FT, 3} / ConstantField{FT, 2}

  • ProfileKzField{FT} with default rank 3 or ProfileKzField(profile; spatial_rank = 2)

  • PreComputedKzField{FT, A} wrapping 3D or 2D storage

  • DerivedKzField for meteorology-driven Beljaars-Viterbo on structured grids

  • CubedSphereField wrapping one structured rank-3 field per panel

apply! contract

julia
apply!(state, meteo, grid, op::ImplicitVerticalDiffusion, dt; workspace)
  • Refreshes the Kz cache with update_field!(op.kz_field, current_time(meteo)); meteo = nothing uses t = 0 for standalone calls.

  • For Kz fields, reads workspace.layer_thickness as the current layer thicknesses [m]. The caller is responsible for filling this array before calling apply! — typically from a hydrostatic integration of the current delp and surface temperature.

  • PrecomputedCSDkgField bypasses Kz/geometry reconstruction and does not read layer_thickness; its interface exchange [kg s⁻¹] is already complete.

  • Uses workspace.factors for Thomas elimination or Dkg mass-retention factors. The Dkg mass path does not use the packed reference scratch.

  • Launches a topology-specific mass-flux kernel. Packed layouts factor each atmospheric column once and advance every tracer with those factors.

The spatial operator is linear, but Backward Euler is not a semigroup: V(dt) and V(dt/2) ∘ V(dt/2) differ by O(dt²). The surface-coupling policy therefore selects the timestep composition explicitly.

Fields

  • kz_field::KzF — any AbstractTimeVaryingField{FT, 2} or AbstractTimeVaryingField{FT, 3} providing cell-centered Kz values [m²/s geometric], or a PrecomputedCSDkgField.
source
AtmosTransport.Operators.Diffusion.NoDiffusion Type
julia
NoDiffusion()

Identity operator — apply! is a no-op. Default for configurations without active vertical mixing, and the value strang_split_mt! sees when the palindrome's V position is unoccupied.

source
AtmosTransport.Operators.Diffusion.SplitSurfaceFluxCoupling Type
julia
SplitSurfaceFluxCoupling()

Apply surface flux at the transport-palindrome center, bracketed by two half-step diffusion solves.

source
AtmosTransport.Operators.Diffusion.apply_vertical_diffusion_vmr! Function
julia
apply_vertical_diffusion_vmr!(rm, air_mass, op, workspace, dt, meteo = nothing;
                              [halo_width, tracer]) -> nothing

Apply one Backward-Euler diffusion step to tracer mass rm (in and out): structured (Nx, Ny, Nz, Nt), face-indexed (ncells, Nz, Nt) or (ncells, Nz), or six halo-padded cubed-sphere panels (Nc + 2Hp, Nc + 2Hp, Nz[, Nt]) with halo_width = Hp. This is the entry point of the transport palindrome and of the cubed-sphere adjoint replays. For Kz fields the column solve gives the new mixing ratio and the tracer mass is updated in flux form, rm_k += F_{k−½} − F_{k+½}, so each column total changes only by unbiased rounding (diffusion_kernels.jl). Precomputed cubed-sphere dkg fields use the conservative mass solve of conservative_dkg.jl.

NoDiffusion is a no-op method, so call sites compile to nothing.

source
AtmosTransport.Operators.Diffusion.apply_vertical_diffusion_vmr! Method
julia
apply_vertical_diffusion_vmr!(rm, air_mass, op, workspace, dt, meteo;
                              halo_width, tracer = nothing)

Cubed-sphere helper for state variables stored as tracer mass. The implicit solver uses the current dry air mass. Precomputed Dkg uses conservative bidiagonal factors directly on tracer mass. Kz fields solve for the mixing ratio and update the tracer mass in flux form before advection resumes.

The Dkg solve keeps a column mass ledger whose sub-ulp remainder persists in workspace.ledger_carry, one value per column and tracer (see _ledger_deposit). Packed tracers ((N, N, Nz, Nt) panels) use carry slot t for tracer t. A single-tracer panel tuple uses slot tracer, its index in the state; with tracer = nothing it keeps no carry, so the call is a function of its input alone (adjoint tapes and replays rely on this). Other fields ignore tracer.

source
AtmosTransport.Operators.Diffusion.fill_dz_hydrostatic_constT! Method
julia
fill_dz_hydrostatic_constT!(dz, ps, ak_ifc, bk_ifc;
                             T_ref = HYDROSTATIC_REFERENCE_TEMPERATURE,
                             R = R_DRY_AIR, gravity = STANDARD_GRAVITY)

Populate a 3D (Nx, Ny, Nz) dz array (host or device) from surface pressure ps and hybrid sigma-pressure interface coefficients ak_ifc, bk_ifc (length Nz + 1). Backend follows dz.

source
AtmosTransport.Operators.Diffusion.fill_dz_hydrostatic_constT! Method
julia
fill_dz_hydrostatic_constT!(dz::AbstractArray{<:Any, 2},
                             ps::AbstractArray{<:Any, 1},
                             ak_ifc, bk_ifc; ...)

Face-indexed variant for ReducedGaussian topology: dz shape (ncells, Nz), ps shape (ncells,). Same constant-T_ref formula as the structured/CS overloads, just unrolled over the face-indexed cell axis.

source
AtmosTransport.Operators.Diffusion.fill_dz_hydrostatic_constT! Method
julia
fill_dz_hydrostatic_constT!(dz_panels::NTuple{6}, ps_panels::NTuple{6},
                             ak_ifc, bk_ifc; ...)

Cubed-sphere variant: per-panel 3D (Nc, Nc, Nz) dz arrays are filled from per-panel ps arrays of shape (Nc, Nc) (interior only — the runtime stores surface_pressure without the advection halo).

source
AtmosTransport.Operators.Diffusion.fill_dz_hydrostatic_virtualT! Method
julia
fill_dz_hydrostatic_virtualT!(dz, t_lyr, qv_lyr, ps, ak_ifc, bk_ifc;
                               R = R_DRY_AIR, gravity = STANDARD_GRAVITY)

Populate a 3D (Nx, Ny, Nz) dz array using virtual temperature per layer: T_v = T · (1 + VIRTUAL_TEMPERATURE_FACTOR · qv) (factor 0.61). Matches the geometry the LocalHoltslagBovilleKzField uses for its column-mid heights, closing the inconsistency between solver dz and Kz-cache dz.

t_lyr, qv_lyr are layer-center 3D fields with shape (Nx, Ny, Nz), typically pulled from the active window's VDIFF payload.

source
AtmosTransport.Operators.Diffusion.fill_dz_hydrostatic_virtualT! Method
julia
fill_dz_hydrostatic_virtualT!(dz_panels::NTuple{6}, t_panels::NTuple{6},
                               qv_panels::NTuple{6}, ps_panels::NTuple{6},
                               ak_ifc, bk_ifc; ...)

Cubed-sphere variant of fill_dz_hydrostatic_virtualT!. Per-panel 3D (Nc, Nc, Nz) dz/t_lyr/qv_lyr arrays + per-panel (Nc, Nc) ps. The VDIFF payload is panel-native, so passing the panel tuples directly is the natural runtime shape.

source
AtmosTransport.Operators.Diffusion.recover_total_surface_pressure! Method
julia
recover_total_surface_pressure!(dest, ps_dry, qv, A, B)

Recover total surface pressure on the array backend used by dest. For the hybrid interfaces p_{k+1/2}=A_{k+1/2}+B_{k+1/2}p_{s,total}, stored dry surface pressure satisfies

ps,dry=∑k(1−qv,k)[ΔAk+ΔBkps,total],ps,total=ps,dry−∑k(1−qv,k)ΔAk∑k(1−qv,k)ΔBk.

The implementation uses KernelAbstractions for CPU and the package's supported CUDA and Metal backends; device arrays are never scalar-indexed by the host. For an A-only coordinate (B ≡ 0) recovery is unobservable and unnecessary, so ps_dry is copied unchanged. Otherwise the humidity-weighted B slope must be finite and positive or the function throws ArgumentError.

source
AtmosTransport.Operators.Diffusion.solve_tridiagonal! Method
julia
solve_tridiagonal!(x, a, b, c, d, w)

Solve the tridiagonal linear system T x = d in place, where T has sub-diagonal a, main diagonal b, super-diagonal c. By convention a[1] and c[Nz] are ignored (no-neighbor positions).

Argument roles:

  • x::AbstractVector{FT} — output, overwritten with the solution.

  • a, b, c, d::AbstractVector{FT} — read only, not mutated.

  • w::AbstractVector{FT} — caller-supplied workspace, length ≥ Nz. Holds the Thomas forward-elimination factors w[k] = c[k] / denom used during back-substitution.

Implements the standard Thomas algorithm with one extra array (w) so that b and d can be read-only. For per-column Nz this is Θ(Nz) arithmetic, no allocation.

Adjoint note (this routine is forward-only)

The matrix transpose T^T is obtained by swapping shifted sub- and super-diagonals:

julia
a_T[k] = c[k - 1]     # for k ≥ 2
b_T[k] = b[k]
c_T[k] = a[k + 1]     # for k ≤ Nz - 1

The CS adjoint kernel _vertical_diffusion_cs_single_adjoint_kernel! in src/Adjoints/DiffusionAdjoint.jl builds (a_T, b_T, c_T) inline and runs an inlined Thomas (rather than reusing this routine) so the kernel stays allocation-free.

source

SurfaceFlux ​

Source containers, the surface-flux operator, and the emission totals (total_emission_rate, emission_segments, integrated_emission) used by logs and budget diagnostics. Emission totals gives the equations.

AtmosTransport.Operators.SurfaceFlux Module
julia
SurfaceFlux

Surface emission operators.

Ships the data types and helpers needed to apply per-tracer surface sources to a CellState:

  • SurfaceFluxSource{RateT} — single-tracer source plus a rate array in model-storage units per second per cell, also available from the top-level AtmosTransport module. Physical inventories in kg species/s are converted to these units by the runtime's basis-aware input builders.

  • PerTracerFluxMap{S} — NTuple-backed map of SurfaceFluxSources, keyed by tracer_name. Ships with the flux_for(map, :name) lookup helper. Storage-bits-stable on GPU.

The AbstractSurfaceFluxOperator hierarchy (NoSurfaceFlux, SurfaceFluxOperator), the _surface_flux_kernel! KA kernel, and the apply! / apply_surface_flux! entry points live alongside these data types.

A TimeVaryingSurfaceFluxSource keeps its rate series in host memory. Its device copy is a small window buffer (FluxWindowBuffer) holding only the slices that the current met window reads; stage_surface_flux_window! fills it when a window becomes current, and the kernels read the rates from it.

Emission totals for logs and budgets come from the operator's own temporal reconstruction: total_emission_rate(src, t) (global storage rate at an instant), emission_segments(src, t, dt) (the pieces one step applies) and integrated_emission(src, t0, t1, dt) (the mass added over a run's steps).

Surface layer convention

All kernels here assume k = Nz is the surface. This matches src/Models/DrivenSimulation.jl and the LatLon grid storage layout. A future AbstractLayerOrdering{TopDown, BottomUp} refactor can generalise this.

source
AtmosTransport.Operators.SurfaceFlux.AbstractFluxTemporalScheme Type
julia
AbstractFluxTemporalScheme

How a TimeVaryingSurfaceFluxSource reconstructs a per-step rate from its stored time slices at t = current_time(meteo) (and step dt). Concrete schemes implement

julia
_flux_temporal_weights(scheme, times, t, dt) -> (i0, i1, w0, w1)

returning two bracketing slice indices and blend weights (w0 + w1 == 1) applied as w0·series[i0] + w1·series[i1], together with

julia
_flux_temporal_segments(scheme, times, t, dt) -> [(i0, i1, w0, w1, frac), …]
_slices_read_at(scheme, times, τ) -> (first, last)

the pieces of one step [t, t + dt], and the first and last slice index that an evaluation at the instant τ reads (non-decreasing in τ; the device window buffer of a time-varying source holds the slices between those of a window's start and end). New reconstructions (e.g. mass-flux-integral matching, higher-order in time) are added as new subtypes and these methods — no kernel or operator change.

source
AtmosTransport.Operators.SurfaceFlux.AbstractSurfaceFluxOperator Type
julia
AbstractSurfaceFluxOperator

Top of the surface emission operator hierarchy. Concrete subtypes:

Every concrete subtype implements two entry points:

  • State-level apply!(state, meteo, grid, op, dt; workspace) → mutates state.tracers_raw.

  • Array-level apply_surface_flux!(q_raw, op, ws, dt, meteo, grid; tracer_names) → mutates a raw tracer buffer directly. Supported layouts are:

  • structured packed: (Nx, Ny, Nz, Nt)

  • face-indexed packed: (ncells, Nz, Nt)

  • face-indexed single-tracer slice: (ncells, Nz)

  • cubed-sphere single-tracer panels: NTuple{6} of (Nc + 2Hp, Nc + 2Hp, Nz) Used by the structured multi-tracer palindrome and the reduced-Gaussian face-indexed transport block.

source
AtmosTransport.Operators.SurfaceFlux.AbstractSurfaceFluxSource Type
julia
AbstractSurfaceFluxSource

Supertype for all per-tracer surface emission sources consumed by the surface-flux operator and stored in a PerTracerFluxMap.

Concrete subtypes:

  • SurfaceFluxSource — a single static per-cell rate array.

  • TimeVaryingSurfaceFluxSource — a time series of per-cell rate slices plus a sorted times vector; the operator linearly interpolates to the simulation clock at apply! time.

Every subtype carries a tracer_name :: Symbol field used by flux_for to key the map.

source
AtmosTransport.Operators.SurfaceFlux.ConservativeMeanFlux Type
julia
ConservativeMeanFlux()

Window/step integral-conserving: evaluate the linear reconstruction at the step centre t + dt/2. Summed over the substeps of a met window, this midpoint quadrature reproduces the exact trapezoidal time-integral of a piecewise-linear flux — i.e. the emitted mass per window matches the source's own integral, independent of the substep count.

source
AtmosTransport.Operators.SurfaceFlux.LinearInterpFlux Type
julia
LinearInterpFlux()

Linear interpolation between the two bracketing slices, point-evaluated at the step's current_time(meteo). HEMCO-like treatment of instantaneous 3-hourly fields. Default scheme.

source
AtmosTransport.Operators.SurfaceFlux.NoSurfaceFlux Type
julia
NoSurfaceFlux()

Identity operator — apply! is a no-op. Default for configurations without surface emissions, and the value strang_split_mt! sees when the palindrome's S position is unoccupied.

NoSurfaceFlux's apply! is literally = state (and the array-level apply_surface_flux! is = nothing). Julia's dispatch turns the call site into a dead branch with zero floating-point work and a bit-exact no-op.

source
AtmosTransport.Operators.SurfaceFlux.PerTracerFluxMap Type
julia
PerTracerFluxMap{S <: Tuple}

An ordered tuple of SurfaceFluxSources, keyed by the tracer_name field on each entry. Supplies the surface flux operator with per-tracer surface-rate data and ensures efficient tuple-splatting at kernel-launch time.

The map is NTuple-backed rather than Dict-backed. Rationale:

  • Runtime source construction naturally produces a small tuple; the map keeps that storage and adds a lookup helper.

  • Tuples are bits-stable on GPU (captured by kernels without boxing / hashing); Dicts would require special Adapt handling and per-launch lookup cost.

  • Small-N (typically 1-5 emitting tracers) makes linear scan cheaper than hashing anyway.

Tracers absent from the map have zero surface flux. The consumer operator iterates the map and applies each source; it does NOT walk the state's full tracer_names and look up each by name.

Construction

julia
co2_rate  = fill(2.0e-7, Nx, Ny)    # model-storage units/s per cell
sf6_rate  = fill(1.5e-9, Nx, Ny)    # already converted from physical flux
rn222_rate = fill(3.0e-11, Nx, Ny)

map = PerTracerFluxMap(
    SurfaceFluxSource(:CO2,   co2_rate),
    SurfaceFluxSource(:SF6,   sf6_rate),
    SurfaceFluxSource(:Rn222, rn222_rate),
)

length(map)                 # 3
flux_for(map, :CO2) === ... # the :CO2 SurfaceFluxSource
flux_for(map, :CH4)         # nothing (not emitting)

Adapt / GPU

Adapt.adapt(CuArray, map) adapts each source through its adapt_structure; tuples adapt element-wise. A SurfaceFluxSource moves its cell_mass_rate to the device. A TimeVaryingSurfaceFluxSource moves only its compensation and its small window buffer; its rate series stays in host memory.

Fields

  • sources :: S — NTuple{N, <:AbstractSurfaceFluxSource} for some N. Each entry carries a tracer name and its rate data (a static array for SurfaceFluxSource, a time series for TimeVaryingSurfaceFluxSource).
source
AtmosTransport.Operators.SurfaceFlux.PerTracerFluxMap Method
julia
PerTracerFluxMap(sources::AbstractVector{<:AbstractSurfaceFluxSource})
PerTracerFluxMap(sources::Tuple)

Generic collection constructor. The input is frozen into an NTuple.

source
AtmosTransport.Operators.SurfaceFlux.PerTracerFluxMap Method
julia
PerTracerFluxMap(sources::AbstractSurfaceFluxSource...)

Variadic constructor: PerTracerFluxMap(src1, src2, src3) wraps the three sources into an NTuple-backed map.

source
AtmosTransport.Operators.SurfaceFlux.StepwiseFlux Type
julia
StepwiseFlux()

Piecewise-constant in time: emit the value of the slice block containing t (the largest slice time ≤ t), with no blending. This holds each 3-hourly CAMS value constant across its block — the "change the flux in 3h blocks" option.

source
AtmosTransport.Operators.SurfaceFlux.SurfaceFluxOperator Type
julia
SurfaceFluxOperator(flux_map::PerTracerFluxMap)
SurfaceFluxOperator(sources::SurfaceFluxSource...)

Applies a PerTracerFluxMap of surface sources to the k = Nz slab of the conservative tracer-storage array.

For every tracer named in the flux map, the operator launches the layout-appropriate surface-flux kernel and adds rate × dt to the surface layer k = Nz of the matching tracer:

  • structured packed state (Nx, Ny, Nz, Nt) → _surface_flux_kernel! over (Nx, Ny)

  • face-indexed packed state (ncells, Nz, Nt) → _surface_flux_face_kernel! over ncells

Tracer indices are resolved on the host from state.tracer_names. Tracers absent from the map are untouched.

The rate is a model-storage amount per second per cell (already area-integrated); no cell-area multiplier appears in the kernel. File-based physical kg-species rates are converted by the source builder before they reach this operator.

apply! contract

julia
apply!(state, meteo, grid, op::SurfaceFluxOperator, dt; workspace)
  • Walks op.flux_map in storage order.

  • For each source, skips tracers not present in state.

  • Dispatches to the array-level entry point with tracer_names pulled from the state.

  • Synchronises the backend once at the end.

Fields

  • flux_map :: M — a PerTracerFluxMap of SurfaceFluxSources. Emitting tracers are exactly those named in the map.
source
AtmosTransport.Operators.SurfaceFlux.SurfaceFluxOperator Method
julia
SurfaceFluxOperator(sources::SurfaceFluxSource...)

Convenience constructor: wraps a variadic list of sources in a PerTracerFluxMap first. Empty variadic list is allowed (produces an empty-map operator; equivalent to NoSurfaceFlux except type- distinguishable).

source
AtmosTransport.Operators.SurfaceFlux.SurfaceFluxSource Type
julia
SurfaceFluxSource{RateT}

A single-tracer surface source: a tracer_name plus a cell_mass_rate array supplying model-storage amount added per cell per second to the surface layer.

  • tracer_name :: Symbol — matches a name in CellState.tracer_names.

  • cell_mass_rate :: RateT — one of:

    • a 2D (Nx, Ny) array for structured grids

    • a 1D (Nc,) array for face-indexed grids

    • an NTuple{6} of 2D (Nc, Nc) arrays for cubed-sphere panels

    The rates are already area-integrated. For dry-VMR tracers, file-based physical fluxes in kg species/s are converted by the source builder to dry-air-equivalent storage units before reaching this struct. The surface flux kernel applies rm_surface += rate × dt without multiplying by cell area.

Why per-cell rates (not kg/m²/s)

The prognostic tracer is stored per cell, so a per-cell rate × dt is the natural unit. A per-area variant would need to multiply by cell area before entering the operator.

Provenance

The source lives with SurfaceFluxOperator; model and simulation layers only compose and schedule the operator.

Fields

  • tracer_name :: Symbol

  • cell_mass_rate :: RateT — backend-agnostic; Adapt.adapt converts the array between host and device transparently via Adapt.adapt_structure.

source
AtmosTransport.Operators.SurfaceFlux.SurfaceLayerDeposit Type
julia
SurfaceLayerDeposit()
ProfileDeposit(profile)

Vertical placement of the mass emitted into a cubed-sphere column during one step. SurfaceLayerDeposit adds it to the surface layer k = Nz with Kahan compensation. ProfileDeposit spreads it over the column with per-layer fractions profile[i, j, k] that sum to one: the non-local PBL transport of fresh surface emissions in GEOS-Chem's VDIFF (the counter-gradient term ∂(ρ K γ)/∂z, linear in the surface flux). A fraction may be negative: that layer loses tracer to the layers above.

Of a column's emission x, the counter-gradient redistribution moves ck=x(fk−δkN) into layer k (fk the fraction, N=Nz the surface layer). As in GEOS-Chem's qmincg check, the column keeps all of x in the surface layer instead when that redistribution would make a layer negative or a negative layer more negative,

∃k:ck<0andqk+ck<0,

with qk the tracer before the step's emission. A layer the redistribution raises or leaves unchanged (ck≥0) never triggers the fallback, so the roundoff negatives that transport leaves in zero-background tracers do not. GEOS-Chem tests qk+ck<0 on its bottom npbl layers, which hold every nonzero ck; it also falls back on a layer that was negative before the step and that the redistribution does not lower.

Both placements add exactly x to the column. Upper-layer additions are tracked with _two_sum, and the surface layer receives the emission minus what landed above through the Kahan update, so the column gains exactly the emitted mass.

source
AtmosTransport.Operators.SurfaceFlux.TimeVaryingSurfaceFluxSource Type
julia
TimeVaryingSurfaceFluxSource{RateT, T, S, CompT, BufT}

A single-tracer surface source whose per-cell rate advances through a time series of slices with the simulation clock. Used to carry the CAMS/LMDZ 3-hourly diurnal cycle (instead of collapsing it to a monthly mean), the daily and hourly cs_native biosphere drivers, and to match GeosChem.

  • tracer_name :: Symbol — matches a name in CellState.tracer_names.

  • cell_mass_rate_series :: RateT — a stack of static rate slices, in the same per-cell model-storage units as SurfaceFluxSource. For cubed-sphere this is an NTuple{6} of (Nc, Nc, ntime) arrays (one stacked panel per face). The trailing dimension is time. The series stays where it was built (host memory for every loader): Adapt does not move it, so a multi-year series costs no device memory.

  • times :: T — slice times in seconds since run start, length ntime, sorted strictly ascending. Stays a host Vector even after Adapt-to-device so the operator can searchsortedlast it on the host before launching the interpolated kernel.

  • scheme :: S — an AbstractFluxTemporalScheme selecting how the stored slices are reconstructed onto the simulation clock (StepwiseFlux, LinearInterpFlux, or ConservativeMeanFlux). A singleton, carried through Adapt unchanged.

  • compensation :: CompT — Kahan compensation, one slice in shape, on the compute backend.

  • window_buffer :: BufT — a FluxWindowBuffer: the slices the current met window reads, on the compute backend. Adapt moves it (and compensation) to the device.

The operator resolves the segments (i0, i1, w0, w1, frac) of _flux_temporal_segments(scheme, times, current_time(meteo), dt) on the host, makes their slices resident in window_buffer (the runner has staged the window's slices before its first step, see stage_surface_flux_window!), then blends the two slices in the kernel. End slices are clamped (constant extrapolation) outside [times[1], times[end]].

source
AtmosTransport.Operators.SurfaceFlux.apply_surface_flux! Function

apply_surface_flux!(q_raw, op::AbstractSurfaceFluxOperator, workspace, dt, meteo, grid; tracer_names)

Array-level surface-flux application. Writes directly to the supplied tracer buffer q_raw, adding each source's rate × dt contribution to the surface slab k = Nz.

Supported layouts:

  • structured packed: q_raw :: (Nx, Ny, Nz, Nt)

  • face-indexed packed: q_raw :: (ncells, Nz, Nt)

  • face-indexed single-tracer slice: q_raw :: (ncells, Nz)

tracer_names::NTuple{Nt, Symbol} is required as a keyword so the function can resolve each source's name to a slab index without reaching back into the caller's CellState. This lets the palindrome integration point the operator at either the caller's state.tracers_raw or the workspace's ping-pong buffer — whichever currently holds the post-Z-sweep tracer state.

workspace, meteo, and grid are accepted but currently unused; they are in the signature to match the operator-interface convention (§"Workflow: Adding a new physics operator" in CLAUDE.md) and to leave room for future extensions (e.g. meteorology-dependent emissions).

Returns nothing on success. For NoSurfaceFlux, returns nothing immediately (zero floating-point work).

source
AtmosTransport.Operators.SurfaceFlux.apply_surface_flux! Method
julia
apply_surface_flux!(q_raw::NTuple{6}, op, workspace, dt, meteo, grid;
                    tracer_names, halo_width, deposit = SurfaceLayerDeposit())

Cubed-sphere surface fluxes on halo-padded panels, single-tracer (Nc + 2Hp, Nc + 2Hp, Nz) (exactly one name in tracer_names) or packed (…, Nz, Nt). deposit places each column's emitted mass: SurfaceLayerDeposit() or an NTuple{6} of per-panel ProfileDeposits.

source
AtmosTransport.Operators.SurfaceFlux.emission_deposit Method
julia
emission_deposit(diffusion_op) -> SurfaceLayerDeposit() or NTuple{6, ProfileDeposit}

Where the diffusion scheme puts fresh surface emissions: the surface layer, except for GEOS-Chem's non-local VDIFF, whose counter-gradient transport spreads them over the boundary layer (GCHPNonlocalPBLField.emission_profile).

source
AtmosTransport.Operators.SurfaceFlux.emission_segments Function
julia
emission_segments(src::AbstractSurfaceFluxSource, t, dt) -> Vector{Tuple{Float64, Float64}}

The pieces in which one surface-flux step [t, t + dt] adds src: pairs (R, τ) of global storage rate (kg s⁻¹) and duration (s) with ∑τ=dt. The step adds ∑Rτ, the global total of what apply_surface_flux! adds cell by cell.

  • SurfaceFluxSource: one piece (R, dt).

  • TimeVaryingSurfaceFluxSource: one piece per segment of _flux_temporal_segments(scheme, times, t, dt). LinearInterpFlux gives one piece, the rate interpolated at the step start t. StepwiseFlux and ConservativeMeanFlux split the step at every slice time inside it and evaluate each piece at its midpoint. This integrates the piecewise-constant or piecewise-linear reconstruction exactly, so their pieces over one long step and over many short steps give the same integral.

Within a piece the rate is constant (StepwiseFlux, LinearInterpFlux) or is the piece mean of a linear reconstruction (ConservativeMeanFlux). Budget diagnostics that integrate a sink alongside the source (radioactive decay over a met window, for example) need these pieces rather than only their sum.

source
AtmosTransport.Operators.SurfaceFlux.emitting_tracer_indices Method
julia
emitting_tracer_indices(op::SurfaceFluxOperator, state::CellState) -> NTuple

Ordered tuple of tracer indices in state.tracer_names for the tracers present in op.flux_map. Tracers in the map but missing from the state are skipped (returned as nothing slots). Useful for testing / introspection and unchanged by apply!.

source
AtmosTransport.Operators.SurfaceFlux.flux_for Method
julia
flux_for(map, tracer_name::Symbol) -> SurfaceFluxSource | nothing

Return the SurfaceFluxSource for the named tracer, or nothing if the tracer has no surface source in this map. O(N) linear scan, which is fine for typical N ≤ 10.

source
AtmosTransport.Operators.SurfaceFlux.flux_temporal_scheme Method
julia
flux_temporal_scheme(name) -> AbstractFluxTemporalScheme

Map a config string to a temporal scheme. "stepwise"/"block" → StepwiseFlux; "linear"/"interp" → LinearInterpFlux; "conservative"/"window_mean"/"integral" → ConservativeMeanFlux.

source
AtmosTransport.Operators.SurfaceFlux.integrated_emission Function
julia
integrated_emission(src::AbstractSurfaceFluxSource, t0, t1, dt) -> Float64

Global storage mass (kg dry-air equivalent) that the surface-flux operator adds from src over the run's steps of length dt between t0 and t1 (seconds since the run start): the steps start at tn=t0+ndt, n=0,…,N−1, with N=(t1−t0)/dt an integer (to rounding), and

E=∑n=0N−1∑(R,τ)∈segments(tn,dt)Rτ,

the pieces of emission_segments. A SurfaceFluxSource gives E=RNdt.

For StepwiseFlux and ConservativeMeanFlux, E is the exact integral of the reconstruction over [t0, t1] and does not depend on dt (up to rounding); one step of length t1 - t0 gives the same value. For LinearInterpFlux, E is the step-start Riemann sum of the interpolated rate and depends on dt: pass the run's advection step.

Slice totals are summed once per call in Float64 on the host series (as for total_emission_rate, a series built on a device is copied one slice at a time) and the step sum is compensated (Neumaier), so a run-long integral carries about one rounding of the total. Throws ArgumentError unless dt, t0 and t1 are finite, dt > 0, t1 ≥ t0, and (t1 - t0) / dt is an integer to a relative √eps(Float64).

source
AtmosTransport.Operators.SurfaceFlux.total_emission_rate Function
julia
total_emission_rate(src::AbstractSurfaceFluxSource, t) -> Float64

Global emission rate of src at the instant t (seconds since the run start): the per-cell model-storage rates summed over all cells, in kg dry-air equivalent s⁻¹ (physical kg species s⁻¹ × DRY_AIR_MOLAR_MASS / M_species).

Cell sums are accumulated in Float64 (pairwise) whatever the storage float type, so a Float32 source gives its total to Float64 accuracy. The sums run on the host, because Metal has no Float64: a static source adapted to a device (CUDA, Metal) is copied to host memory first. A time-varying source keeps its series in host memory on every backend (only its window buffer is on the device), so it is summed in place; a series built directly on a device is copied one slice at a time. The runner logs total_emission_rate(source, 0.0), the rate at the run start.

source

Chemistry ​

AtmosTransport.Operators.Chemistry Module
julia
Chemistry

Source/sink operators for tracer transformations (decay, photolysis, ...).

Type hierarchy:

julia
AbstractChemistryOperator
├── NoChemistry                     — identity / inert tracers
├── ExponentialDecay{FT, N}         — multi-tracer first-order decay
└── CompositeChemistry              — sequential composition

Interface (OPERATOR_COMPOSITION.md §6):

julia
apply!(state::CellState, meteo, grid, op::AbstractChemistryOperator, dt;
       workspace=nothing)

The operator mutates state.tracers_raw in place and returns state. meteo, grid, and workspace are accepted for interface conformance and may be nothing for operators that do not need them (pure decay).

Multi-tracer decay is fused into a single KernelAbstractions kernel — see chemistry_kernels.jl. Tracers not listed in the operator's tracer_names are left untouched.

source
AtmosTransport.Operators.Chemistry.CompositeChemistry Type
julia
CompositeChemistry(schemes...)
CompositeChemistry(schemes::Tuple)

Apply multiple chemistry operators sequentially. Used when different species need independent transformations or when different operator types (decay + photolysis + ...) must run in a prescribed order.

julia
chem = CompositeChemistry(
    ExponentialDecay(; Rn222 = 330_350.4),
    ExponentialDecay(; Kr85  = 3.394e8),
)
source
AtmosTransport.Operators.Chemistry.ExponentialDecay Type

Keyword constructor: ExponentialDecay(; Rn222 = half_life_seconds, ...).

source
AtmosTransport.Operators.Chemistry.ExponentialDecay Type
julia
ExponentialDecay{FT, N, R}(decay_rates, tracer_names)

Multi-tracer first-order decay: c *= exp(-rate * dt) applied in-place to every selected tracer at every cell. Exact for constant rate and any dt; unconditionally stable; trivially parallel.

Fields

  • decay_rates :: R — an NTuple{N, <: AbstractTimeVaryingField{FT, 0}} of rate-valued fields, one per selected tracer [1/s]. apply! calls update_field! on each rate before launching the kernel.

  • tracer_names :: NTuple{N, Symbol} — which tracers this operator applies to

Construction

julia
ExponentialDecay(; Rn222 = 330_350.4)                   # from half-lives [s]
ExponentialDecay(Float32; Rn222 = 330_350.4, Kr85 = 3.394e8)

The keyword constructor converts half-life T to decay rate λ = log(2) / T (first-order exponential decay) and wraps each rate in a ConstantField{FT, 0}. Future plans may pass time-varying rates (e.g. temperature-dependent reaction rates) through the same field interface.

Common isotopes:

  • ²²²Rn: half-life = 330_350.4 s (3.8235 days) → λ ≈ 2.098e-6 s⁻¹

  • ⁸⁵Kr: half-life = 3.394e8 s (10.76 years) → λ ≈ 2.042e-9 s⁻¹

source
AtmosTransport.Operators.Chemistry.NoChemistry Type
julia
NoChemistry()

Identity operator — apply! is a no-op. Default for runs without active chemistry.

source

Step plans ​

The model step as data: the StepPlan that TransportModel.step! and DrivenSimulation execute and the cubed-sphere adjoint replays, its stages, and the stage executors. The step recorders (AbstractStepRecorder, NoStepRecorder) are in the top-level section above. See the forward model the adjoint transposes.

AtmosTransport.Operators.StepPlans Module
julia
StepPlans

The model step as data: which operators run, in which order, with which time step, at every advection step and at the met-window boundaries.

A StepPlan is built once from the run configuration (physics cadence, the transport binary's window contract, the air-mass reset mode). Production executes it: TransportModel.step! runs its step stages and DrivenSimulation runs all four phases. The cubed-sphere adjoint executes the same plan with a tape recorder (AbstractStepRecorder) and walks the recorded tape backwards, so the forward model it differentiates is the production step by construction.

Phases, in the order a met window runs them:

phaseruns
window_advanceon entering a met window after the first (air-mass reset to the new window)
window_startbefore the first step of every met window
stepevery advection step
window_endafter the last step of every met window
source
AtmosTransport.Operators.StepPlans.AbstractPhysicsCadence Type
julia
AbstractPhysicsCadence

When the physics blocks (convection, chemistry) run: every advection step (SubstepPhysics) or once per met window (WindowPhysics).

source
AtmosTransport.Operators.StepPlans.AbstractStageDt Type
julia
AbstractStageDt

The time step a stage integrates over: StepDt (the advection step) or WindowDt (the met window).

source
AtmosTransport.Operators.StepPlans.AbstractStepStage Type
julia
AbstractStepStage

One operator application of a StepPlan. Concrete stages: TransportStage, ConvectionStage, ChemistryBeforeStage, ChemistryAfterStage and AirMassResetStage. execute_stage!(stage, ops, timing, meteo, recorder) applies it.

source
AtmosTransport.Operators.StepPlans.AirMassResetStage Type
julia
AirMassResetStage(mode, target)

Replace the prognostic air mass by the met target (WindowStartAirMass() or WindowEndAirMass()), preserving the tracer mass (:preserve_tracer_mass) or the mixing ratio (:preserve_vmr).

source
AtmosTransport.Operators.StepPlans.ChemistryAfterStage Type
julia
ChemistryAfterStage(StepDt() | WindowDt())

The second half-step of a symmetrically split chemistry operator, or the full step of any other chemistry.

source
AtmosTransport.Operators.StepPlans.ChemistryBeforeStage Type
julia
ChemistryBeforeStage(StepDt() | WindowDt())

The first half-step of a symmetrically split chemistry operator (constant-rate exponential decay); a no-op for other chemistry.

source
AtmosTransport.Operators.StepPlans.ConvectionStage Type
julia
ConvectionStage(StepDt() | WindowDt())

The convection operator over the advection step or the met window.

source
AtmosTransport.Operators.StepPlans.StepDt Type
julia
StepDt()  — the stage integrates over the advection step `dt`.
source
AtmosTransport.Operators.StepPlans.StepOperators Type
julia
StepOperators(; state, fluxes, grid, advection, advection_workspace,
              diffusion = NoDiffusion(), diffusion_workspace = nothing,
              emissions = NoSurfaceFlux(), convection = NoConvection(),
              convection_workspace = nothing, convection_forcing = nothing,
              chemistry = NoChemistry())

The state, the step's face fluxes and the operators with their workspaces that the stages of a StepPlan act on. TransportModel provides them (step_operators(model)); the cubed-sphere adjoint builds them for its single-tracer replay.

source
AtmosTransport.Operators.StepPlans.StepPlan Type
julia
StepPlan(; physics_cadence = nothing, window_contract = false,
         air_mass_reset = :none)

The model step of a run: the stages of its four phases (window_advance, window_start, step, window_end).

  • Without a window contract, or with physics_cadence = :substep, every step runs ChemistryBefore(dt) → Transport → Convection(dt) → ChemistryAfter(dt) (constant-rate decay split symmetrically around transport).

  • With a transport binary's per-window contract (window_contract = true) and physics_cadence = :window (the default), every step runs the transport block only; the window starts with the first decay half over the window and ends with the air-mass reset to the window's end mass, convection over the window and the closing chemistry.

  • air_mass_reset other than :none resets the air mass to each newly entered window (window_advance) and, with a window contract, to the window's end mass before the window-end physics.

TransportModel.step! executes the step stages of the default plan; DrivenSimulation executes all phases of the plan of its configuration; the cubed-sphere adjoint records and transposes the same plan.

source
AtmosTransport.Operators.StepPlans.StepTiming Type
julia
StepTiming(dt, window_dt = dt)

The time steps of one advection step: its own dt and the length window_dt of the met window it belongs to.

source
AtmosTransport.Operators.StepPlans.SubstepPhysics Type
julia
SubstepPhysics()  — convection and chemistry run every advection step.
source
AtmosTransport.Operators.StepPlans.TransportStage Type
julia
TransportStage()

The transport block of one advection step: the step-start halo refresh, the advection palindrome and its center (vertical diffusion and surface fluxes in the diffusion operator's coupling, or one diffusion solve V(dt) without a surface-flux operator).

source
AtmosTransport.Operators.StepPlans.WindowDt Type
julia
WindowDt()  — the stage integrates over the met window `window_dt`.
source
AtmosTransport.Operators.StepPlans.WindowEndAirMass Type
julia
WindowEndAirMass()  — the reset target is the window's authoritative end air mass.
source
AtmosTransport.Operators.StepPlans.WindowPhysics Type
julia
WindowPhysics()  — convection and chemistry run once per met window.
source
AtmosTransport.Operators.StepPlans.WindowStartAirMass Type
julia
WindowStartAirMass()  — the reset target is the air mass of the met window just entered.
source
AtmosTransport.Operators.StepPlans.execute_stage! Function
julia
execute_stage!(stage, ops, timing, meteo, recorder)

Apply one stage to ops.state.

source
AtmosTransport.Operators.StepPlans.execute_stages! Function
julia
execute_stages!(stages::Tuple, ops, timing, meteo, recorder = NoStepRecorder())

Apply stages in order. meteo is the run clock and met context (current_time(meteo), the air-mass reset targets); recorder observes every operator.

source
AtmosTransport.Operators.StepPlans.execute_step_stages! Function
julia
execute_step_stages!(plan, ops, timing, meteo, recorder = NoStepRecorder())
source
AtmosTransport.Operators.StepPlans.execute_window_advance! Function
julia
execute_window_advance!(plan, ops, timing, meteo, recorder = NoStepRecorder())
source
AtmosTransport.Operators.StepPlans.execute_window_end! Function
julia
execute_window_end!(plan, ops, timing, meteo, recorder = NoStepRecorder())
source
AtmosTransport.Operators.StepPlans.execute_window_start! Function
julia
execute_window_start!(plan, ops, timing, meteo, recorder = NoStepRecorder())
source
AtmosTransport.Operators.StepPlans.plan_stages Method
julia
plan_stages(plan) -> Tuple

Every stage of plan, in phase order (window_advance, window_start, step, window_end).

source