Skip to content

Models API

The Models module contains the runtime stepper — TransportModel, DrivenSimulation, the IC pipeline, and the recipe builders that turn a TOML config into a fully-wired runtime object.

AtmosTransport.Models Module
julia
Models

Minimal standalone runtime layer for src.

source
AtmosTransport.Models.DrivenSimulation Type
julia
DrivenSimulation

Low-level window-driven runtime for custom driver/model wiring.

Most TOML-based runs should go through run_driven_simulation, which builds and validates this object for you. Construct DrivenSimulation directly only when you need a custom met driver, callback loop, or model assembly.

A DrivenSimulation keeps transport-window timing and forcing in the driver, while the model retains ownership of prognostic tracer and air-mass state. The runtime interpolates forcing within each met window and advances the model with the same step!(model, Δt) entry point used by the fixed-flux smoke harness.

For experienced users, the constructor is meant to be assembled from the same pieces used by run_driven_simulation: a met driver, a basis-compatible state, empty flux storage owned by the model, and a runtime physics recipe. A minimal LL/RG manual setup looks like this:

julia
using TOML, AtmosTransport
using AtmosTransport.MetDrivers: air_mass_basis, driver_grid, flux_kind

# First run examples/generate_synthetic_quickstart.jl from the terminal.
cfg = TOML.parsefile("config/examples/minimal_template.toml")
paths = expand_binary_paths(cfg["input"])
FT = Float64

driver = TransportBinaryDriver(first(paths); FT = FT, arch = CPU())
recipe = build_runtime_physics_recipe(cfg, driver, FT)
validate_runtime_physics_recipe(recipe, driver)

grid = driver_grid(driver)
window1 = load_transport_window(driver, 1)
Basis = air_mass_basis(driver) === :dry ? DryBasis : MoistBasis
air = copy(window1.air_mass)

vmr = build_initial_mixing_ratio(
    air, grid, Dict("kind" => "uniform", "background" => 400e-6);
    surface_pressure = window1.surface_pressure,
)
co2 = pack_initial_tracer_mass(grid, air, vmr; mass_basis = Basis())

state = CellState(Basis, air; CO2 = co2)
fluxes = allocate_face_fluxes(grid.horizontal, nlevels(grid);
                              FT = FT, basis = Basis)
model = TransportModel(state, fluxes, grid, recipe.advection;
                       diffusion = recipe.diffusion,
                       convection = recipe.convection)

sim = DrivenSimulation(model, driver;
                       stop_window = min(total_windows(driver), 24),
                       chemistry = recipe.chemistry)
run_window!(sim)

The full runner adds the practical edges around this skeleton: resolving many daily binaries, GPU adaptation, surface-flux source construction, snapshot output, progress reporting, and capability checks against every file.

SurfaceFluxSource lives with the surface-flux operator in src/Operators/SurfaceFlux/.

source
AtmosTransport.Models.DrivenSimulation Method
julia
DrivenSimulation(model, driver; kwargs...)

Construct a window-driven src runtime.

Keyword arguments:

  • start_window=1

  • stop_window=total_windows(driver)

  • initialize_air_mass=true

  • use_midpoint_forcing=true

  • interpolate_fluxes_within_window=nothing (derive from driver)

  • air_mass_reset_mode=:preserve_tracer_mass — one of :none, :preserve_vmr, or :preserve_tracer_mass. When non-:none, each newly loaded window replaces prognostic air mass using the selected tracer invariant. For binary-scheduled runs, the same endpoint reset is applied before the once-per-window convection/chemistry block so physics sees the binary's authoritative window-end mass.

  • surface_sources=()

  • chemistry=NoChemistry() — applied after advection + surface sources each step

  • callbacks=NamedTuple()

  • start_time=0 — simulation clock origin [s]. Multi-binary runners MUST pass the accumulated run time here when rebuilding the sim per binary: sim.time feeds current_time(meteo), which time-varying surface-flux sources use to select their emission slice (seconds since the RUN start, not the binary start). Restarting the clock at 0 each day silently replays day-1 fluxes — the December-2021 co2_natural +1 Pg/month surplus (plan 45 Stage-4 A/B experiment attributed the leak to exactly this).

source
AtmosTransport.Models.Simulation Type
julia
Simulation

Low-level fixed-step harness for custom in-memory experiments.

Most users should call run_driven_simulation with a run config. Use Simulation only when you already constructed a TransportModel and want to drive it with a custom time loop.

source
AtmosTransport.Models.TransportModel Type
julia
TransportModel(state, fluxes, grid, advection; kwargs...)

Minimal Oceanigans-style model object for standalone src transport runs.

Carries advection, chemistry, vertical diffusion, surface emissions, and convection operators. The step composition is: transport_block(dt) → convection_block(dt) → chemistry_block(dt)

where transport_block runs the full palindrome with diffusion and emissions at the center:

julia
X  Y  Z  V(dt/2)  S(dt)  V(dt/2)  Z  Y  X      (emissions active)
X  Y  Z  S(dt)  V(dt)  Z  Y  X                  (diffusive boundary coupling)
X  Y  Z  V(dt)  Z  Y  X                          (no emissions)

step!(model, dt) executes the full runtime composition: transport block → convection block → chemistry block.

Defaults chemistry = NoChemistry(), diffusion = NoDiffusion(), emissions = NoSurfaceFlux(), and convection = NoConvection() make the inactive operator slots compile to no-op dispatches.

Convection fields

  • convection :: ConvT — operator type, defaults to NoConvection(). Concrete subtypes are CMFMCConvection and TM5Convection. NoConvection is a compile-time dead branch in step!.

  • convection_forcing :: CF — per-step forcing container. Defaults to ConvectionForcing() (all-nothing placeholder). DrivenSimulation construction allocates real buffers via allocate_convection_forcing_like; _refresh_forcing! populates them from sim.window.convection each substep.

Helpers with_convection(model, op) and with_convection_forcing(model, forcing) parallel with_chemistry / with_diffusion / with_emissions.

source
AtmosTransport.Models.advection_spec Method
julia
advection_spec(section) -> AbstractAdvectionSpec

Parse an [advection] section into a typed spec. ppm_order is only meaningful for scheme = "linrood"; pairing it with scheme = "ppm" is rejected (the split PPM path takes no order knob), matching the old builder.

source
AtmosTransport.Models.build_runtime_chemistry Method
julia
build_runtime_chemistry(cfg, ::Type{FT}) -> AbstractChemistryOperator

Read the optional [chemistry] TOML section and produce the corresponding chemistry operator.

Supported kind values:

  • "none" (default) — NoChemistry().

  • "decay"ExponentialDecay(FT; ...). Half-lives are read from the half_lives_seconds table:

    [chemistry]
    kind = "decay"
    [chemistry.half_lives_seconds]
    rn222 = 330350.4   # 3.8235 days

    The keyword name must match the corresponding [tracers.<name>] symbol that the run is carrying (case-insensitive — the builder symbolizes the key as-is and ExponentialDecay.apply! resolves it against state.tracer_names at call time).

Thin wrapper: parse the [chemistry] section into a typed AbstractChemistrySpec once (validated), then materialize at run precision FT. Spec types + parser live in RuntimePhysicsSpecs.jl.

source
AtmosTransport.Models.build_runtime_physics_recipe Method
julia
build_runtime_physics_recipe(cfg, context, FT; halo_width=nothing)

Parse typed advection, diffusion, convection, and chemistry specifications from cfg, materialize them for context and floating-point type FT, then run the complete runtime compatibility validation.

source
AtmosTransport.Models.chemistry_spec Method
julia
chemistry_spec(section) -> AbstractChemistrySpec

Parse a [chemistry] section into a typed spec. kind = "decay" with an empty (or absent) half_lives_seconds table reduces to NoChemistrySpec — matching the old builder (an inert decay scheme is just no chemistry). Each half-life is validated positive at parse time (the old builder silently produced an Inf/negative decay rate for a non-positive value).

source
AtmosTransport.Models.convection_chemistry_step! Method
julia
convection_chemistry_step!(model::TransportModel, dt; meteo = nothing)

Advance the non-transport physics blocks once: convection block → chemistry block.

source
AtmosTransport.Models.convection_spec Method
julia
convection_spec(section) -> AbstractConvectionSpec

Parse a [convection] TOML section into a typed spec, validating at the boundary.

INTENTIONAL behavior change vs the old builder: the collaborative-LU knobs lmax_conv/n_merge only take effect when use_collab_lu = true (they steer the workgroup-collaborative kernel; the legacy per-thread path ignores them). Setting them without use_collab_lu used to be a silent no-op; it is now a hard error.

source
AtmosTransport.Models.diffusion_spec Method
julia
diffusion_spec(section) -> AbstractDiffusionSpec

Parse a [diffusion] section into a typed spec, validating at the boundary. An empty/absent section or kind = "none" is explicit "no diffusion". The legacy type = "..." schema is rejected (it used to silently fall through to NoDiffusion, hiding configs that expected diffusion to run); a present section with no kind is rejected too.

source
AtmosTransport.Models.run! Method
julia
run!(simulation)

Advance a Simulation or DrivenSimulation until its configured stop condition. Returns the mutated simulation.

source
AtmosTransport.Models.run_window! Method
julia
run_window!(sim::DrivenSimulation)

Advance exactly the current meteorological window and return sim. If sim is positioned at a completed non-final window, the next window is loaded first.

source
AtmosTransport.Models.step! Method
julia
step!(model::TransportModel, dt; meteo = nothing)

Advance model.state by one full runtime step: transport block (advection with vertical diffusion at the palindrome center, surface emissions wrapped by the two V half-steps when active) → convection block → chemistry block.

With defaults diffusion = NoDiffusion(), emissions = NoSurfaceFlux(), chemistry = NoChemistry(), convection = NoConvection(), every live component is a dead branch and the call is bit-exact equivalent to the advection-only path.

meteo is optional and defaults to nothing; pass a real meteorology object (AbstractMetDriver) or a DrivenSimulation to thread current_time(meteo) through operators that consume time-varying fields.

source
AtmosTransport.Models.transport_step! Method
julia
transport_step!(model::TransportModel, dt; meteo = nothing)

Advance model.state by one transport step: advection with vertical diffusion at the palindrome center and surface emissions. The diffusion operator's coupling policy selects either V(dt/2) -> S(dt) -> V(dt/2) or S(dt) -> V(dt) when both diffusion and a surface source are active.

Binary-scheduled driven runs use this block at the per-window advection substep cadence stored in the transport binary. Convection and chemistry are separate physics blocks and can run at the met-window cadence via convection_chemistry_step!.

source
AtmosTransport.Models.validate_runtime_physics_recipe Method
julia
validate_runtime_physics_recipe(recipe, context; halo_width=nothing)

Validate topology support, binary capabilities, and halo requirements for a materialized RuntimePhysicsRecipe. Returns recipe or throws ArgumentError before model allocation.

source
AtmosTransport.Models.with_chemistry Method
julia
with_chemistry(model::TransportModel, chemistry)

Return a copy of model with its chemistry operator replaced. All other fields share storage with the original. Chemistry is installed into the model rather than held at the sim level, so this helper is primarily useful for tests that want to swap chemistry on a constructed model.

source
AtmosTransport.Models.with_convection Method
julia
with_convection(model::TransportModel, convection)

Return a copy of model with its convection operator replaced. All other fields — including convection_forcing — share storage with the original.

Note: with_convection does NOT allocate convection-forcing buffers. The model-side ConvectionForcing() placeholder stays as-is. DrivenSimulation construction is responsible for allocating real buffers via allocate_convection_forcing_like after the first window loads. For tests that bypass the sim layer, use with_convection_forcing(model, forcing) to inject allocated buffers directly. The model workspace is re-wrapped as needed so concrete operators can carry their own scratch storage without disturbing the advection workspace.

source
AtmosTransport.Models.with_convection_forcing Method
julia
with_convection_forcing(model::TransportModel, forcing::ConvectionForcing)

Return a copy of model with its per-step convection-forcing container replaced. All other fields — including the convection operator — share storage with the original.

Used by DrivenSimulation construction to install the allocated forcing buffers after the first window loads. Also useful for tests that inject forcing directly without going through the sim's _refresh_forcing! path.

source
AtmosTransport.Models.with_diffusion Method
julia
with_diffusion(model::TransportModel, diffusion)

Return a copy of model with its diffusion operator replaced. All other fields share storage with the original. Parallel to with_chemistry; useful for installing a diffusion operator into a model that was constructed with the default NoDiffusion().

source
AtmosTransport.Models.with_emissions Method
julia
with_emissions(model::TransportModel, emissions)

Return a copy of model with its surface-emissions operator replaced. All other fields share storage with the original. Parallel to with_chemistry and with_diffusion; used by DrivenSimulation to install the sim-level surface_sources tuple as a SurfaceFluxOperator inside the wrapped model, so the palindrome's S slot runs at the right place in the transport block without sim-level post-step hacks.

source
AtmosTransport.Models.InitialConditionIO Module
julia
InitialConditionIO

Single owner of initial-condition I/O, vertical remap, and topology-dispatched VMR builders for the unified runtime.

Public API (exported via ModelsAtmosTransport)

  • build_initial_mixing_ratio — topology-dispatched builder returning dry VMR on interior cells. Accepts kind = uniform | latitude_step | gaussian_blob | file | netcdf | file_field | catrine_co2 for LL/RG meshes. CS supports uniform | latitude_step | gaussian_blob | file | netcdf | file_field | catrine_co2.

  • pack_initial_tracer_mass — basis-aware VMR → conservative model storage conversion. Dispatches on mass_basis::AbstractMassBasis:

    • DryBasis (default per CLAUDE.md invariant 14): rm = vmr .* air_mass.

    • MoistBasis: rm = vmr .* air_mass .* (1 .- qv) per CLAUDE.md invariant 9; qv must be supplied.

  • FileInitialConditionSource — container for a loaded IC NetCDF (3D VMR + hybrid coefficients + surface pressure).

Contents

  • LL/RG and CS build_initial_mixing_ratio for uniform | file | catrine_co2 | netcdf | file_field.

  • Basis-aware pack_initial_tracer_mass (DryBasis + MoistBasis); CS output is halo-padded with the halo zeroed. _build_source_latlon_mesh helper for LL→CS conservative regridding.

  • Surface-flux NetCDF loader (13 helpers + FileSurfaceFluxField struct) and LL/RG build_surface_flux_source methods; CS build_surface_flux_source conservatively LL→CS-regrids the flux (the regridder's dst_areas × regridded density already yields kg/s per cell) and unpacks to NTuple{6, Matrix{FT}} — satisfying the per-cell kg/s contract at src/Operators/SurfaceFlux/sources.jl:12.

Private helpers (underscore-prefixed) stay unexported and are accessed by callers (including the canonical driven runtime) via AtmosTransport.Models.InitialConditionIO.<name> if needed.

source
AtmosTransport.Models.InitialConditionIO.FileInitialConditionSource Type
julia
FileInitialConditionSource{FT}

Container for a file-based initial condition (e.g. Catrine startCO2).

Fields

  • raw — 3D mixing ratio field (nlon_src, nlat_src, nlevel_src). Level ordering follows the source file (Catrine: k=1 is SURFACE, k=end is TOA — verify via ap[1]+bp[1]*ps ≈ ps).

  • lon, lat — source coordinate vectors [degrees]. May be in [-180,180) or [0,360); the bilinear sampler wraps to [0,360) internally via wrapped_longitude_360.

  • ap, bp — hybrid half-level coefficients (nlevel_src + 1): p_half[k] = ap[k] + bp[k] × ps_src. Units: ap [Pa], bp [dimensionless].

  • psurf — surface pressure (nlon_src, nlat_src) [Pa].

  • needs_vinterptrue if source levels ≠ target levels (triggers log-pressure vertical interpolation in _interpolate_log_pressure_profile!).

source
AtmosTransport.Models.InitialConditionIO.FileSurfaceFluxField Type
julia
FileSurfaceFluxField{FT}

2-D flux density (Nx_src, Ny_src) loaded from a NetCDF emission file. Units are kg/m²/s after _load_file_surface_flux_field has normalised the native units. native_total_mass_rate is the pre-regrid global integral, preserved for the bilinear path's renormalisation (the conservative path does not need it).

source
AtmosTransport.Models.InitialConditionIO.TimeVaryingFileSurfaceFluxField Type
julia
TimeVaryingFileSurfaceFluxField{FT}

A stack of 2-D flux-density slices (Nx_src, Ny_src, ntime) loaded from a NetCDF emission file, keeping every time slice (no monthly averaging). Units are kg/m²/s after _load_timevarying_surface_flux_field has normalised the native units, identically per slice. times_sec holds the slice times in seconds since the run reference_time, ascending.

source
AtmosTransport.Models.InitialConditionIO.build_initial_mixing_ratio Method
julia
build_initial_mixing_ratio(air_mass, mesh, cfg)
build_initial_mixing_ratio(air_mass, grid::AtmosGrid, cfg; surface_pressure=nothing)

Construct the initial dry-air volume mixing ratio described by cfg on the horizontal topology and vertical layout of air_mass.

Bare lat-lon and reduced-Gaussian meshes support the analytic uniform, latitude_step, and gaussian_blob modes; bl_enhanced is lat-lon only. Passing an AtmosGrid additionally enables file-backed modes (file, netcdf, file_field, and catrine_co2) with topology-aware horizontal mapping and log-pressure vertical interpolation. Cubed-sphere construction requires an AtmosGrid and also supports pressure_layer and cs_native. File-backed and pressure-layer construction require the transport window's surface_pressure where indicated by the selected mode.

Returns an array shaped like air_mass for lat-lon and reduced-Gaussian grids, or an NTuple{6} of interior (Nc, Nc, Nz) arrays for cubed-sphere grids.

source
AtmosTransport.Models.InitialConditionIO.build_surface_flux_source Method
julia
build_surface_flux_source(grid::AtmosGrid{<:CubedSphereMesh},
                          tracer_name, cfg, ::Type{FT})

CS surface-flux builder. Conservatively LL→CS regrids the 2-D flux density (kg/m²/s) onto the 6 CS panel cell centres, multiplies by each panel's cell area to yield per-cell kg species/s, then converts that physical rate to the model storage basis. Returns a SurfaceFluxSource whose cell_mass_rate is an NTuple{6, Matrix{FT}} of interior-only (Nc, Nc) panels.

cfg must set kind (non-none); any of the file-based surface-flux kinds _load_file_surface_flux_field understands work (gridfed_fossil_co2 or user-supplied file + variable). Conservative regridding is enforced — CS bilinear is not supported.

If cfg["time_varying"] = true and the kind supports a 3-D (lon,lat,time) series (currently :lmdz_co2), the builder keeps every time slice, builds the LL→CS regridder ONCE, applies it per slice, and returns a TimeVaryingSurfaceFluxSource whose cell_mass_rate_series is an NTuple{6} of (Nc, Nc, ntime) panels plus a times vector (seconds since reference_time). The default (time_varying absent/false) path is byte-identical to before.

source
AtmosTransport.Models.InitialConditionIO.build_surface_flux_sources Method
julia
build_surface_flux_sources(grid, tracer_specs, ::Type{FT}; reference_time=nothing)

Build surface-flux source instances for every tracer spec that requests one. Returns a tuple (possibly empty) suitable for the surface_sources = (…,) kwarg on DrivenSimulation.

reference_time (the run start DateTime) is threaded to each per-tracer builder so the time-varying CS path can align its slice times to the simulation clock. It is ignored by static sources.

source
AtmosTransport.Models.InitialConditionIO.pack_initial_tracer_mass Method
julia
pack_initial_tracer_mass(grid, air_mass, vmr_dry; mass_basis::AbstractMassBasis,
                                                  qv = nothing)

Convert dry volume mixing ratio vmr_dry to conservative model storage matching the binary's mass-basis contract. On DryBasis that storage is vmr_dry × dry_air_mass; it is not physical kg species. Returns an array of the same shape as air_mass.

Dispatch

  • mass_basis::DryBasisair_mass is m_dry per CLAUDE.md invariant 14. Result: vmr_dry .* air_mass. qv is ignored.

  • mass_basis::MoistBasisair_mass is m_moist per CLAUDE.md invariant 9. Result: vmr_dry .* air_mass .* (1 .- qv). qv must be supplied from the first transport window; missing qv errors.

CS dispatch handles per-panel halo packing.

Arguments

  • gridAtmosGrid{<:LatLonMesh} or AtmosGrid{<:ReducedGaussianMesh} (CS added in 1c).

  • air_mass — storage-shaped air mass from the transport window. Shape matches vmr_dry.

  • vmr_dry — dry volume mixing ratio, same shape as air_mass.

  • mass_basisDryBasis() or MoistBasis().

  • qv — specific humidity, same shape as air_mass; required iff mass_basis isa MoistBasis.

source
AtmosTransport.Models.BinaryPathExpander Module
julia
BinaryPathExpander

Expand a [input] TOML block into a sorted list of transport-binary file paths. Supports both an explicit-list shape and a folder + date-range shape with continuity verification. Keeps the runtime file-window contract unchanged; the CLI simply receives a longer or shorter Vector{String}.

Accepted TOML shapes

toml
# Shape A (current, preserved): explicit list
[input]
binary_paths = ["...a.bin", "...b.bin"]

# Shape B (new): folder + inclusive date range
[input]
folder       = "~/data/.../cs_c48/.../"
start_date   = "2021-12-01"
end_date     = "2021-12-10"
file_pattern = "era5_transport_{YYYYMMDD}_merged1000Pa_float32.bin"  # optional

Shapes A and B are mutually exclusive.

Folder scanning

When folder is supplied:

  • readdir(folder) → candidate filenames.

  • Each filename is parsed for an 8-digit YYYYMMDD token. If file_pattern is supplied, the {YYYYMMDD} placeholder marks where the date lives, and the surrounding text is matched literally (as a regex). Without file_pattern, any \d{8} run anywhere in the filename is used.

  • Files parseable as dates within [start_date, end_date] are kept; files outside the range are silently dropped; files that don't match the pattern error out.

  • The resulting sorted list is verified for continuity: every date in the closed interval must be present. Missing days list in the error message.

Returns

Vector{String} of absolute paths, sorted chronologically by parsed date.

source
AtmosTransport.Models.BinaryPathExpander.expand_binary_paths Method
julia
expand_binary_paths(input_cfg::AbstractDict) -> Vector{String}

See module docstring for the accepted TOML shapes. Throws ArgumentError on mutual exclusion, missing folder, date parse failure, or gaps in the date range.

source
AtmosTransport.Models.InputStaging.InputStager Type
julia
InputStager

Rolling local-disk stager for the daily transport binary list. Construct from the resolved NAS binary_paths and the [input.staging] config sub-table; call staged_path_for! at each day's driver-open site and cleanup_staging! at run end.

All mutable bookkeeping (staged, tasks, failed) is touched only by the main loop thread — background tasks are pure (copy + return the path or throw), so there is no shared-state race.

source
AtmosTransport.Models.InputStaging.InputStager Method
julia
InputStager(binary_paths, staging_cfg)

Parse the [input.staging] sub-table and build the stager. Keys (all optional):

  • enabled : Bool (default false ⇒ no staging, NAS paths used)

  • dir : String (REQUIRED when enabled) — local NVMe directory

  • lookahead_days : Int (default 2)

  • keep_behind_days : Int (default 0)

  • cleanup_on_exit : Bool (default true)

source
AtmosTransport.Models.InputStaging.cleanup_staging! Method
julia
cleanup_staging!(mgr)

Wait for any in-flight copies and remove all remaining staged files (when cleanup_on_exit). Safe to call unconditionally at run end.

source
AtmosTransport.Models.InputStaging.staged_path_for! Method
julia
staged_path_for!(mgr, idx) -> String

Return the path the day-idx driver should open. With staging enabled this ensures day idx is on local NVMe (blocking only if its copy is still in flight), kicks off async copies for the look-ahead window, and evicts processed days. Falls back to the NAS path if the copy failed. With staging disabled it returns the original NAS path unchanged.

source
AtmosTransport.Models.DrivenRunner Module
julia
DrivenRunner

Library-level entry point for the driven transport runtime.

The canonical CLI, scripts/run_transport.jl, is a thin wrapper over run_driven_simulation(cfg). The library function handles LL/RG and CS runtime flows with dispatch driven by the first binary's header (inspect_binary(first_path).grid_type). Historical runner names live under scripts/deprecated/ only for reference.

Ownership boundary

  • Binary header (grid_type, mass_basis, payload_sections, panel convention) — authoritative for topology and capability. Accessed here via binary_capabilities(driver.reader) and air_mass_basis(driver).

  • TOML [input] — either an explicit binary_paths = [...] list (Shape A) or a folder + start_date + end_date (+ file_pattern) block (Shape B). Both are resolved to a sorted Vector{String} by expand_binary_paths.

  • TOML physics ([advection] / [diffusion] / [convection]) — validated against binary capabilities by validate_runtime_physics_recipe / build_runtime_physics_recipe before the loop.

  • TOML [tracers.*] — tracer specs consumed via build_initial_mixing_ratio + pack_initial_tracer_mass (basis-aware per feedback_vmr_to_mass_basis_aware) and build_surface_flux_sources.

Where the physics actually runs

DrivenRunner is the orchestration layer, not the kernel layer. It opens transport binaries, builds initial state, chooses output cadence, and installs the TOML-selected operators on a TransportModel. The live physics call chain is:

  1. build_runtime_physics_recipe reads [advection], [diffusion], [convection], [chemistry], and tracer surface-flux settings and returns the operator objects.

  2. _make_structured_model or the CS model constructor installs those operators on TransportModel(state, fluxes, grid, recipe.advection; ...).

  3. DrivenSimulation(model, driver; ...) loads the current met window and wraps chemistry and surface sources into the model with with_chemistry and with_emissions.

  4. The runtime loop below calls run_window!(sim) for LL/RG or step!(sim) for CS. Those functions live in DrivenSimulation.jl.

  5. DrivenSimulation.step! refreshes time-varying forcing from the driver, then calls TransportModel.step! or, for binary-scheduled substeps, transport_step! plus an end-of-window convection_chemistry_step!.

  6. TransportModel.jl is where advection, surface emissions, diffusion, convection, and chemistry are applied to the state.

So, when following a run as a scientist: start here to understand data and configuration flow, then jump to DrivenSimulation.step! and TransportModel.step! to see the actual physics ordering.

GPU residency (feedback_verify_gpu_runs_on_gpu)

When [architecture].use_gpu = true or backend selects a GPU, the runner asserts that state.air_mass lives on the selected backend after model construction and prints a [gpu verified] … line. A silent CPU fallback aborts the run with a precise error message.

source
AtmosTransport.Models.DrivenRunner.TransportTracerSpec Type
julia
TransportTracerSpec

Validated runtime tracer configuration: tracer name, initial-condition configuration, and optional surface-flux configuration.

source
AtmosTransport.Models.DrivenRunner.run_driven_simulation Method
julia
run_driven_simulation(cfg::AbstractDict) -> TransportModel

Canonical high-level entry point for scientists running an AtmosTransport TOML. Use this unless you are writing a custom time loop. It resolves [input] to a sorted binary list, prints a one-line summary for each binary, dispatches on the first binary's grid_type, validates physics-vs-capability, runs the loop, optionally writes topology-native snapshots, and returns the terminal TransportModel.

This function does not call the advection/diffusion/convection kernels directly. The handoff to physics happens inside the structured loop at run_window!(sim) and inside the CS loop at step!(sim). Both routes enter DrivenSimulation.step!, which refreshes forcing and then calls TransportModel.step! / transport_step! / convection_chemistry_step!.

source
AtmosTransport.Models.DrivenRunner.validate_config Method
julia
validate_config(cfg::AbstractDict) -> (ok::Bool, errors::Vector{String})

Run inexpensive pre-flight checks for a driven runtime config: input shape, resolved binary paths, numeric type, backend/float compatibility, tracer table shape, and basic run-window bounds. It does not open binary readers or allocate model state; topology and payload capability checks still run when run_driven_simulation inspects the first binary.

source