icon_sc.core.coupling — the coupling algebra

The coupling algebra (architecture §4.2, after Tasmania): couplings, steppers, federations, the slow-tendency bus and coupling constraints.

The four federations: PS, STS, SUS, SSUS (architecture §4.2, SPEC S04).

Each federation realizes one of the thesis’s coupling strategies over a list of sections. A section is either a bare Stepper-shaped component (adjustment-type processes enter directly; wrappers and dynamical cores qualify by duck kind) or a (TendencyComponent, stepper_name) pair resolved through the S02 registries of icon_sc.core.coupling.steppers.

  • ParallelSplitting — thesis eq. (2.10): every section integrates independently from ψⁿ; the recombination ψⁿ⁺¹ = Σₗ ψₗⁿ⁺¹ - L·ψⁿ (2.10d) is a per-field axpy accumulation (dict_axpy() shape; a fused multi-axpy vault op at T1).

  • SequentialTendencySplitting — eq. (2.11): the first section (the dynamics) is stepped plainly from ψⁿ; every later section is a SequentialTendencyStepper integrating from the step-initial ψⁿ with the accumulated provisional state as forcing. Diagnostics land on the ψⁿ-level state (visible to later sections), provisional outputs accumulate separately (tasmania’s two-dict discipline).

  • SequentialUpdateSplitting — eq. (2.12): each section corrects the state left by its predecessor.

  • SSUS — eq. (2.13a-e): sections traversed in reverse over λΔt (per-side steppers Eₗ*, which may differ from Eₗ — thesis §2.3.5), the core over Δt, sections forward over (1-λ)Δt; built from two SequentialUpdateSplitting passes. λ = ½ (Strang) is the only choice with second-order coupling error regardless of problem structure.

Every federation is itself a component (Stepper-shaped: (state, timestep, *, out=None) -> (diagnostics, new_state)), so federations compose. Declared coupling constraints (icon_sc.core.coupling.constraints) are validated at construction.

class icon_sc.core.coupling.federations.SSUS(sections, core, lam=0.5, pre_steppers=None, *, name=None)

SSUS — thesis eq. (2.13a-e) (frozen interface, SPEC S04).

SSUS(sections, core, lam=0.5, pre_steppers=None): the physics sections are traversed in reverse over λΔt (2.13a-b), the dynamics core runs over the full Δt (2.13c), then the sections run forward over (1-λ)Δt (2.13d-e). pre_steppers optionally names, per section, the scheme used on the pre side (Eₗ* ≠ Eₗ is legal, thesis §2.3.5); entries must be None for bare-Stepper sections (they own their numerics). Constraints are validated on the forward order [core, *sections] under the "ssus" label; the reverse pass is its mirror by construction.

Parameters:
  • sections (Sequence[SectionSpec])

  • core (SectionSpec)

  • lam (float)

  • pre_steppers (Sequence[str | None] | None)

  • name (str | None)

kind: ClassVar[str] = 'ssus'

Federation kind label, used for constraint validation and errors.

property lam: float

The λ split; ½ is Strang splitting (second-order coupling error).

property pre: SequentialUpdateSplitting

The reverse-order λΔt pass (2.13a-b), as a SUS (S05 accessor).

property post: SequentialUpdateSplitting

The forward-order (1-λ)Δt pass (2.13d-e), as a SUS (S05 accessor).

property core: Any

The dynamics core stepper run over the full Δt (2.13c; S05 accessor).

visit(plan_builder)

S05 plan-compiler hook: doubled reversed λ-scaled op list (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.coupling.federations.ParallelSplitting(sections, *, name=None)

PS — thesis eq. (2.10) (frozen interface, SPEC S04).

Every section is integrated independently from ψⁿ; the provisional states are recombined per field as ψⁿ⁺¹ = Σₗ ψₗⁿ⁺¹ - L·ψⁿ where L counts the sections stepping that field (eq. 2.10d). Section diagnostics are all computed on ψⁿ (sections are order-independent by construction).

Parameters:
  • sections (Sequence[SectionSpec])

  • name (str | None)

kind: ClassVar[str] = 'parallel_splitting'

Federation kind label, used for constraint validation and errors.

visit(plan_builder)

S05 plan-compiler hook: one k-ary Axpy recombination (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.coupling.federations.SequentialTendencySplitting(sections, *, name=None)

STS — thesis eq. (2.11) (frozen interface, SPEC S04).

The first section (the dynamics, eq. 2.11a) is stepped plainly from ψⁿ; every later section must be a (TendencyComponent, stepper_name) pair resolved to a SequentialTendencyStepper — it integrates from the step-initial ψⁿ (eq. 2.11b), with the accumulated provisional state entering as the constant forcing (ψ_prov - ψⁿ)/Δt. Bare Steppers cannot express that signature and are rejected past position 0 (adjustment-type processes belong in SequentialUpdateSplitting).

Parameters:
  • sections (Sequence[SectionSpec])

  • name (str | None)

kind: ClassVar[str] = 'sequential_tendency_splitting'

Federation kind label, used for constraint validation and errors.

visit(plan_builder)

S05 plan-compiler hook: provisional slots + DiffScale forcings (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.coupling.federations.SequentialUpdateSplitting(sections, *, name=None, _validate=True, _operator_label=None)

SUS — thesis eq. (2.12) (frozen interface, SPEC S04).

Sections are chained: each corrects the state left by its predecessor (diagnostics included). Ordering is semantics and is validated against the sections’ declared coupling constraints at construction.

Parameters:
  • sections (Sequence[SectionSpec])

  • name (str | None)

  • _validate (bool)

  • _operator_label (str | None)

kind: ClassVar[str] = 'sequential_update_splitting'

Federation kind label, used for constraint validation and errors.

visit(plan_builder)

S05 plan-compiler hook: flatten into the op list (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

ConcurrentCoupling — tasmania’s heterogeneous composite (architecture §4.2).

ConcurrentCoupling(comps) bundles TendencyComponent, ImplicitTendencyComponent and DiagnosticComponent instances (wrappers and nested couplings included) into one tendency provider: tendency contributions are summed, diagnostics are unioned in declared order and — tasmania’s serial execution policy, the only one ported — each member sees the diagnostics computed by the members before it. It is the building block of full coupling (FC: evaluate at every integrator stage) and, held constant by the dycore’s slow port or by CallingFrequency, of lazy full coupling (LFC; thesis eq. 2.9).

The coupling is itself a component: it exposes the tendency-kind call signature (state, timestep, *, out=None) -> (tendencies, diagnostics), parsed property dicts, and the restart/functional-state protocol (delegated to its members), so couplings nest and wrap like any §4.1 component.

class icon_sc.core.coupling.concurrent.ConcurrentCoupling(components, *, name=None)

Sum tendencies, union diagnostics, chain serially (frozen interface, SPEC S04).

Parameters:
  • components (Sequence[Any])

  • name (str | None)

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

couplings nest inside couplings and steppers.

Type:

Tendency-kind fingerprint

timestep_required

Whether any member needs a timestep (ImplicitTendencyComponent semantics).

property components: tuple[Any, ...]

The wrapped components, in declared (serial) order.

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

Merged parsed property dicts (inputs net of internally chained diagnostics).

visit(plan_builder)

S05 plan-compiler hook: serial member walk (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

restart_state()

Union of the members’ private state, namespaced by member position.

Return type:

dict[str, DataArray]

load_restart_state(restart)

Route restart entries back to the owning members.

Parameters:

restart (Mapping[str, DataArray])

Return type:

None

functional_state()

Union of the members’ carry schemas, namespaced by member position.

Return type:

Mapping[str, PropertySpec]

Registry-based tendency-stepper family (architecture §4.2, SPEC S04).

Two Factory roots, each with the same four registered schemes (forward_euler, rk2 (Heun), rk3ws (Wicker-Skamarock), ssprk3 (Shu-Osher)):

  • TendencyStepper — steps a state from the tendencies of a wrapped ConcurrentCoupling (or bare tendency component): ψⁿ⁺¹ = E(ψⁿ, Δt; P). Resolved by name through TendencyStepper.factory(name, coupling) so user-defined steppers are first-class citizens of every federation.

  • SequentialTendencyStepper — the two-input-state signature E(ψⁿ, Δt; P + (ψ_prov - ψⁿ)/Δt) (thesis eq. 2.11b) that makes sequential-tendency splitting expressible: the accumulated provisional tendency of upstream processes enters every tendency evaluation as a constant forcing.

Scheme provenance (REFERENCES.lock): forward_euler/rk3ws coefficients from tasmania’s tendency steppers (rk3ws provisional fractions 1/3, 1/2, 1 per Doms & Baldauf’s COSMO documentation); rk2 is Heun per SPEC S04 — sympl’s 2-stage SSP Runge-Kutta — which deviates from tasmania’s midpoint rk2 (recorded in STATUS.md); ssprk3 from sympl’s 3-stage SSP Runge-Kutta (Shu & Osher 1988). The generic-forcing formulation of the sequential steppers is algebraically identical to tasmania’s fused first-stage ops (sts_rk2_0: ½(ψⁿ + ψ_prov + Δt·P); sts_rk3ws_0: (2ψⁿ + ψ_prov + Δt·P)/3).

Diagnostics returned by a stepper are those of the first tendency evaluation, i.e. diagnosed at ψⁿ (tasmania’s convention); later stage evaluations feed the scheme only.

class icon_sc.core.coupling.steppers.SequentialTendencyStepper(coupling)

E(ψⁿ, Δt; P + (ψ_prov - ψⁿ)/Δt) — registry root (frozen interface, SPEC S04).

The two-state signature of thesis eq. (2.11b): the integration starts from the step-initial state ψⁿ and the provisional state of the upstream sections enters as the constant forcing (ψ_prov - ψⁿ)/Δt added to every tendency evaluation.

Parameters:

coupling (Any)

visit(plan_builder)

S05 plan-compiler hook (only meaningful as an STS section, §8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

class icon_sc.core.coupling.steppers.TendencyStepper(coupling)

ψⁿ⁺¹ = E(ψⁿ, Δt; P) — registry root (frozen interface, SPEC S04).

TendencyStepper.factory(name, coupling) resolves name through the S02 registry; coupling is a ConcurrentCoupling or a bare tendency component. Instances are Stepper-shaped components: (state, timestep, *, out=None) -> (diagnostics, new_state).

Parameters:

coupling (Any)

visit(plan_builder)

S05 plan-compiler hook: stage arithmetic as Axpy ops (§8.2).

Parameters:

plan_builder (PlanBuilder)

Return type:

None

The slow-tendency bus: slot declaration + single-consumer check (SPEC S04).

Architecture §4.2 (“the bus, reframed”): slow processes publish piecewise-constant tendencies into named state slots (icon:ddt_* convention) under CallingFrequency; the dynamical core and transport declare those slots as inputs (the DynamicalCore slow-tendency port). The bus is the naming-and-checking convention for that port — pure composition-time bookkeeping, never on the step path:

exception icon_sc.core.coupling.bus.BusError

A tendency-bus declaration or wiring rule is violated (composition time).

class icon_sc.core.coupling.bus.SlowTendencyBus

Composition-time registry of tendency slots and their wiring (SPEC S04).

property slots: dict[str, TendencySlot]

The declared slots, keyed by slot (state-field) name.

declare(slot)

Register a slot; re-declaring a name with a different contract rejects.

Parameters:

slot (TendencySlot)

Return type:

TendencySlot

publish(component, *slot_names)

Record that component writes the named slots (validated against outputs).

Parameters:
  • component (Any)

  • slot_names (str)

Return type:

None

consume(component, *slot_names)

Record that component reads the named slots (validated against inputs).

Parameters:
  • component (Any)

  • slot_names (str)

Return type:

None

check()

The single-consumer check (frozen interface, SPEC S04 acceptance 3).

Every published slot must have exactly one consumer; every consumed slot must have at least one publisher.

Raises:

BusError – Naming the slot and the offending components.

Return type:

None

class icon_sc.core.coupling.bus.TendencySlot(name, units, dims=None)

One bus slot: a named state field carrying a piecewise-constant tendency.

name is the state key (icon:ddt_* convention downstream); units are the canonical tendency units; dims, when given, are checked against publisher/consumer declarations.

Parameters:

Coupling constraints (architecture §4.2/§11 risk 7; SPEC S04).

Components may declare, via a coupling_constraints attribute holding a CouplingConstraints, where they are allowed to sit in a composition:

  • must_follow / must_precede — ordering constraints against other components (matched by component name), enforced for the sequential federations, where ordering is semantics;

  • admissible_operators — the coupling operators the component may enter at all (None = any). Operator labels are the federation kind strings: "parallel_splitting", "sequential_tendency_splitting", "sequential_update_splitting", "ssus".

Validation runs when a federation is constructed (composition time — never on the step path); a violation raises CouplingConstraintError naming both components involved. This is what decides whether a composite is merely structurally legal or also scientifically admissible (§4.2): experimental composites never inherit the validated-preset label.

exception icon_sc.core.coupling.constraints.CouplingConstraintError

A composition violates a component’s declared coupling constraints.

class icon_sc.core.coupling.constraints.CouplingConstraints(must_follow=(), must_precede=(), admissible_operators=None)

Declared placement constraints of one component (frozen interface, SPEC S04).

Parameters:
icon_sc.core.coupling.constraints.constraints_of(component)

The component’s declared constraints (empty constraints when undeclared).

Parameters:

component (Any)

Return type:

CouplingConstraints

icon_sc.core.coupling.constraints.validate_composition(components, *, operator, ordered=True)

Check a composition against every member’s constraints (SPEC S04).

components is the composition in execution order (the constraint carriers: the scientific components, not the steppers wrapped around them). ordered=False (parallel splitting: order carries no semantics) skips the ordering constraints and checks admissible_operators only.

Constraint names are matched against the innermost component of a wrapper chain (see _constraint_name()), so wrapping a component — CallingFrequency-wrapped slow physics being the canonical case — neither sheds the constraints it declares (attribute delegation) nor the constraints declared against it (name unwrapping).

must_follow/must_precede bind only when the named component is present: for a component at position i, every name in must_follow present in the composition must first occur before i, and every name in must_precede must last occur after i.

Parameters:
Return type:

None

Numpy-level dict arithmetic for the coupling algebra (SPEC S04, PLAN item 3).

The T0 recombinations — ParallelSplitting’s ψⁿ⁺¹ = Σψₗ - L·ψⁿ and the stage updates of the tendency steppers — reduce to a handful of axpy-shaped operations over name -> DataArray dicts. They are kept deliberately dumb (numpy expressions on .data buffers); the S05 plan compiler turns them into fused vault ops (architecture §8.2).

Semantics donor: tasmania’s DataArrayDictOperator (fma/iaddsub, see REFERENCES.lock) minus its gt4py stencil dispatch and unit-conversion paths — canonical units + strict mode make the payloads directly combinable.

icon_sc.core.coupling.dictops.dict_axpy(y, a, x, fields=None)

In place y[f].data += a * x[f].data over fields (frozen helper, SPEC S04).

fields defaults to the keys shared by both dicts (time excluded). Values are boundary DataArrays; the arithmetic happens on their .data buffers, so buffer identity in y is preserved (the property the S05 vault relies on).

Parameters:
Return type:

None

icon_sc.core.coupling.dictops.dict_fma(base, increment, factor, fields=None)

Out-of-place base[f].data + factor * increment[f].data per field.

Returns:

Plain buffers (not DataArrays), keyed by field — the stage-state payloads the tendency steppers thread between evaluations (tasmania fma).

Parameters:
Return type:

dict[str, Any]