Skip to content

Output schema

The default runtime output is a NetCDF4 snapshot file declared by [output] path. split = "single" writes one file per run; split = "daily" writes one file per daily binary. Single-file NetCDF runs append and flush one snapshot at a time along an unlimited time dimension. They retain no history of snapshot frames in memory. Daily output retains the current day's selected frames and allows one background write; the runner waits for it on success or failure. This page documents the NetCDF variables, dimensions, units, and per-topology conventions.

Streaming files record completed_snapshots, the number of fully flushed records. A write failure can leave an incomplete trailing record beyond that count. Reopening a stream to resume a run is not supported.

The stream writes directly to its configured final path; file existence does not prove that the simulation finished. A failed or interrupted run leaves its partial file in place. If it can be opened, use only records up to completed_snapshots and compare their times with the requested schedule. An abrupt process or machine failure can also leave an unreadable NetCDF/HDF5 file. Preserve the log for diagnosis and rerun to a new path; the writer does not recover or resume that file.

For long runs, format = "binary_mmap" writes ATMSNAP files with a Float32 spatial payload and compensated Float64 tracer totals; convert them to this same NetCDF schema with scripts/postprocess/binary_to_netcdf.jl. See TOML schema for that throughput-oriented workflow.

The write_snapshot_netcdf entry point in src/Output/netcdf_writer.jl dispatches on the runtime mesh type into one of three per-topology writers.

The variable list is controlled by [output.fields]. By default every field below is written. Setting layers = "none" suppresses per-level tracer VMR variables; setting layers = "selected" writes the same variable names on the lev_selected dimension. tracers = [...] restricts all tracer diagnostics to that subset, with optional [output.fields.per_tracer.<name>] overrides. The runtime captures the union of requested layers for the selected tracers, plus the required column reductions, for NetCDF; column-only output avoids copying and retaining complete tracer volumes on the host. ATMSNAP continues to capture full native state.

Every selected tracer also has a topology-independent <tracer>_total_mass(time) variable. It is the compensated Float64 global sum of the conservative mixing_ratio × carrier_air_mass storage captured from the model state. Negative values are valid, and kg refers to the carrier-mass storage unit rather than physical kilograms of tracer species. This is the authoritative snapshot conservation series; do not reconstruct it by summing a Float32 spatial payload when signed components can cancel. CUDA selected capture uses compensated Float64 device partial sums and a compensated host reduction; sum and correction remain separate until the final reduction. Metal, which cannot execute Float64 arithmetic, uses bounded host slabs for these totals. Different reduction orders need not be bitwise identical, but output precision does not erase small signed residuals.

Column means and column mass diagnostics also accumulate in Float64. CUDA performs those reductions on the device. Metal transfers at most 16 vertical levels at a time and carries each column's Float64 sum across host slabs in model-level order, matching the CPU diagnostic without retaining a complete tracer volume. This avoids losing signed residuals in a Float32 column sum.

Global attributes

Every snapshot file carries a CF-style global header set by _define_common_attributes! in src/Output/netcdf_schema.jl:

AttributeValue
Conventions"CF-1.8"
title"AtmosTransport runtime snapshot"
source"AtmosTransport.jl"
institutionENV["ATMOSTR_INSTITUTION"] if set, else "Caltech / Frankenberg group"
gridsummary(mesh) string (e.g. "72×37 LatLonMesh{Float32}")
grid_type"latlon" / "reduced_gaussian" / "cubed_sphere"
mass_basis"dry" or "moist" (matches state.air_mass)
output_contractversion tag for the schema
creation_dateISO-8601 UTC timestamp of the run
framework"AtmosTransport.jl"
framework_commitgit SHA of the source tree at run time (or "unknown")
framework_dirty"clean" or "dirty" (uncommitted changes flag)
runtimeJulia version plus machine, kernel, and operating-system summary
hostnameBase.Libc.gethostname() at run start
user$USER (or $USERNAME on Windows; "unknown" if neither is set)
output_optionsfloat_type=…, deflate_level=…, shuffle=… (only present when writer options are passed)
historyCF-canonical chain; the writer prepends "<creation_date>: written by AtmosTransport.Output (commit <sha>[+dirty]) with N frame(s)"

Every provenance value is best-effort: non-git checkouts get framework_commit = "unknown"; environments without a USER variable get user = "unknown". output_options is present when writer options are passed; the other listed attributes are written by the current NetCDF writer.

Lat-lon snapshot

Dimensions:

DimLength
lonNx (cell centers)
latNy
levNz (positive = "down"lev[1] is TOA, lev[end] is surface)
timeone entry per configured output time that actually fired
lev_selectedonly present when [output.fields] layers = "selected" or air_mass_layers = "selected"

Coordinate variables:

VariableShapeUnits (writer string)
lon(lon,)degrees_east
lat(lat,)degrees_north
lon_bounds(lon, nv) (nv = 2)degrees_east
lat_bounds(lat, nv) (nv = 2)degrees_north
cell_area(lon, lat)m2
time(time,)hours since the configured simulation start
lev(lev,)1 (dimensionless level index; positive = "down")

Per-topology mass diagnostics (enabled by default):

VariableShapeUnits (writer string)Meaning
air_mass(lon, lat, lev, time)kgper-cell air mass on mass_basis
air_mass_per_area(lon, lat, lev, time)kg m-2layer mass divided by cell_area
column_air_mass_per_area(lon, lat, time)kg m-2column total divided by cell_area

Per-tracer fields (one set per [tracers.<name>] block). The units string written into the NetCDF reflects the runtime basis:

VariableShapeUnits (DryBasis writer string)Units (MoistBasis writer string)
<tracer>(lon, lat, lev, time)mol mol-1 drymol mol-1
<tracer>_column_mean(lon, lat, time)mol mol-1 drymol mol-1
<tracer>_column_mass_per_area(lon, lat, time)kg m-2 model storagekg m-2 model storage
<tracer>_total_mass(time,), Float64kg model storagekg model storage

The per-tracer full-3D field <tracer> is the mixing ratio. The <tracer>_column_mass_per_area diagnostic is the sum of the model's χ × carrier-air-mass storage divided by area; no molecular-weight conversion to physical kg species is applied.

Reduced-Gaussian snapshot

Dimensions:

DimLength
cellncells (flat ring-by-ring; ring j starts at ring_offsets[j])
lev, timeas for LL
lon, latrasterized regular LL diagnostic grid (for plotting)

All horizontal fields are written in native face-indexed form (dimension cell). For plot tools that don't understand reduced Gaussian, a single rasterized variant — the per-tracer column mean — is also written on a regular LL grid ((lon, lat)) via nearest-neighbor lookup. The native fields remain authoritative for any quantitative analysis.

VariableNative shapeRasterized?
air_mass(cell, lev, time)no
air_mass_per_area(cell, lev, time)no
column_air_mass_per_area(cell, time)no
cell_area(cell,)no
<tracer>(cell, lev, time)no
<tracer>_column_mean_native(cell, time)
<tracer>_column_mean(lon, lat, time) (rasterized via nearest-neighbor — diagnostic only)
<tracer>_column_mass_per_area(cell, time)no
<tracer>_total_mass(time,), Float64no

The native fields are authoritative; the rasterized ones are for visualization.

Cubed-sphere snapshot

Dimensions:

DimLength
XdimNc (per-panel cell-x index)
YdimNc (per-panel cell-y index)
nf6 (panel face index, ordered by the active panel_convention)
lev, timeas for LL

The per-panel arrays are stacked into the nf dimension at write time by _cs_stack3 / _cs_stack2 in src/Output/netcdf_writer.jl.

Per-topology fields:

VariableShapeUnits (writer string)
air_mass(Xdim, Ydim, nf, lev, time)kg
air_mass_per_area(Xdim, Ydim, nf, lev, time)kg m-2
column_air_mass_per_area(Xdim, Ydim, nf, time)kg m-2
cell_area(Xdim, Ydim, nf)m2
<tracer>(Xdim, Ydim, nf, lev, time)mol mol-1 dry (or mol mol-1 on moist basis)
<tracer>_column_mean(Xdim, Ydim, nf, time)same as <tracer>
<tracer>_column_mass_per_area(Xdim, Ydim, nf, time)kg m-2
<tracer>_total_mass(time,), Float64kg model storage

A grid_mapping = "cubed_sphere" attribute is set on the horizontally-resolved variables; the active CS definition, coordinate law, center law, panel convention (gnomonic / geos_native), and longitude offset are in the global header so consumers can reconstruct the panel layout if needed (see Cubed-sphere).

Reading the snapshot

ncdump

bash
ncdump -h ~/data/.../my_run.nc | head -40

Python (NetCDF4)

python
from os.path import expanduser
import netCDF4 as nc

with nc.Dataset(expanduser("~/data/.../my_run.nc")) as ds:
    print(ds.dimensions)
    print(list(ds.variables.keys()))
    co2_cm = ds["co2_bl_column_mean"][:]  # (time, lat, lon) in netCDF4
    print(co2_cm.shape, co2_cm.min(), co2_cm.max(), co2_cm.mean())

with nc.Dataset(expanduser("~/data/.../my_cs_run.nc")) as ds:
    co2_cs = ds["co2_bl_column_mean"][:]  # (time, nf, Ydim, Xdim)
    panel = co2_cs[-1, 0, :, :]            # last frame, panel 1

Julia (NCDatasets.jl)

julia
using NCDatasets

NCDataset(expanduser("~/data/.../my_run.nc")) do ds
    @show keys(ds.variables)
    co2_cm = ds["co2_bl_column_mean"][:, :, end] # last frame, (lon, lat)
    co2_air = ds["air_mass"][:, :, :, end]       # (lon, lat, lev)
end

Fill value

Every payload variable is defined with _FillValue = 1.0e15 (and missing_value = 1.0e15 for older tools); this matches the GEOS-Chem convention (Met_AD._FillValue == 1.0e15) so Panoply / ncview / IDV mask the same out-of-range cells with the same value. Float32 outputs truncate to Float32(1e15), which sits comfortably below floatmax(Float32) ≈ 3.4e38 and outside any physical mass / mixing-ratio range. The sentinel is written via NetCDF4's storage default so uninitialised cells are masked even if the writer never reaches them.

Compression and packing

OptionDefaultEffect
[output] deflate_level0 (no compression)NetCDF4 zlib level 0..9
[output] shuffletrueshuffle filter (only effective when deflate_level > 0)

Compression trades writer time for disk space and depends strongly on the field. Benchmark deflate_level = 1 through 4 on representative output before choosing a production setting; high levels usually have diminishing returns.

Runtime NetCDF spatial precision follows [numerics].float_type: Float32 transport writes Float32 spatial fields; Float64 transport writes Float64. ATMSNAP spatial payloads always use Float32. When calling the lower-level write_snapshot_netcdf API directly, choose precision with SnapshotWriteOptions (whose default is Float32). Time coordinates and <tracer>_total_mass remain Float64 independently of spatial precision.