icon_sc.core.components — component taxonomy

Component taxonomy (§4.1), control-flow wrappers and the dynamical-core base (§4.2).

Component taxonomy with the Ubbiali ABI (architecture §4.1-§4.2, SPEC S03).

The five sympl kinds — DiagnosticComponent, TendencyComponent, ImplicitTendencyComponent, Stepper, Monitor — with the object-oriented ABI adopted from stubbiali/sympl oop (see REFERENCES.lock):

  • __call__(state, timestep, *, out=None) with caller-provided output DataArrays (out is one flat name -> DataArray mapping across the component’s output property dicts);

  • array_call(inputs, outputs, timestep) receiving both input and output raw buffers and writing outputs in place;

  • allocate_output(name, schema, ctx) invoked exactly once per output field the caller did not provide;

  • the restart protocol (restart_state/load_restart_state) and the explicit-carry schema functional_state() (§4.5, §8.5).

All checking routes through S02’s contracts machinery: __init_subclass__ runs the StaticChecker at class creation; __call__ runs the DynamicChecker + Ingress/ EgressPlan per call — T0’s fully dynamic reference semantics — with one negotiation cached per (instance, state-schema) so an unchanged schema does not renegotiate (PLAN item 2; not the S05 plan compiler: dicts and DataArrays stay on the path).

class icon_sc.core.components.base.Component(*, ctx=None, name=None)

Shared machinery of the four callable component kinds (frozen ABI, SPEC S03).

Subclasses declare property dicts as class attributes; they are validated by the StaticChecker at class creation (__init_subclass__), so a component declaring, e.g., non-canonical units never constructs. Concrete subclasses implement array_call() only — the base owns negotiation, allocation and egress.

Parameters:
output_dict_names: ClassVar[tuple[str, ...]] = ()

Which property dicts are outputs of this kind, in egress order.

timestep_required: ClassVar[bool] = False

Whether __call__ requires a timestep (Stepper/ImplicitTendencyComponent).

property ctx: ComputeContext

The compute context this component allocates and checks against.

property parsed_properties: Mapping[str, Mapping[str, PropertySpec]]

Parsed property dicts (per-class, produced by the StaticChecker).

abstract array_call(inputs, outputs, timestep)

Compute on raw buffers, writing every output in place (frozen ABI).

inputs/outputs are keyed by contract field names (aliases already resolved); outputs is the flat union of the kind’s output dicts.

Parameters:
Return type:

None

allocate_output(name, schema, ctx)

Allocate one output buffer (frozen ABI); called only for fields not in out=.

Default: an uninitialized buffer from the context allocator. Override for framework-side allocation (gt4py fields, pooled buffers, …).

Parameters:
Return type:

FieldBuffer

restart_state()

Component-private persistent fields to serialize (default: stateless).

Return type:

dict[str, DataArray]

load_restart_state(restart)

Restore component-private fields from restart_state() output.

Parameters:

restart (Mapping[str, DataArray])

Return type:

None

functional_state()

Schema of the private carry surfaced to the F-tier (§8.5; default empty).

Return type:

Mapping[str, PropertySpec]

visit(plan_builder)

Double-dispatch hook of the S05 plan compiler (and later tree walks).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.components.base.DiagnosticComponent(*, ctx=None, name=None)

Pure function of the state at one time: computes diagnostics (§4.1).

Parameters:
output_dict_names: ClassVar[tuple[str, ...]] = ('diagnostic_properties',)

Which property dicts are outputs of this kind, in egress order.

class icon_sc.core.components.base.ImplicitTendencyComponent(*, ctx=None, name=None)

A TendencyComponent whose tendencies depend on the timestep (§4.1).

Parameters:
output_dict_names: ClassVar[tuple[str, ...]] = ('tendency_properties', 'diagnostic_properties')

Which property dicts are outputs of this kind, in egress order.

timestep_required: ClassVar[bool] = True

Whether __call__ requires a timestep (Stepper/ImplicitTendencyComponent).

class icon_sc.core.components.base.Monitor(*, name=None)

Consumes states for output/inspection; never on the component call path.

Parameters:

name (str | None)

abstract store(state)

Store the given state (side effect defined by the concrete monitor).

Parameters:

state (Mapping[str, Any])

Return type:

None

class icon_sc.core.components.base.OutputSchema(spec, shape, dtype)

What Component.allocate_output() needs to allocate one output field.

Parameters:
class icon_sc.core.components.base.Stepper(*, ctx=None, name=None)

Steps the state forward by one timestep with its own numerics (§4.1).

Parameters:
output_dict_names: ClassVar[tuple[str, ...]] = ('diagnostic_properties', 'output_properties')

Which property dicts are outputs of this kind, in egress order.

timestep_required: ClassVar[bool] = True

Whether __call__ requires a timestep (Stepper/ImplicitTendencyComponent).

class icon_sc.core.components.base.TendencyComponent(*, ctx=None, name=None)

Computes instantaneous tendencies (+ diagnostics) of the state (§4.1).

Parameters:
output_dict_names: ClassVar[tuple[str, ...]] = ('tendency_properties', 'diagnostic_properties')

Which property dicts are outputs of this kind, in egress order.

DynamicalCore — the three-cadence-tier core base class (SPEC S04).

Tasmania’s base class for two-time-level, multi-stage cores (thesis §3.5, Fig. 3.8-3.10), whose tier terminology Tasmania itself borrowed from ICON:

  • slow tier — tendencies computed outside the core and consumed inside at the point the numerics dictate: the slow-tendency input port. Port slots are ordinary input_properties entries (state fields, icon:ddt_* convention; the SlowTendencyBus is their checker); the tendency_port class attribute maps each prognostic to its slot. Slow tendencies are held constant across the step — the port buffers are never written.

  • fast tier — an optional per-stage fast_tendency_component (ConcurrentCoupling) evaluated once per stage on the latest provisional state (FC within the core); its tendencies are summed onto the slow port values into per-stage scratch buffers. Empty in the ICON preset; the natural experiment port.

  • super-fast tier — substepping with its own hook: stage s performs max(1, round(substep_fraction[s] · N)) substeps of Δt/N each, where N comes from substeps (static) or ratio_provider (adaptive, CFL-style; called once per step with the state — the semantics shared with Subcycle). Fig. 3.10’s example is a 3-stage Wicker-Skamarock core with N = 6 and fractions (⅓, ½, 1) → 2, 3, 6 substeps.

Subclass contract (frozen, SPEC S04): implement stage_array_call(stage, inputs, outputs, dt) and substep_array_call(stage, substep, inputs, outputs, dt); declare n_stages and substep_fraction. Hooks receive raw buffers (the S03 ABI): inputs holds the latest provisional state plus the combined (slow + fast) tendency values under the port slot names, and — inside substeps — the enclosing stage’s outputs under "stage/<name>" keys.

The orchestration is numpy-level T0 reference semantics (scratch buffers via numpy.empty_like); the S05 plan compiler unrolls the tiers into the op list.

class icon_sc.core.components.dycore.DynamicalCore(*, fast_tendency_component=None, substeps=0, ratio_provider=None, ctx=None, name=None)

Multi-stage, three-tier dynamical core base (frozen interface, SPEC S04).

Stepper-shaped: (state, timestep, *, out=None) -> (diagnostics, new_state). Subclasses declare input_properties (prognostics + slow-port slots + any static fields), output_properties (the prognostics) and optionally diagnostic_properties; every prognostic must also be an input, and tendency_port maps prognostics to their slow-tendency slot names.

Parameters:
output_dict_names: ClassVar[tuple[str, ...]] = ('diagnostic_properties', 'output_properties')

Which property dicts are outputs of this kind, in egress order.

timestep_required: ClassVar[bool] = True

Whether __call__ requires a timestep (Stepper/ImplicitTendencyComponent).

substep_nesting: ClassVar[str] = 'stage_outer'

How the stage and super-fast tiers nest (S14, additive ClassVar): "stage_outer" — the Fig. 3.10 default this base class orchestrates (each stage runs its own substep block); "substep_outer" — ICON’s nesting (every substep runs the full stage sequence, mo_nh_stepping.f90::perform_dyn_substepping). A substep-outer core overrides array_call with its own orchestration and implements the plan-hook quartet the S05/S14 compiler unrolls the step into (all follow the materialized (*prefix, inputs, outputs, timestep) BoundCall pack):

  • plan_ingress(n_substeps, inputs, outputs, dt) — step entry: boundary buffers → component-private state (§4.5); dt is the full Δt;

  • plan_substep_begin(substep, inputs, outputs, sub_dt) — the component-private carry swaps that precede the substep’s stages;

  • substep_array_call(stage, substep, inputs, outputs, sub_dt) — the frozen S04 hook, one stage of one substep;

  • plan_substep_end(substep, inputs, outputs, sub_dt) — the private time-level swap between substeps (not emitted after the last);

  • plan_egress(inputs, outputs, dt) — step exit: private state → boundary output buffers, plus step bookkeeping.

Private swaps stay inside these BoundCalls — the vault only ever holds boundary fields and their step-level ping-pong (§8.2/§4.5).

n_stages: ClassVar[int] = 1

Number of stages of the time-marching scheme (subclass contract).

substep_fraction: ClassVar[float | tuple[float, ...]] = 1.0

Per-stage fraction of the total substep count N (scalar broadcasts).

tendency_port: ClassVar[Mapping[str, str]] = {}

Prognostic field name -> input slot carrying its slow tendency (may be partial).

property fast_tendency_component: ConcurrentCoupling | None

The per-stage fast coupling (empty in the ICON preset).

property substeps: int

The static super-fast substep count (0 = tier disabled).

property ratio_provider: Callable[[Mapping[str, Any]], int] | None

The adaptive substep-count provider (shared semantics with Subcycle).

property substep_fractions: tuple[float, ...]

substep_fraction normalized to one entry per stage.

visit(plan_builder)

S05 plan-compiler hook: unroll the stage/substep tiers (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

abstract stage_array_call(stage, inputs, outputs, dt)

Integrate one stage on raw buffers, writing every output in place.

inputs: the latest provisional state (field names) plus the combined slow+fast tendencies under the tendency_port slot names. outputs: the stage’s prognostic buffers plus the core’s diagnostic buffers. dt is the full timestep; the stage’s own coefficients decide spans.

Parameters:
Return type:

None

abstract substep_array_call(stage, substep, inputs, outputs, dt)

Integrate one super-fast substep on raw buffers.

inputs: the current substepped state (field names), the combined tendencies (slot names) and the enclosing stage’s outputs under "stage/<name>" keys. dt is the substep size Δt/N.

Parameters:
Return type:

None

array_call(inputs, outputs, timestep)

Tier orchestration (Fig. 3.10); subclasses implement the hooks instead.

Parameters:
Return type:

None

Control-flow wrappers: CallingFrequency, Subcycle, ScalingWrapper (SPEC S03).

Pure control flow around the §4.1 kinds — at T0 they are ordinary callables honoring the component ABI (__call__(state, timestep, *, out=None), property dicts delegated to the wrapped component); at T1+ they dissolve into the execution plan (§8.2: cadence masks, unrolled subcycles, folded constants).

Semantics donors (REFERENCES.lock): sympl UpdateFrequencyWrapper (firing rule + cached output) for CallingFrequency; tasmania’s per-section substeps for Subcycle; sympl ScalingWrapper verbatim in spirit.

class icon_sc.core.components.wrappers.CallingFrequency(component, dt)

Reduced calling frequency with piecewise-constant cached output (§4.2 LFC).

CallingFrequency(component, dt) calls the wrapped component only when state["time"] has advanced at least one effective period past the last update, returning the cached output verbatim in between (ICON’s slow-physics choice: lazy evaluation stretched over N steps).

Rounding-to-multiple rule: when a call supplies the loop timestep, the effective period is the nearest positive integer multiple of it (exact integer-microsecond arithmetic, ties round up); without a timestep the raw per-process dt applies. Phase (the last update time) and the cached output are component-private carry: surfaced by restart_state() and declared in functional_state() (S10 relies on this being carry).

Parameters:
period_for(timestep)

Effective period under the rounding-to-multiple rule (SPEC S03).

Parameters:

timestep (timedelta | None)

Return type:

timedelta

property update_period: timedelta

The raw per-process dt (pre rounding-to-multiple; S05 accessor).

property last_update_time: Any

The firing phase (None until the first call; S05 bind accessor).

visit(plan_builder)

S05 plan-compiler hook: dissolve into a cadence mask (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.components.wrappers.ComponentWrapper(component)

Shared delegation base of the control-flow wrappers.

Property dicts, names and every other attribute delegate to the wrapped component via __getattr__. Wrappers accept Component | ComponentWrapper so they compose (CallingFrequency(Subcycle(...), ...)) without casts.

Parameters:

component (Component | ComponentWrapper)

property component: Component | ComponentWrapper

The wrapped component.

visit(plan_builder)

Refuse plan compilation for unknown wrappers (S05).

Defined on the base so __getattr__ can never silently delegate the walk to the wrapped component (which would dissolve the wrapper’s semantics); the three known wrappers override with their real hooks.

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.components.wrappers.ScalingWrapper(component, *, input_scale_factors=None, tendency_scale_factors=None, diagnostic_scale_factors=None, output_scale_factors=None)

Scale selected inputs/outputs of a wrapped component (sympl semantics).

Scale-factor dict keys are validated against the wrapped component’s property dicts at construction. Inputs are scaled into attr-preserving copies before the call (allocating — T0/debug affordance); outputs/tendencies/diagnostics are scaled in place after the call, so out= pointer identity is preserved.

Parameters:
property scale_factors: Mapping[str, Mapping[str, float]]

The validated scale-factor dicts, keyed by factor-dict name (S05 accessor).

visit(plan_builder)

S05 plan-compiler hook: fold into bound constants (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.components.wrappers.Subcycle(stepper, n=None, ratio_provider=None)

Run a Stepper n times over timestep / n (§4.2 combinator).

Exactly one of n (static) and ratio_provider (adaptive: called once per outer step with the current state, must return an integer >= 1) is given. Intermediate sub-states chain through the stepper (tasmania’s substep semantics); out= is forwarded to the final substep only, so earlier substeps never alias the caller’s buffers. state["time"] is not advanced between substeps (deliberately dumb at T0; the plan compiler owns cadence).

Note

Because time does not advance between substeps, a time-triggered wrapper inside a subcycle — Subcycle(CallingFrequency(...), ...) — degenerates to at most one effective fire per outer step: every substep after the first sees an unchanged state["time"] and replays the cache.

Parameters:
property n: int | None

The static substep count (None under a ratio_provider; S05 accessor).

property ratio_provider: Callable[[Mapping[str, Any]], int] | None

The adaptive substep-count provider (S05 accessor).

visit(plan_builder)

S05 plan-compiler hook: unroll with bound dt (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None