pipekit-cycle¶
Time-stepping, data assimilation, observation operators. Built on
pipekit.state (the StatefulOperator + CarryState primitives in
pipekit core). Master plan reference: Report 10.
Cycle wrappers¶
pipekit_cycle.cycle
¶
Generic time-stepping and iteration wrappers.
These are the cycle abstractions — Cycle, EnsembleCycle,
WindowedCycle, Recurrence — that lift a per-step
StatefulOperator (or a ForwardModel) into a multi-step operator.
Each is itself a StatefulOperator, so cycles compose into longer
cycles and into Sequential pipelines.
Design choice: each cycle returns (carrier, state) to preserve
the StatefulOperator contract. When save_history=True the
intermediate carriers are accumulated on self.history — a plain
list attribute available after the call. Treat the attribute as
write-once-per-call: re-running the operator overwrites it.
See master plan Report 10, section 2.2.
Cycle
¶
Bases: StatefulOperator
Apply step_op for n_steps, threading state through each step.
Equivalent in shape to jax.lax.scan but carrier-agnostic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
step_op
|
Any
|
A |
required |
n_steps
|
int
|
Number of steps to take. Must be ≥ 0; |
required |
save_history
|
bool
|
When |
False
|
history_stride
|
int
|
Save every |
1
|
Returns from _apply(carrier, state): (final_carrier, final_state).
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Example::
cycle = Cycle(step_op=Advect(dt=3600.0), n_steps=24, save_history=True)
carrier, state = cycle(initial_carrier, initial_state)
# cycle.history is the per-step trajectory
Source code in packages/pipekit-cycle/src/pipekit_cycle/cycle.py
EnsembleCycle
¶
Bases: StatefulOperator
Run Cycle over a list of ensemble-member carriers.
The carrier is expected to be a sequence (list / tuple) of
n_members individual carriers; the same step_op is applied
to each member in turn, sharing the carry-state across members
(the typical EnKF arrangement, where the state holds covariances
or inflation factors that all members read).
Vectorisation across members is delegated to the user — for
array-shaped carriers, wrap step_op in a batched / vmapped
operator before passing it in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
step_op
|
Any
|
Per-member step. |
required |
n_steps
|
int
|
Steps per member. |
required |
n_members
|
int
|
Expected ensemble size; the input carrier is length-checked at call time. |
required |
Returns from _apply(members, state): (list_of_final_members, state).
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Source code in packages/pipekit-cycle/src/pipekit_cycle/cycle.py
Recurrence
¶
Bases: StatefulOperator
Apply step_op repeatedly until condition_op returns truthy.
For iterative inference: fixed-point methods, Levenberg-Marquardt,
EM, variational solvers. condition_op is an Operator that
inspects the post-step state and returns a bool-ish value; the
loop exits when it returns truthy or when max_iters is reached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
step_op
|
Any
|
Per-iteration step. |
required |
condition_op
|
Operator
|
|
required |
max_iters
|
int
|
Safety cap on iterations (default 1000). |
1000
|
Returns from _apply(carrier, state): (carrier, state)
after either convergence or hitting max_iters.
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Source code in packages/pipekit-cycle/src/pipekit_cycle/cycle.py
WindowedCycle
¶
Bases: StatefulOperator
Sliding-window time-stepping over a stream of initial carriers.
The input is a list of carriers (one per stream position). For
each window — windows start at positions 0, stride, 2*stride, …
up to len(stream) - window — the cycle seeds a carrier from
the first stream element of that window and threads it through
window applications of step_op. State threads across
windows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
step_op
|
Any
|
Per-step operator. |
required |
window
|
int
|
Number of |
required |
stride
|
int
|
Step between successive window starts (in stream units). |
1
|
Returns from _apply(stream, state):
(list_of_window_final_carriers, state).
Raises:
| Type | Description |
|---|---|
ValueError
|
From the constructor for invalid |
Source code in packages/pipekit-cycle/src/pipekit_cycle/cycle.py
Protocols¶
pipekit_cycle.protocols
¶
Protocols decomposing data assimilation into its standard form.
The core triple — ForwardModel, ObservationOperator, AnalysisStep
(predict, compare, update) — plus the reduced-order seams for
variational and reduced-rank methods: ReducedBasis (a reduced control
basis with a prior), TangentLinearModel (M' / M* of the dynamics),
ErrorSubspace (a propagating low-rank covariance factor), and
ReducedOrderModel (a Galerkin ROM — encode/decode + latent dynamics).
Ensemble-method seams: ObservationNoise (a noise model that can both
report its covariance and draw samples), CovarianceLocalizer
(distance-based covariance tapering), EnsembleInflator (post-analysis
ensemble inflation), and LatentMap (an encode/decode pair for
latent-space assimilation — the stateless subset of
ReducedOrderModel).
Iterative-inversion seams: IterativeProcess (ensemble Kalman
inversion / sampling — EKI, EKS, UKI) and StepScheduler (its
step-size policy).
Each algorithm
library (filterax, vardax, plumax, …) ships adapter classes that satisfy
these protocols without importing pipekit-cycle. The protocols are
runtime-checkable so isinstance(obj, ForwardModel) succeeds on any
structurally compatible class.
See master plan Report 10, section 2.3.
AnalysisStep
¶
Bases: Protocol
Combine forecast state with observations to produce analysis state.
Implementations: ensemble Kalman analyses (EnKF, ETKF, LETKF), variational solvers (3D/4D-Var), particle filters, smoothers. Algorithm libraries supply concrete classes.
Members
call(forecast, obs, *, obs_op, obs_err_cov): Return the analysis state given the forecast, the observations, the observation operator, and the observation-error covariance.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
CovarianceLocalizer
¶
Bases: Protocol
Taper spurious long-range covariances in ensemble methods.
Localisation suppresses sampling noise in low-rank ensemble
covariances by damping entries between distant locations
(Gaspari-Cohn, Gaussian taper, hard cutoff, …). Cycles thread the
localizer through to the AnalysisStep; supplying one is optional.
Members
call(covariance, coords): Return the localized covariance given per-element coordinates used to compute distances.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
EnsembleInflator
¶
Bases: Protocol
Inflate an analysis ensemble to counter variance underestimation.
Ensemble filters systematically underestimate spread; inflation (multiplicative, additive, relaxation-to-prior) corrects for it after each analysis. Relaxation methods blend in the forecast ensemble, hence the optional second argument.
Members
call(particles, forecast_particles=None, **kwargs): Return
the inflated ensemble. forecast_particles is the prior
ensemble for relaxation-to-prior schemes (RTPS / RTPP);
pure multiplicative or additive schemes ignore it.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
ErrorSubspace
¶
Bases: Protocol
A propagating low-rank error-covariance factor L (B ≈ L Lᵀ).
The reduced-rank representation behind SEEK / SEIK / RRSQRT and other
reduced-order Kalman methods: instead of a full covariance, the error is
carried as a small set of modes that are propagated through the
linearised dynamics each forecast and updated by the analysis. Thread it
alongside the model state (e.g. on DAState.extras) so a reduced-rank
AnalysisStep can read and refresh it.
Members
propagate(model, state, dt): Advance the modes by dt about the
linearisation point state — typically M' L via a
TangentLinearModel, or finite differences of a ForwardModel.
Returns the forecast error subspace.
modes: The low-rank factor (its columns are the error modes).
rank: Number of modes retained.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
ForwardModel
¶
Bases: Protocol
Advance a model state forward in time by dt.
Implementations: domain forward models (chemistry transport, ocean state, plume dispersion), neural emulators, hybrid physics + ML hybrids. Algorithm libraries provide adapters that satisfy this protocol structurally.
Members
step(state, dt): Return the state advanced by dt.
dt: Default integration step.
state_signature: Optional pipekit.Signature describing the
shape / dtype of the state carrier. None if the model
doesn't track named dimensions.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
IterativeProcess
¶
Bases: Protocol
An iterative ensemble inversion / sampling process (EKI, EKS, UKI).
The parameter-estimation counterpart of the sequential-DA triple:
instead of cycling through time, the ensemble iterates in
pseudo-time toward the posterior over parameters. pipekit owns the
loop (initialise, evaluate the forward model on each particle,
update, repeat per the StepScheduler); algorithm libraries supply
the process.
Members
init(particles, obs, noise_cov): Build the initial iteration state from the prior ensemble, the observations, and the observation-noise covariance. update(state, forward_evals, **kwargs): Apply one iteration given the forward-model evaluations of the current particles; return the updated state.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
LatentMap
¶
Bases: Protocol
An encode/decode pair between full state space and a latent space.
The stateless subset of ReducedOrderModel — no latent dynamics,
just the projection maps. This is the seam latent-space assimilation
methods need (encode the ensemble, assimilate in latent space,
decode the analysis) and what autoencoder priors naturally expose.
Implementations: POD/EOF projections, autoencoders, or an identity
map for testing.
Members
encode(state): Full state -> latent coordinates z.
decode(coords): Latent coordinates -> full state.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
ObservationNoise
¶
Bases: Protocol
An observation-error model that exposes covariance and sampling.
Richer than the opaque obs_err_cov operand of AnalysisStep:
deterministic analyses (ETKF, 3D-Var) need only the covariance,
while stochastic ones (perturbed-observation EnKF) and synthetic-data
generation also need draws from the error distribution. Bundling
both keeps the two views consistent by construction.
Members
covariance(): The error covariance — a matrix, a linear
operator, or any object the consuming AnalysisStep
understands.
sample(key, shape): Draw error samples of the given batch
shape. key is the RNG seed/key (e.g. a JAX PRNG key);
implementations on a global RNG may ignore it.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
ObservationOperator
¶
Bases: Protocol
Map model state → predicted observations.
The H operator in classical data-assimilation notation:
H(x) produces "what would the observations look like if the
state were x?". The innovation in DA is then obs - H(forecast).
Members
call(state): Return predicted observations for state.
linearize(state): Optional tangent-linear operator at
state (returns a callable / matrix). Implementations
that don't expose a linearisation may raise
NotImplementedError.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
ReducedBasis
¶
Bases: Protocol
Map a reduced control vector to a state increment (with a prior).
Variational inverse problems (4D-Var on SSH, …) parameterise the
control vector X not in full state space but in a reduced basis
Φ (Gaussian RBF, wavelet, MIOST), each coefficient carrying a
diagonal prior Q. The basis maps coefficients to a state
increment, and its prior supplies the background term ½ Xᵀ Q⁻¹ X.
Concrete implementations live in algorithm libraries (or
pipekit_cycle.basis) and satisfy this protocol structurally.
Members
operg(t, X, state): Apply the basis — coefficients X to a
state increment Φ(t) X at time t.
prior_inv(X): Apply the diagonal prior inverse Q⁻¹ X (the
4D-Var background term operand).
nbasis: Length of the control vector X.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
ReducedOrderModel
¶
Bases: Protocol
A Galerkin / POD reduced-order model: encode/decode + latent dynamics.
A Galerkin ROM projects the full state onto a low-dimensional trial subspace and advances the reduced coordinates with the subspace-projected dynamics. Bundling the projection maps with the reduced step lets one object serve both uses:
- As a fast
ForwardModelon full states — wrapx -> decode(step(encode(x), dt)). - For data assimilation in reduced space —
encodethe state (and observations), assimilate the small latent vector, thendecode.
Concrete ROMs (POD/EOF, balanced truncation, autoencoder latents) live
in algorithm libraries and satisfy this structurally; the latent
dynamics may itself be a ForwardModel or a learned emulator.
Members
encode(state): Full state -> reduced coordinates z (e.g.
z = Psi^T (x - x_ref) for a (Petrov-)Galerkin trial/test
basis).
decode(coords): Reduced coordinates -> full state
(x = x_ref + Phi z).
step(coords, dt): Advance the reduced coordinates by dt with the
projected reduced dynamics.
latent_dim: Dimension of the reduced coordinate vector.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
StepScheduler
¶
Bases: Protocol
Step-size policy for an IterativeProcess.
Iterative ensemble methods (EKI, EKS, UKI) advance in pseudo-time; the scheduler chooses each increment — fixed, data-misfit controlled, or stability-bounded — from the current iteration state.
Members
get_dt(state): Return the scalar pseudo-time step for the next iteration given the process state (particles, forward evaluations, misfit, …).
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
SupportsAdjoint
¶
Bases: Protocol
A differentiable component that exposes its adjoint strategy.
Components whose forward pass is a rollout (a ForwardModel
backed by an ODE/SDE solve, a differentiable analysis loop, …)
advertise how gradients flow through them via a declarative spec
from pipekit_cycle.adjoints. Cycles never interpret the spec —
they pass components through untouched — so strategy selection is
pure configuration.
Members
adjoint: The current adjoint spec (an
pipekit_cycle.adjoints.AdjointSpec member, or any object
the component's execution layer understands).
with_adjoint(spec): Return a copy of the component configured
with spec; the original is unchanged.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
TangentLinearModel
¶
Bases: Protocol
Tangent-linear and adjoint of a ForwardModel's dynamics.
The M' / M* seam for methods that need the linearised model
explicitly — incremental 4D-Var inner loops and reduced-rank Kalman
error-subspace propagation — rather than relying on automatic
differentiation of ForwardModel.step. Implementations are supplied by
algorithm libraries (or wrap jax.jvp / jax.vjp of a
differentiable model).
Members
tangent(state, dx, dt): Apply the tangent-linear M'(state) to a
state perturbation dx, advancing it by dt.
adjoint(state, dz, dt): Apply the adjoint M*(state) to an
adjoint (dual) variable dz over dt.
Source code in packages/pipekit-cycle/src/pipekit_cycle/protocols.py
Adjoint strategy specs¶
A shared, declarative vocabulary for how gradients flow through the
three nested rollouts of a differentiable DA system — the dynamics
integration, the inner variational solve, and the assimilation cycle.
Each execution layer interprets a spec into its native mechanism
(diffrax adjoints via pipekit_jax.to_diffrax_adjoint, optimistix
adjoints in vardax, scan transforms in filterax):
| Spec | dynamics (diffrax) | inner solve (optimistix) | cycle (scan) |
|---|---|---|---|
DirectAdjoint |
store-everything | unrolled | plain scan — exact, O(T) memory |
RecursiveCheckpointAdjoint |
treeverse ✅ default | checkpointed | checkpointed scan — exact, O(√T) |
TruncatedAdjoint(k) |
— | k-step / one-step | truncated scan — biased, O(1) in T, chaos-tolerant |
ImplicitAdjoint |
steady-state only | IFT at the fixed point | — |
BacksolveAdjoint |
continuous adjoint ⚠ unstable for chaotic/stiff dynamics | — | — |
pipekit_cycle.adjoints
¶
Adjoint strategy specs — a shared vocabulary for gradient flow.
Differentiating a data-assimilation system involves up to three nested rollouts: the dynamics integration (one forecast window), the inner variational solve (4D-Var minimisation), and the outer assimilation cycle. Each layer realises reverse-mode differentiation through a different mechanism (diffrax adjoints, optimistix adjoints, scan transforms), but the strategy — how gradients should flow — is the same small set of concepts everywhere.
The classes here are that vocabulary: plain frozen dataclasses, pure
Python, serialisable like any other pipekit config knob. They carry no
behaviour; each execution layer interprets a spec into its native
mechanism (see pipekit_jax.to_diffrax_adjoint for the diffrax
mapping, and the algorithm libraries — filterax, vardax — for the
cycle- and solver-layer interpretations). Names mirror diffrax's
adjoint classes where the concepts coincide, so the vocabulary is
familiar; TruncatedAdjoint is the one concept diffrax does not
ship.
Strategy-by-layer summary:
| Spec | dynamics | inner solve | cycle |
|---|---|---|---|
DirectAdjoint |
store all | unrolled | plain scan |
RecursiveCheckpointAdjoint |
treeverse | checkpointed | ckpt. scan |
TruncatedAdjoint |
— | k-step | trunc. scan |
ImplicitAdjoint |
steady-state | IFT | — |
BacksolveAdjoint |
continuous | — | — |
AdjointSpec = DirectAdjoint | RecursiveCheckpointAdjoint | TruncatedAdjoint | ImplicitAdjoint | BacksolveAdjoint
module-attribute
¶
Union of the adjoint strategy specs.
BacksolveAdjoint
dataclass
¶
Continuous adjoint: solve the adjoint ODE backwards (O(1) memory).
Warning
Optimise-then-discretise — the result is not the exact gradient of the discrete forward solve, and reconstructing the forward trajectory by integrating backwards diverges for chaotic or stiff dynamics (the usual case in DA). Provided for completeness at the dynamics layer; never a default.
Source code in packages/pipekit-cycle/src/pipekit_cycle/adjoints.py
DirectAdjoint
dataclass
¶
Exact gradients by storing every step of the rollout.
Reverse-mode memory grows linearly with the rollout length; the right choice for short rollouts where recomputation overhead is not worth paying.
Source code in packages/pipekit-cycle/src/pipekit_cycle/adjoints.py
ImplicitAdjoint
dataclass
¶
Differentiate a converged fixed point via the implicit function theorem.
O(1) memory and independent of the solve path, at the price of one linear solve with the (Gauss-Newton) Hessian. Only sound when the inner solve actually converged — an unconverged iterate gets the gradient of a point it never reached. Applies to the inner-solve layer (and steady-state dynamics solves); meaningless for a non-fixed-point cycle.
Source code in packages/pipekit-cycle/src/pipekit_cycle/adjoints.py
RecursiveCheckpointAdjoint
dataclass
¶
Exact gradients with treeverse checkpointing (the recommended default).
Stores only checkpoints snapshots and recomputes segments
during the backward pass (Griewank 1992): memory is logarithmic to
square-root in the rollout length at the cost of extra forward
work. checkpoints=None lets the interpreter pick its default
schedule.
Attributes:
| Name | Type | Description |
|---|---|---|
checkpoints |
int | None
|
Number of checkpoints to store, or |
Source code in packages/pipekit-cycle/src/pipekit_cycle/adjoints.py
TruncatedAdjoint
dataclass
¶
Biased gradients: flow stops more than k steps back.
Steps before the trailing-k window run under stop_gradient,
giving O(1) memory in the rollout length. k=1 is the one-step
adjoint (Bolte et al. 2023) — exact when the rollout has
converged to a fixed point, and the ROAD-EnKF training regime when
applied at the cycle layer. For chaotic dynamics, truncation is
gradient regularisation: the exact adjoint norm grows
exponentially with the horizon, so the unbiased gradient is
useless at long horizons regardless of how it is computed.
Attributes:
| Name | Type | Description |
|---|---|---|
k |
int
|
Number of trailing steps that propagate gradients. Must be at least 1. |
Source code in packages/pipekit-cycle/src/pipekit_cycle/adjoints.py
Data-assimilation cycles¶
pipekit_cycle.da
¶
Data-assimilation cycles.
Three composable shapes:
DACycle— classic forecast → observe → analyse loop.EnsembleDACycle— same shape, but the carrier is an ensemble of members; the analysis step is an ensemble update (EnKF, ETKF, …).SmootherCycle— windowed 4D-Var-shaped variant; the analysis step receives the full window of observations at once.
Each DACycle returns (analysis_carrier, state). When
save_history=True the per-cycle (forecast, analysis) pairs
accumulate on self.history.
See master plan Report 10, section 2.4.
DACycle
¶
Bases: StatefulOperator
Classic forecast-analysis cycle.
Each call advances n_steps cycles. Within each cycle:
- Forecast:
forecast = forward_model.step(state_carrier, dt). - Observation:
obs = obs_source(step_index)ifobs_sourceis set, else skip the update. - Analyse:
carrier = analysis_step(forecast, obs, obs_op=obs_op, obs_err_cov=state.obs_err_cov).
The CarryState (typically DAState) carries
obs_err_cov and bookkeeping (t, cycle_count). At
least: any object with attributes t, cycle_count,
obs_err_cov works — duck-typed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forward_model
|
ForwardModel
|
Object satisfying |
required |
obs_op
|
ObservationOperator
|
Object satisfying |
required |
analysis_step
|
AnalysisStep
|
Object satisfying |
required |
obs_source
|
Operator | None
|
Optional |
None
|
n_steps
|
int
|
Number of cycles per call. |
1
|
save_history
|
bool
|
Append |
True
|
Returns from _apply(carrier, state):
(analysis_carrier, updated_state).
Raises:
| Type | Description |
|---|---|
TypeError
|
If a component doesn't satisfy its protocol
( |
ValueError
|
If |
Source code in packages/pipekit-cycle/src/pipekit_cycle/da.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | |
EnsembleDACycle
¶
Bases: StatefulOperator
Ensemble-based DA — EnKF, ETKF, LETKF, particle filters, …
Differences from DACycle:
- The carrier is a list (or tuple) of
n_membersensemble members. Each member is propagated through the forward model independently. - The analysis step receives the full ensemble (
forecast_members) plus observations; it returns the updated ensemble.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forward_model
|
ForwardModel
|
Same protocol as |
required |
obs_op
|
ObservationOperator
|
Same protocol as |
required |
analysis_step
|
AnalysisStep
|
An ensemble analysis (e.g. filterx.EnKFAnalysis).
Receives |
required |
obs_source
|
Operator | None
|
Optional observation source. |
None
|
n_steps
|
int
|
Number of cycles per call. |
1
|
n_members
|
int
|
Expected ensemble size (keyword-only; length-checked at call time). |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a component doesn't satisfy its protocol, or
|
ValueError
|
If |
Source code in packages/pipekit-cycle/src/pipekit_cycle/da.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | |
SmootherCycle
¶
Bases: StatefulOperator
4D-Var-shaped windowed smoother.
Each call processes window forward steps, accumulating
observations, then runs the analysis step on the whole window
at once. This matches the 4D-Var control-variable formulation
where the cost function is minimised over the full window
jointly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forward_model
|
ForwardModel
|
Same protocol as |
required |
obs_op
|
ObservationOperator
|
Same protocol as |
required |
analysis_step
|
AnalysisStep
|
A smoother / variational solver. Called as
|
required |
window
|
int
|
Number of forward steps per analysis window. |
required |
stride
|
int
|
Number of windows to take per call (default 1). |
1
|
obs_source
|
Operator | None
|
Optional observation source; invoked per step
inside the window. |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a component doesn't satisfy its protocol, or
|
ValueError
|
If |
Source code in packages/pipekit-cycle/src/pipekit_cycle/da.py
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
Observation operators¶
pipekit_cycle.obs
¶
Generic observation operators.
Each is a pipekit.Operator that also satisfies the
ObservationOperator Protocol so it can be passed wherever a
protocol-typed slot is expected (e.g. into DACycle.obs_op).
IdentityObs— direct measurement (H = I).LinearObs(H)— fixed linear map,H @ state. Carrier-agnostic via duck-typed@.CallableObs(fn)— arbitrary callable (e.g. radiative-transfer forward). Carriesforbid_in_yaml = True.CompositeObs(components)—h_k ∘ … ∘ h_1applied left-to-right.
See master plan Report 10, section 2.5.
CallableObs
¶
Bases: Operator
Arbitrary callable observation operator.
The escape hatch for nonlinear observation operators (radiative
transfer, instrument-response convolution, …). Carries
forbid_in_yaml = True because the callable doesn't round-trip
through JSON / YAML.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Any], Any]
|
Callable |
required |
linearize_fn
|
Callable[[Any], Any] | None
|
Optional callable |
None
|
name
|
str | None
|
Optional name for |
None
|
Source code in packages/pipekit-cycle/src/pipekit_cycle/obs.py
CompositeObs
¶
Bases: Operator
Compose multiple observation operators: H_k ∘ … ∘ H_1.
Apply components[0] first, then components[1], etc.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
components
|
tuple[Operator, ...]
|
One or more |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
if any component isn't an |
ValueError
|
if |
Source code in packages/pipekit-cycle/src/pipekit_cycle/obs.py
linearize(state)
¶
Compose component linearisations (chain rule).
Each component must expose a linearize. If a non-linearisable
component is present, raises NotImplementedError.
Source code in packages/pipekit-cycle/src/pipekit_cycle/obs.py
IdentityObs
¶
Bases: Operator
Direct measurement: H(x) = x.
The state is observable as-is — useful for synthetic-twin tests and as the default observation operator when one is needed but no real mapping applies.
Source code in packages/pipekit-cycle/src/pipekit_cycle/obs.py
LinearObs
¶
Bases: Operator
Linear observation map: H(x) = H @ x.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
H
|
Any
|
Any object supporting the |
required |
Carries forbid_in_yaml = True because the matrix H is a
runtime array that doesn't round-trip through JSON / YAML;
get_config() reports only its shape as a debug payload.
Source code in packages/pipekit-cycle/src/pipekit_cycle/obs.py
Forward-model adapters¶
pipekit_cycle.forward
¶
Generic ForwardModel adapters.
Each class is a pipekit.Operator and satisfies the
ForwardModel Protocol so it can be passed wherever a protocol-typed
slot accepts forward models (e.g. DACycle.forward_model).
CallableForward(fn, dt)— wrap any callable as aForwardModel.CompositeForward(components, dt)— apply each component in turn; total advance isdt(each component sees its own portion).NeuralForward(model_op, dt)— wrap a neural emulator operator (typically apipekit-array.ModelOp) as a forward model.
See master plan Report 10, section 2.6.
CallableForward
¶
Bases: Operator
Wrap a plain callable as a forward model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Any, float], Any]
|
Callable |
required |
dt
|
float
|
Default integration step in user units (seconds, hours, …). |
required |
state_signature
|
Any
|
Optional |
None
|
name
|
str | None
|
Optional name for |
None
|
Carries forbid_in_yaml = True — the callable doesn't round-trip.
Source code in packages/pipekit-cycle/src/pipekit_cycle/forward.py
CompositeForward
¶
Bases: Operator
Compose multiple forward models — F_k ∘ … ∘ F_1.
Each component advances state by its own internal dt. The
composite's dt is the sum (the total advance per step call).
The components run left-to-right per step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
components
|
tuple[Any, ...]
|
Tuple of objects satisfying |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
TypeError
|
if any component doesn't satisfy |
Source code in packages/pipekit-cycle/src/pipekit_cycle/forward.py
NeuralForward
¶
Bases: Operator
Wrap a neural-emulator operator (e.g. ModelOp) as a ForwardModel.
The wrapped model_op should be a pipekit.Operator that accepts
the state carrier and returns the advanced state. dt is
declared explicitly — neural emulators have a fixed training
cadence that callers must respect.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_op
|
Operator
|
The neural emulator |
required |
dt
|
float
|
The (fixed) integration step the emulator was trained for. |
required |
state_signature
|
Any
|
Optional |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
if |
Source code in packages/pipekit-cycle/src/pipekit_cycle/forward.py
Carry-state subclasses¶
pipekit_cycle.state
¶
Canonical CarryState subclasses for the cycle patterns.
Each cycle pattern brings its own state shape — pipekit-cycle ships
the three most common ones. Algorithm libraries are free to subclass
pipekit.CarryState directly with their own fields.
DAState— clock + cycle bookkeeping forDACycle/EnsembleDACycle.IterationState— residual + iteration counter forRecurrence.WindowState— sliding-window bookkeeping forWindowedCycle/SmootherCycle.
See master plan Report 10, section 6.2.
DAState
¶
Bases: CarryState
State threaded through a DA cycle.
Attributes:
| Name | Type | Description |
|---|---|---|
t |
Current model time (seconds, or any user-defined unit). |
|
cycle_count |
Number of completed cycles. |
|
obs_err_cov |
Observation-error covariance (handed to the analysis step). Carrier-agnostic — typically a numpy / JAX array or a covariance object from the algorithm library. |
|
extras |
Free-form dict for algorithm-specific state. |
Source code in packages/pipekit-cycle/src/pipekit_cycle/state.py
IterationState
¶
Bases: CarryState
State threaded through a Recurrence.
Attributes:
| Name | Type | Description |
|---|---|---|
iter_count |
Number of completed iterations. |
|
residual_norm |
Most recent residual norm (convergence check). |
|
extras |
Free-form dict for algorithm-specific bookkeeping (learning rate schedule, line-search trace, …). |
Source code in packages/pipekit-cycle/src/pipekit_cycle/state.py
WindowState
¶
Bases: CarryState
State threaded through a windowed / smoother cycle.
Attributes:
| Name | Type | Description |
|---|---|---|
window_index |
Zero-based index of the current window. |
|
last_analysis |
Most recent analysis carrier (the bridge between
windows). |
|
extras |
Free-form dict for algorithm-specific bookkeeping (windowed obs accumulator, adjoint trajectories, …). |