Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Composable Models: Terms → Operators → Cycles

Authors
Affiliations
University of Valencia

somax models plug into the pipekit ecosystem along three seams that build on each other. This tutorial walks the whole path end to end:

  1. Terms — a model’s right-hand side as a sum of composable physics kernels, with per-term explicit/implicit tags for IMEX integration.

  2. Operators — wrapping a built model as a pipekit.Operator: a one-step pipeline stage that satisfies pipekit_cycle.ForwardModel and (for flat-config models) round-trips through pipekit.serial.

  3. Cycles — driving the stepping loop with pipekit_cycle.Cycle, threading state and collecting a trajectory.

The Operator/Cycle parts use pipekit, which is a base somax dependency (somax’s core still never imports it — conformance is structural). somax’s core never imports pipekit — models satisfy the protocols structurally.

1. A model as a sum of term-kernels

The linear shallow water right-hand side decomposes into four distinct physics kernels — a gravity-wave (pressure) term, Coriolis, lateral diffusion, and bottom drag — that compose with +:

rhs=gravity_wave⏟fast, stiff+coriolis+ν⋅diffusion⏟stiff+(−κ)⋅drag\texttt{rhs} = \underbrace{\texttt{gravity\_wave}}_{\text{fast, stiff}} + \texttt{coriolis} + \nu \cdot \underbrace{\texttt{diffusion}}_{\text{stiff}} + (-\kappa) \cdot \texttt{drag}

The differentiable parameters (ν\nu, κ\kappa) enter as Scaled coefficients (JAX leaves), so a loss can be differentiated straight through them.

LinearSWMGravityWave -> LinearSWMGravityWave(
  diff=Difference2D(
    grid=CartesianGrid2D(
      Nx=66, Ny=66, Lx=2000000.0, Ly=2000000.0, dx=31250.0, dy=31250.0
    )
  ),
  g=9.81,
  H0=100.0
)
LinearSWMCoriolis -> LinearSWMCoriolis(
  coriolis=Coriolis2D(
    grid=CartesianGrid2D(
      Nx=66, Ny=66, Lx=2000000.0, Ly=2000000.0, dx=31250.0, dy=31250.0
    ),
    mask=None,
    interp=Interpolation2D(
      grid=CartesianGrid2D(
        Nx=66, Ny=66, Lx=2000000.0, Ly=2000000.0, dx=31250.0, dy=31250.0
      )
    )
  ),
  f_field=weak_f32[66,66]
)
Scaled -> Scaled(
  term=LinearSWMDiffusion(
    diff=Difference2D(
      grid=CartesianGrid2D(
        Nx=66, Ny=66, Lx=2000000.0, Ly=2000000.0, dx=31250.0, dy=31250.0
      )
    )
  ),
  coeff=weak_f32[]
)
Scaled -> Scaled(term=LinearSWMDrag(), coeff=weak_f32[])

viscosity nu read back from the tree: 200.0
bottom drag kappa read back from the tree: 1.0000000116860974e-07

2. IMEX — tag stiff terms implicit

Because each kernel carries an integration kind, a stiff term can be routed to the implicit stage of a splitting (IMEX) solver. With the default (all explicit) the whole RHS lowers to a single diffrax.ODETerm; with imex=True the diffusion term is tagged implicit and the RHS lowers to a diffrax.MultiTerm that an IMEX solver (e.g. KenCarp3) integrates with the stiff Laplacian handled implicitly.

explicit build_terms(): ODETerm
imex     build_terms(): MultiTerm

Integrating an IMEX model — use somax.solvers.imex_solver

An IMEX model needs an IMEX solver (KenCarp3, …) and an adaptive step-size controller — a fixed step with an implicit solver requires explicit tolerances. Just as important: the implicit stage must be solved matrix-free. diffrax.KenCarp3()'s default Newton root-finder materialises a dense N x N Jacobian (N = nx*ny), which is ~17 GB at 256 x 256 and OOMs (#55).

somax.solvers.imex_solver() returns a KenCarp3 whose implicit stage uses a Krylov (GMRES) Newton solve — O(N) memory, roughly flat runtime in resolution — and imex_stepsize_controller() is the matching adaptive controller. This is the recommended way to run any imex=True model.

IMEX solve finite: True

3. Wrap a built model as a pipekit Operator

SomaxModelOp turns any built somax model into a pipekit.Operator: a one-step stage (op(state) -> next_state) that also satisfies the pipekit_cycle.ForwardModel protocol (step / dt / state_signature).

isinstance(op, ForwardModel): True
wrapped model: LinearSWM2DTermModel

4. Flat-config models round-trip through pipekit.serial

A built model is an eqx.Module (grids, operators, term trees) — not JSON primitives — so the general wrapper above is not serializable. Models whose construction is a flat primitive recipe expose a dedicated Operator (e.g. Burgers2DOp) whose config is all primitives, so dumps/loads round-trips the build recipe faithfully.

serialized config: {"class": "Burgers2DOp", "config": {"Lx": 2.0, "Ly": 2.0, "dt": 0.001, "imex": false, "method": "upwind1", "nu": 0.05, "nx": 32, "ny": 32}, "module": "somax._src.operators"}
round-trip matches: True

5. Drive the model with pipekit_cycle.Cycle

Cycle applies the step operator n_steps times, threading state and (with save_history=True) collecting the trajectory. We seed a Gaussian height bump and watch gravity waves radiate outward under rotation — a geostrophic-adjustment problem. The gravity-wave term from step 1 is exactly what drives this.

steps recorded in history: 6
<Figure size 1600x360 with 5 Axes>

6. The scenario x model registry, end to end

SomaxModelOp.from_registry builds any compatible scenario x model pair through the dispatcher and hands back (op, state0) ready for Cycle. This is the full “what we simulate (scenario) x how we simulate it (model)” composition, wrapped as a pipekit Operator.

built: LinearShallowWater2D
forward-integrated 10 steps; finite: True
op | op == two steps: True

Recap

  • Terms make a model’s RHS a transparent sum of physics kernels, with IMEX tags (gravity_wave + coriolis + nu*diffusion - kappa*drag).

  • Operators (SomaxModelOp / Burgers2DOp) expose a model as a pipekit stage + ForwardModel; flat-recipe models also serial round-trip.

  • Cycles drive the stepping loop and collect trajectories.

The same three seams power the somax-sim runner (which drives its chunked integration with Cycle) and let somax models plug into data-assimilation libraries (vardax, filterax) by satisfying ForwardModel structurally.