T2 — Your first run: a single-column model¶
The science question first. Take one atmospheric column whose lower troposphere starts supersaturated, and let ICON’s fast moist physics act on it: what rains out, and how fast? A single-column model (SCM) is the smallest experiment that exercises real parameterizations in their real calling order — and it is small enough to run in under a minute on a laptop.
This page walks examples/01_scm_column.py end to end. All code shown is
included directly from that
CI-tested file; nothing is copied
by hand.
Everything here runs. From a repository checkout:
uv run python examples/01_scm_column.py --hours 1 --output scm_column.nc
What the script says it does¶
"""ICON-sc example 01 — a single-column model (SCM), the architecture-§5.1 shape.
One legible script (the sympl-paper promise): which schemes, what order, what
cadences, where output happens — all on one screen. The composition is the S09
validated preset (``icon_sc.icon.presets.scm``): ICON's fast-physics subset
satad → graupel microphysics → satad coupled by sequential-update splitting
(tutorial §3.7.2), plus a ``CallingFrequency``-wrapped prescribed cooling
publishing a piecewise-constant tendency to the ``icon:ddt_temperature_slow``
bus slot, consumed by a trivial dycore stand-in.
Run (CI smoke: < 60 s CPU on the embedded debug backend)::
uv run python examples/01_scm_column.py --hours 1 --output scm_column.nc
"""
Unpacking that: the composition is a preset — a pre-built, validated arrangement of components, in this case the SCM subset of ICON’s fast-physics calling sequence. Saturation adjustment runs before and after graupel microphysics (“to ensure that vapor and liquid phase are in equilibrium before entering the slow physics parameterizations”, in the ICON tutorial’s words), and each process consumes the state left by the previous one — sequential-update splitting, exactly ICON’s operational fast-physics coupling. That ordering rule is not a comment: it is attached to the preset as a machine-checked constraint, and a composition that violates it refuses to build.
On top of the fast suite there is one slow process — a prescribed cooling
standing in for radiation. It runs every 300 s (ten fast steps), and between
calls its heating rate is held piecewise-constant on the
slow-tendency bus: it publishes
to the named slot icon:ddt_temperature_slow, and a consumer component
(standing in for the dycore’s slow-tendency port) integrates that slot every
fast step. This is ICON’s operational arrangement for slow physics, at
column scale. The bus is checked when the model is built: a published slot
with no consumer — a forcing that would silently vanish — is a build error.
The model¶
def build_model() -> Any:
"""The example's model: exactly the S09 preset builder, default config.
Exposed as a function so the S14 plan-hash drift test can bind the
composition this script runs and assert it hashes identically to the
preset builder's (the layout-doc drift test — SPEC S14 acceptance 3).
"""
return build_scm(SCMConfig()) # embedded backend: no compile step
One call builds everything: the components, their coupling, the constraint and bus checks, and the initial column — a decaying-isothermal reference atmosphere whose humidity is scaled so the lower troposphere starts supersaturated (so condensation and precipitation begin immediately). The default configuration is a typed configuration object whose values are fixed once it is created — like a namelist that cannot be edited mid-run, so the settings a run started with are exactly the settings it finished with. Here it is, from the preset module itself:
@dataclasses.dataclass(frozen=True)
class SCMConfig:
"""Configuration of the SCM preset (architecture §5.3 style: typed, frozen).
``slow_timestep`` defaults to exactly ``10 · dtime`` (SPEC acceptance 3);
any positive value is legal — a non-multiple is rounded to the nearest
multiple of the loop timestep by the frozen S03 ``CallingFrequency`` rule
(the tutorial §3.7.1 rounds *up*; see STATUS S09).
"""
#: Vertical levels of the S06 default (flat-terrain) ICON grid.
nlev: int = 65
#: Horizontal extent (independent columns; the preset is single-column).
n_cell: int = 1
#: Fast-physics / loop timestep Δt.
dtime: timedelta = timedelta(seconds=30)
#: Slow-physics cadence (default 10·Δt).
slow_timestep: timedelta = timedelta(seconds=300)
#: Humidity scaling of the reference_moist profile (module docstring).
qv_scale: float = 2.0
#: Cloud droplet number concentration [m-3] (ICON default ``cloud_num``).
qnc: float = CLOUD_NUM
#: Initial model time.
start_time: Any = dataclasses.field(default_factory=lambda: datetime(2000, 1, 1))
#: Newtonian-cooling (slow forcing) parameters.
cooling: PrescribedCoolingConfig = dataclasses.field(default_factory=PrescribedCoolingConfig)
#: Saturation-adjustment configuration (both SUS occurrences share it).
satad: SaturationAdjustmentConfig = dataclasses.field(
default_factory=SaturationAdjustmentConfig
)
#: Microphysics (graupel scheme) configuration.
microphysics: GraupelConfig = dataclasses.field(default_factory=GraupelConfig)
Output selection and the run¶
#: What the monitor writes: prognostics the suite steps, the grid-scale surface
#: precipitation rates it diagnoses, and the slow-tendency bus slot.
OUTPUT_SET: tuple[str, ...] = (
"air_temperature",
"specific_humidity",
"specific_cloud_content",
"specific_ice_content",
"specific_rain_content",
"specific_snow_content",
"specific_graupel_content",
"icon:rain_gsp_rate",
"icon:snow_gsp_rate",
"icon:ice_gsp_rate",
"icon:graupel_gsp_rate",
"icon:ddt_temperature_slow",
)
def main(output: str | Path = "scm_column.nc", hours: float = 1.0) -> dict[str, Any]:
"""Build the SCM preset, run it for ``hours``, write NetCDF; return the final state."""
composition, state, cfg = build_model()
monitor = NetCDFMonitor(output, variables=OUTPUT_SET)
with warnings.catch_warnings():
# icon4py's embedded execution warns about Python-level execution; their
# own integration tests run with the same warnings suppressed.
warnings.simplefilter("ignore")
final = timeloop(
state,
composition.step,
timestep=cfg.dtime,
until=timedelta(hours=hours),
monitors=[monitor],
)
rain = float(np.max(final["icon:rain_gsp_rate"].data))
t_sfc = float(final["air_temperature"].data[0, -1])
print(f"SCM run complete: {hours} h at dt={cfg.dtime.total_seconds():.0f} s")
print(f" surface temperature : {t_sfc:9.3f} K")
print(f" max surface rain rate : {rain:9.3e} kg m-2 s-1")
print(f" output : {monitor.path}")
return final
timeloop drives the composition’s step for the requested duration, and
the monitor writes the selected fields to NetCDF at every step. The run
prints a summary; with the defaults (--hours 1) you should see the column
warm as latent heat is released and rain reach the surface:
SCM run complete: 1.0 h at dt=30 s
surface temperature : 296.307 K
max surface rain rate : 6.718e-04 kg m-2 s-1
output : scm_column.nc
The output file is ordinary CF-style NetCDF; open it with whatever you already use (xarray, ncview, cdo). For a quick look with xarray and matplotlib:
uv run python -c "
import xarray as xr
ds = xr.open_dataset('scm_column.nc')
ds['icon:rain_gsp_rate'].isel(cell=0).plot()
import matplotlib.pyplot as plt; plt.savefig('rain.png')
"
Now change something¶
The point of a preset is that the validated arrangement is one object, and
your experiment is a visible edit against it. Halve the initial
supersaturation by editing the config value in build_model:
return build_scm(SCMConfig(qv_scale=1.5)) # default: 2.0
and rerun. Less initial vapor excess means less condensate and weaker rain —
check icon:rain_gsp_rate in the output. Any config field in SCMConfig
above can be changed the same way (the slow-physics cadence
slow_timestep, the timestep dtime, the number of levels nlev, …), and
anything the configuration cannot express — reordering processes, removing
the consumer of a published tendency — is exactly what the constraint and
bus checks are there to catch: try
build_scm(SCMConfig(), fast_order=("mphys", "satad")) and read the error.
New terms introduced on this page: preset, slow-tendency bus.
Next (planned): T3 — Processes as components: calling saturation adjustment by hand. See the curriculum.