icon_sc.core.functional — the functional (JAX) lowering¶
The §8.5-8.6 functional lowering (F-tier), proven at column scale (SPEC S10).
Layout (architecture §8.5; jax-touching core code lives here and nowhere else in
icon_sc.core — importing icon_sc.core itself never imports jax):
icon_sc.core.functional.pytree— generated frozen-dataclass PyTrees:StateTreefrom the state/vault schema plus explicit carry,ParamTreefrom the components’paramsdeclarations.icon_sc.core.functional.compile— composition → purestep_fn(StateTree, ParamTree, StaticArgs) -> StateTreeand thescan_window(lax.scan+ per-stepjax.checkpoint) window builder.icon_sc.core.functional.rules— implicit-function-theorem helpers for fixed points (lax.custom_rootwrappers; the §8.6customroute).
fp64 is the default for gradient work (§8.6): the compile entry point warns when
jax runs in fp32 (jax.config.update("jax_enable_x64", True)).
Functional compile: composition → pure step_fn + scan_window (§8.5, SPEC S10).
functional_compile(composition, state, timestep=...) is the F-tier consumer
of the composition walk (the same visit(plan_builder) double-dispatch the
S05 plan compiler uses): instead of an imperative op list it emits a pure JAX
function over explicit PyTrees,
step_fn: (StateTree, ParamTree, StaticArgs) -> StateTree # one Δt, traced window = scan_window(step_fn, n_steps, remat=”per_step”) # lax.scan + checkpoint
The semantic mapping is mechanical (§8.5): sequential updates become functional
updates (memory recovered by donate_argnums under jit); cadence wrappers
become carry (cached output + last-fire phase) selected by jnp.where on a
carried step counter — the trace is static across step signatures; monitors and
time are outside the trace.
Component protocol (§8.6 native/custom): a compilable component
provides a pure functional_call(inputs, params, dt=...) -> outputs — inputs
keyed by its contract names, outputs the flat union of its output dicts —
co-located with its imperative kernel and drawing on the same scheme-constants
module, plus functional_params() -> {name: default} for its params
declarations. Components whose contracts declare no native/custom
output are none under the differentiability contract: per differentiated
region the composition-time policy is "error" (default) or
"stop_gradient" — explicit, warned, and stamped into provenance. Gradient
truncation is never silent.
- exception icon_sc.core.functional.compile.FunctionalCompileError¶
The composition cannot be lowered to a pure function (names the node).
- class icon_sc.core.functional.compile.FunctionalProgram(step_fn, state, params, static, state_type, param_type, carry_names, provenance)¶
The compiled F-tier program (frozen interface, SPEC S10).
step_fn(state, params, static) -> stateis pure and traced;state/paramsare instances of the generatedstate_type/param_typePyTrees;provenancestamps every composition-time decision (cadence folds,stop_gradienttruncations, freezes) — never silent (§8.6).
- class icon_sc.core.functional.compile.StaticArgs(dt)¶
Per-call static/scalar arguments of the pure step (frozen interface, SPEC S10).
dtmust equal thetimestepthe program was compiled against: cadence periods are folded into the trace as step counts at compile time.- Parameters:
dt (float)
- icon_sc.core.functional.compile.functional_compile(composition, state, *, timestep, policy='error')¶
Lower a composition to a pure
step_fnover explicit PyTrees (SPEC S10).compositionis one composition node or an ordered sequence of nodes (the §5.1 loop-body order — e.g. the SCM preset’s(slow, core, fast)); every node lowers through the samevisit(plan_builder)protocol the S05 plan compiler consumes.stateis the schema-representative boundary state the trees are generated from.policygovernsdifferentiable: 'none'components per §8.6:"error"(default) or"stop_gradient".
- icon_sc.core.functional.compile.scan_window(step_fn, n_steps, *, remat='per_step', ys_of=None)¶
A multi-step window over
step_fn:lax.scan+ checkpoint policy (§8.5).Returns
window(state, params, static).remat="per_step"wraps the step injax.checkpoint(reverse-mode memory ≈ one extra forward, §8.7);remat=Nonestores all activations.ys_ofoptionally maps each post-step state to a per-step observable; the window then returns(final_state, stacked_ys)instead offinal_state.
Generated frozen-dataclass PyTrees: StateTree and ParamTree (§8.5, SPEC S10).
StateTree is derived from the state/vault schema — one leaf per slot —
extended with explicit carry: every leaf a component declared in
functional_state() is surfaced into the tree (tension T7: §4.5’s privacy is
an imperative-tier convenience, demoted here by contract). ParamTree holds
the tunable scheme constants of the §8.6 params declarations, distinct from
state — calibration never smuggles constants through state fields.
Leaf ordering is deterministic: leaves are sorted by canonical name at type
generation (PLAN pitfall — carry ordering must not depend on dict insertion
order). Canonical names (icon:qnc, fcarry/0/...) are not identifiers;
they are sanitized into attribute names with the canonical → attribute map kept
on the generated class (__icon_sc_leaves__).
- icon_sc.core.functional.pytree.build_param_tree(params, *, type_name='ParamTree')¶
ParamTree type + instance from a flat name → default-value mapping (§8.6).
- icon_sc.core.functional.pytree.build_state_tree(state, extra_leaves=None, *, type_name='StateTree')¶
StateTree type + instance from a state dict (one leaf per slot) plus carry.
stateis a boundary state (dict of DataArrays;timeis skipped — the F-tier trace has no clock, cadence rides in the carry).extra_leavesare the explicit-carry leaves and any compiler-seeded slots (already arrays).
- icon_sc.core.functional.pytree.make_pytree_type(type_name, leaf_names)¶
Generate a frozen-dataclass PyTree type with one field per leaf name.
Leaves are sorted by canonical name; the generated class is registered with
jax.tree_util.register_dataclass(all fields are data fields) and carries__icon_sc_leaves__: the(canonical_name, attribute_name)pairs in field order.
- icon_sc.core.functional.pytree.mapping_of(tree)¶
The canonical-name → leaf mapping of a generated PyTree instance.
- icon_sc.core.functional.pytree.sanitize_leaf_name(name)¶
A valid Python attribute name for one canonical leaf name.
- icon_sc.core.functional.pytree.tree_of(cls, values)¶
Instantiate a generated PyTree type from a canonical-name → leaf mapping.
Implicit-function-theorem rules for fixed points (§8.6 custom route, SPEC S10).
Implicit structure gets IFT treatment rather than unrolling: the fixed point
differentiates through a lax.custom_root-style rule, not through recorded
Newton iterations — which also sidesteps while_loop’s reverse-mode
prohibition (the solver runs inside custom_root’s non-differentiated primal).
Verified on the pinned jax (REFERENCES.lock jax): lax.custom_root with a
linear elementwise tangent_solve supports both jvp and vjp
(reverse mode derives from the JVP by transposition), with a bounded
lax.while_loop inside solve.
- icon_sc.core.functional.rules.implicit_fixed_point(residual, x0, solve)¶
Solve
residual(x) == 0elementwise; differentiate via the IFT (§8.6).residualmust be elementwise inx(its Jacobian at the solution is diagonal): the linearized-residual solve is theny / g(1), which is linear — exactly whatlax.custom_rootneeds to derive both the JVP and, by transposition, the VJP. Everythingresidualcloses over is differentiated through the implicit function;solveis primal-only and may uselax.while_loop.
- icon_sc.core.functional.rules.masked_newton_solve(residual, residual_prime, x0, *, active0, tolerance, max_iter)¶
Elementwise Newton with per-point convergence freeze (primal-only solver).
Mirrors the granule-style masked iteration (icon4py satad, REFERENCES.lock
icon4py-satad-stencils): points update only while their mask is active; the mask deactivates when|Δx| <= tolerance; the global loop runs while any point is active, bounded bymax_iter(a data-dependent raise is not expressible underjit— the T0 granule raisesConvergenceErrorinstead; deviation recorded in STATUS S10). Meant to be passed as thesolveofimplicit_fixed_point(), so its iterations are never differentiated.