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 aSequentialTendencyStepperintegrating 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 steppersEₗ*, which may differ fromEₗ— thesis §2.3.5), the core overΔt, sections forward over(1-λ)Δt; built from twoSequentialUpdateSplittingpasses.λ = ½(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 physicssectionsare traversed in reverse overλΔt(2.13a-b), the dynamicscoreruns over the fullΔt(2.13c), then the sections run forward over(1-λ)Δt(2.13d-e).pre_steppersoptionally names, per section, the scheme used on the pre side (Eₗ* ≠ Eₗis legal, thesis §2.3.5); entries must beNonefor 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:
- 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).
- 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·ψⁿwhereLcounts 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 aSequentialTendencyStepper— 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 inSequentialUpdateSplitting).- 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:
- 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 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.
- load_restart_state(restart)¶
Route restart entries back to the owning members.
- functional_state()¶
Union of the members’ carry schemas, namespaced by member position.
- Return type:
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 wrappedConcurrentCoupling(or bare tendency component):ψⁿ⁺¹ = E(ψⁿ, Δt; P). Resolved by name throughTendencyStepper.factory(name, coupling)so user-defined steppers are first-class citizens of every federation.SequentialTendencyStepper— the two-input-state signatureE(ψⁿ, Δ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 - ψⁿ)/Δtadded 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)resolvesnamethrough the S02 registry;couplingis aConcurrentCouplingor 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:
SlowTendencyBus.declare()registers a slot (name + canonical units);SlowTendencyBus.publish()/SlowTendencyBus.consume()record which component writes/reads a slot, validated against the component’s property dicts;SlowTendencyBus.check()enforces that every published slot has exactly one consumer (0 or ≥2 both reject: a dangling tendency silently loses physics, a double consumer double-applies it) and that every consumed slot is published.
- 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:
- publish(component, *slot_names)¶
Record that
componentwrites the named slots (validated against outputs).
- consume(component, *slot_names)¶
Record that
componentreads the named slots (validated against inputs).
- class icon_sc.core.coupling.bus.TendencySlot(name, units, dims=None)¶
One bus slot: a named state field carrying a piecewise-constant tendency.
nameis the state key (icon:ddt_*convention downstream);unitsare the canonical tendency units;dims, when given, are checked against publisher/consumer declarations.
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 componentname), 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 federationkindstrings:"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).
- icon_sc.core.coupling.constraints.constraints_of(component)¶
The component’s declared constraints (empty constraints when undeclared).
- Parameters:
component (Any)
- Return type:
- icon_sc.core.coupling.constraints.validate_composition(components, *, operator, ordered=True)¶
Check a composition against every member’s constraints (SPEC S04).
componentsis 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 checksadmissible_operatorsonly.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_precedebind only when the named component is present: for a component at positioni, every name inmust_followpresent in the composition must first occur beforei, and every name inmust_precedemust last occur afteri.
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].dataoverfields(frozen helper, SPEC S04).fieldsdefaults to the keys shared by both dicts (timeexcluded). Values are boundary DataArrays; the arithmetic happens on their.databuffers, so buffer identity inyis preserved (the property the S05 vault relies on).
- icon_sc.core.coupling.dictops.dict_fma(base, increment, factor, fields=None)¶
Out-of-place
base[f].data + factor * increment[f].dataper field.