icon_sc.core.contracts — property contracts

Contracts: property-dict schema, static/dynamic checkers, ingress/egress plans.

Typed property-dict schema (architecture §2.4, §8.6).

Components keep sympl’s input_properties / tendency_properties / diagnostic_properties / output_properties dicts; ICON-sc’s schema extends the sympl entries (dims, units, alias) with location, halo, differentiable (§8.6) and params (§8.6), and drops sympl’s wildcard/dims-like machinery — canonical names + canonical units make target dims explicit.

parse_properties() validates the dicts themselves (definition-only, no data) and normalizes them into frozen, slotted PropertySpec records; it is the substrate the static checker runs on.

class icon_sc.core.contracts.properties.Differentiable(value)

Differentiability axis of the contract (§8.6).

class icon_sc.core.contracts.properties.HaloPolicy(value)

Contract-side halo requirement of one field in one property dict (§2.4).

exception icon_sc.core.contracts.properties.PropertyDictError

A property dict is malformed (definition-time error, names the component).

class icon_sc.core.contracts.properties.PropertySpec(name, dims, units, location, halo=None, alias=None, dtype=None, differentiable=Differentiable.NONE, params=())

Normalized, immutable form of one property-dict entry (frozen interface).

Parameters:
icon_sc.core.contracts.properties.parse_properties(properties)

Validate a property dict and normalize it (frozen interface, SPEC S02).

Checks the dict itself — entry types, required/unknown keys, enum values, alias bijectivity — with errors naming the offending field. Insertion order is kept (it is the ingress argument order).

Parameters:

properties (Mapping[str, Any])

Return type:

Mapping[str, PropertySpec]

Static and dynamic contract checkers (architecture §4.2 ← Ubbiali sympl fork).

The static/dynamic split is the internal structure of the negotiation phase (§8.2):

  • StaticChecker consumes component definitions only — it validates the property dicts of a component class at composition time (invoked from the __init_subclass__ hook S03 wires).

  • DynamicChecker crosses definitions with actual data at bind time: it checks one property dict against a state (or a lightweight StateSchema, so S05 can reuse it without real data). strict=True (production, §2.4) turns any ingress that would allocate — unit conversion, dim transpose, dtype cast, host↔device transfer — into an exception naming field and component; strict=False collects the needed conversions into a ConversionPlan instead.

Neither checker runs on the step path.

icon_sc.core.contracts.checkers.PROPERTY_DICT_NAMES: tuple[str, ...] = ('input_properties', 'tendency_properties', 'diagnostic_properties', 'output_properties')

The sympl property dicts a component may define (§2.4).

class icon_sc.core.contracts.checkers.ContractViolation(field, component, kind, actual, target)

One convertible contract violation: field, component, kind, actual vs wanted.

Parameters:
exception icon_sc.core.contracts.checkers.ContractViolationError(violations)

Strict-mode contract failure; carries every violation found.

Parameters:

violations (list[ContractViolation])

Return type:

None

class icon_sc.core.contracts.checkers.DynamicChecker(spec, state, *, component='<component>', strict=True, device=None)

Definition x data checks (frozen interface, SPEC S02).

DynamicChecker(spec, state) — spec is a parsed property dict (Mapping[str, PropertySpec]), state a dict-of-DataArrays or a StateSchema. Missing fields, incompatible dim sets and location mismatches raise unconditionally (no conversion exists). Convertible mismatches — units, dim order, dtype, device — raise under strict=True (each named by field + component) and are collected into self.plan (a ConversionPlan) under strict=False.

Device expectation: device (a DLPack device tuple) is normally supplied by the caller (S03’s ComputeContext / S05’s plan compiler). With device=None the checker only enforces that all spec’d fields share one device, adopting the device of the first field in property-dict order as the expectation — so which field gets flagged on a mixed-device state depends on property-dict order. Pass device explicitly whenever a backend-mandated device exists.

Parameters:
class icon_sc.core.contracts.checkers.FieldSchema(dims, units, dtype, device, location=None)

Shape-free schema of one state field: what the dynamic checkers consume.

Parameters:
class icon_sc.core.contracts.checkers.StateSchema(fields)

name → FieldSchema map; the data-free face of a state (§8.2, S05).

Parameters:

fields (Mapping[str, FieldSchema])

classmethod from_state(state)

Derive the schema of a dict-of-DataArrays state (skips the time key).

Parameters:

state (Mapping[str, Any])

Return type:

StateSchema

class icon_sc.core.contracts.checkers.StaticChecker(component_cls)

Definition-only checks on a component class (frozen interface, SPEC S02).

StaticChecker(component_cls) validates every property dict the class defines (schema of the dicts themselves), cross-dict consistency (dims and canonical units of same-named state-valued entries, alias bijectivity) and — for names known to the canonical registry — that declared units are the canonical units. A constructed instance means the definition passed and exposes the parsed specs.

Raises:

PropertyDictError – Naming the offending component and field.

Parameters:

component_cls (type)

ConversionPlan executor: allocating non-strict ingress (SPEC S03, §2.4).

Strict mode turns any ingress that would allocate into an error; with strict=False the DynamicChecker collects the needed conversions into a ConversionPlan instead, and this module executes that plan — the debugging/education path of T0 (never the production path, never the step path).

apply_conversion_plan returns a new shallow-copied state whose offending fields are replaced by converted copies; the input state is never mutated.

exception icon_sc.core.contracts.conversion.ConversionError

A conversion step cannot be executed (unsupported kind or bad data).

icon_sc.core.contracts.conversion.apply_conversion_plan(plan, state, *, component='<component>')

Execute plan against state (allocating; negotiation-time only).

Steps are applied in plan order, several per field composing left to right. The returned dict shares every untouched entry with state; converted fields are copies with their attrs schema (units/location/halo) preserved and updated. Errors name field and component.

Parameters:
Return type:

dict[str, Any]

Dynamic operators: ingress/egress plans and conversion plans (§2.3, §4.2, §8.2).

An IngressPlan is built once at bind time from a property dict and a StateSchema (S05 pre-resolves it into bound argument packs) and applied per step by S03’s interpreted tier. The plan holds field names only — schema in, raw buffers out; no xarray objects, no Pint, no per-step negotiation. apply is a plain tuple comprehension over state[name].data: zero-copy by construction (buffer identities are stable across applications).

ConversionPlan is what the non-strict dynamic checker returns instead of raising: the ordered allocating conversions (units/transpose/cast/transfer) that would reconcile a state with a contract. S03 executes it; strict mode forbids it.

class icon_sc.core.contracts.operators.ConversionPlan(steps=())

Ordered conversions the non-strict checker proposes (empty = no-op ingress).

Parameters:

steps (tuple[ConversionStep, ...])

class icon_sc.core.contracts.operators.ConversionStep(field, kind, source, target)

One allocating reconciliation of a field with its contract.

Parameters:
class icon_sc.core.contracts.operators.EgressPlan(component, fields, names)

Egress twin of IngressPlan: resolves a component’s output buffers.

Mechanically identical — outputs are caller-provided/preallocated DataArrays living in the same state mapping (§8.2 buffer-identity contract), so egress is the same pre-resolved raw-buffer extraction, kept as its own type so plans are self-describing in S03/S05 op lists.

Parameters:
class icon_sc.core.contracts.operators.IngressPlan(component, fields, names)

Pre-resolved zero-copy ingress (frozen interface, SPEC S02).

names are the state keys to pull, in property-dict (= argument) order, with aliases already resolved; fields are the contract names they satisfy.

Parameters:
classmethod build(spec, state_schema, *, component='<component>', device=None)

Build the plan at bind time; strict by construction.

Runs the strict dynamic checker: a state that would need any allocating conversion cannot be pre-resolved (run the DynamicChecker with strict=False and execute its plan first). device is the DLPack device expectation forwarded to the checker; None falls back to the checker’s first-field baseline (see its docstring).

Parameters:
Return type:

IngressPlan

apply(state)

Extract the raw buffers, in plan order (frozen interface, SPEC S02).

Pure lookups: no validation, no conversion, no copies — negotiation already happened in build(). Never touches .values (§4.2).

Parameters:

state (Mapping[str, Any])

Return type:

tuple[FieldBuffer, …]