Measurement Models¶
The measurement module provides classes for defining ODE constraints and observation models used in probabilistic ODE solvers. The design separates concerns cleanly:
- ODE classes define the dynamical system constraint
- Constraint dataclasses define additional constraints (conservation laws, measurements)
- Black-box models allow arbitrary user-defined measurement functions
- Transformed models apply nonlinear state transformations with proper Jacobians
Class Hierarchy¶
ODE Information Classes¶
The module provides four ODE information classes, organized by ODE order and whether hidden states are present:
| Class | ODE Order | Hidden States | Vector Field Signature |
|---|---|---|---|
ODEInformation |
1st | No | vf(x, *, t) -> dx/dt |
ODEInformationWithHidden |
1st | Yes | vf(x, u, *, t) -> dx/dt |
SecondOrderODEInformation |
2nd | No | vf(x, v, *, t) -> d^2x/dt^2 |
SecondOrderODEInformationWithHidden |
2nd | Yes | vf(x, v, u, *, t) -> d^2x/dt^2 |
Flexible Measurement Classes¶
| Class | Description |
|---|---|
BlackBoxMeasurement |
User-defined g(state, t) with autodiff Jacobian |
TransformedMeasurement |
Wraps any model with nonlinear state transformation |
This separation ensures:
- No runtime conditionals - Each class has a fixed code path, optimal for JAX JIT
- Explicit signatures - The vector field type is clear from the class choice
- Single responsibility - Each class handles exactly one case
Composable Constraints¶
Additional constraints are added via frozen dataclasses:
Conservation: Time-invariant linear constraintsA @ x = p, passed via the ODE class'sconstraints=argument.Measurement: Time-varying observationsA @ x = z[t]at specified times. These are not measure constraints; build an observation model withprepare_observations([Measurement(...)], E0, ts)and pass it asobs_model=to the solver. Note:prepare_observationsrequires all stacked measurements to share the same observation times; a step where only some measurements fire raisesNotImplementedError(per-dimension masking is not yet supported).
Usage Examples¶
First-Order ODE¶
from ode_filters.priors import IWP
from ode_filters.measurement import ODEInformation
prior = IWP(q=2, d=1)
def vf(x, *, t):
return -x # exponential decay
model = ODEInformation(vf, prior.E0, prior.E1)
Second-Order ODE (e.g., Harmonic Oscillator)¶
from ode_filters.priors import IWP
from ode_filters.measurement import SecondOrderODEInformation
prior = IWP(q=3, d=1)
omega = 2.0
def vf(x, v, *, t):
return -(omega**2) * x # harmonic oscillator
model = SecondOrderODEInformation(vf, prior.E0, prior.E1, prior.E2)
Joint State-Parameter Estimation¶
For estimating unknown parameters alongside the state, use JointPrior with
the hidden state classes:
from ode_filters import gaussian_filter, prepare_observations
from ode_filters.priors import IWP, JointPrior
from ode_filters.measurement import SecondOrderODEInformationWithHidden, Measurement
# Prior for state x and unknown damping parameter u
prior_x = IWP(q=2, d=1)
prior_u = IWP(q=2, d=1)
prior_joint = JointPrior(prior_x, prior_u)
# Damped oscillator with unknown damping
def vf(x, v, u, *, t):
omega = 1.0
return -(omega**2) * x - u * v
model = SecondOrderODEInformationWithHidden(
vf,
E0=prior_joint.E0_x, # extracts x
E1=prior_joint.E1, # extracts dx/dt
E2=prior_joint.E2, # extracts d^2x/dt^2
E0_hidden=prior_joint.E0_hidden, # extracts u
)
# Data observations go through obs_model, not the measure constraints.
# Observe position x only; align observation times to the filter grid `ts`.
A_obs = prior_joint.E0_x[:1, :] # observe x only
meas = Measurement(A=A_obs, z=observations, z_t=obs_times, noise=0.01)
obs_model = prepare_observations([meas], prior_joint.E0_x, ts)
result = gaussian_filter(
mu_0, P_0_sqr, prior_joint, model, tspan, N, obs_model=obs_model
)
Adding Conservation Laws¶
from ode_filters.measurement import ODEInformation, Conservation
import jax.numpy as np
# Energy conservation: x1 + x2 = 1
cons = Conservation(A=np.array([[1.0, 1.0]]), p=np.array([1.0]))
model = ODEInformation(vf, E0, E1, constraints=[cons])
Black-Box Measurement Models¶
For cases where the standard ODE structure doesn't fit, use BlackBoxMeasurement to define an arbitrary differentiable measurement function. The Jacobian is computed automatically via JAX autodiff.
Example:
from ode_filters.measurement import BlackBoxMeasurement
import jax.numpy as np
# Custom nonlinear observation: observe squared position and velocity
def custom_g(state, *, t):
x, v = state[0], state[1]
return np.array([x**2, v])
model = BlackBoxMeasurement(
g_func=custom_g,
state_dim=6, # full state dimension (e.g., q=2, d=2 -> D=6)
obs_dim=2, # observation dimension
noise=0.01 # measurement noise variance
)
# Use like any other measurement model
H, c = model.linearize(state, t=0.0)
R = model.get_noise(t=0.0)
Transformed Measurement Models¶
TransformedMeasurement wraps any existing measurement model with a nonlinear state transformation sigma(state). The Jacobian of the mean is composed correctly via the chain rule: J_total = J_g(sigma(state)) @ J_sigma(state). Note, however, that only the mean/linearization is transformed: the measurement-noise covariance R is delegated to and taken unchanged from the base model (see get_noise), and is not propagated through sigma. R must therefore already be expressed in the base model's post-transform coordinates.
Use cases:
- Nonlinear coordinate transformations (e.g., polar to Cartesian)
- Applying constraints like softmax normalization
- Feature extraction before measurement
Example with autodiff Jacobian:
from ode_filters.measurement import ODEInformation, TransformedMeasurement
from ode_filters.priors import IWP
import jax
import jax.numpy as np
# Base ODE model
def vf(x, *, t):
return -x
prior = IWP(q=2, d=2)
base_model = ODEInformation(vf, prior.E0, prior.E1)
# Apply softmax to ensure state components sum to 1
def softmax_transform(state):
x = state[:2] # extract position components
x_normalized = jax.nn.softmax(x)
return state.at[:2].set(x_normalized)
model = TransformedMeasurement(base_model, softmax_transform)
# Jacobian includes chain rule automatically
H, c = model.linearize(state, t=0.0)
Example with explicit Jacobian:
For performance-critical applications, you can provide a custom Jacobian:
def sigma_jacobian(state):
# Custom Jacobian implementation
return jax.jacfwd(softmax_transform)(state)
model = TransformedMeasurement(
base_model,
softmax_transform,
use_autodiff_jacobian=False,
sigma_jacobian=sigma_jacobian
)
API Reference¶
Measurement model utilities for ODE filtering.
Modules:
| Name | Description |
|---|---|
measurement_models |
|
Classes:
| Name | Description |
|---|---|
BlackBoxMeasurement |
Black-box measurement model with autodiff Jacobian computation. |
Conservation |
Conservation constraint: A @ x = p (always active). |
Measurement |
Time-varying linear measurement: A @ x = z[t] (active only at specified times). |
ODEInformation |
First-order ODE measurement model: dx/dt = f(x, t). |
ODEInformationWithHidden |
First-order ODE with hidden states: dx/dt = f(x, u, t). |
ObsModel |
Pre-computed observation data for sequential filtering. |
SecondOrderODEInformation |
Second-order ODE measurement model: d^2x/dt^2 = f(x, v, t). |
SecondOrderODEInformationWithHidden |
Second-order ODE with hidden states: d^2x/dt^2 = f(x, v, u, t). |
TransformedMeasurement |
Wrapper that applies a nonlinear state transformation before measurement. |
Functions:
| Name | Description |
|---|---|
ODEconservation |
Create first-order ODE model with conservation constraint. |
SecondOrderODEconservation |
Create second-order ODE model with conservation constraint. |
prepare_observations |
Build an :class: |
Classes¶
BlackBoxMeasurement
¶
BlackBoxMeasurement(
g_func: Callable[..., Array],
state_dim: int,
obs_dim: int,
noise: float | ArrayLike = 0.0,
)
Black-box measurement model with autodiff Jacobian computation.
Allows users to define an arbitrary measurement function g(state, *, t) and automatically computes the Jacobian via JAX autodiff.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g_func
|
Callable[..., Array]
|
Measurement function g(state, *, t) -> observation. Must be a differentiable function compatible with JAX. |
required |
state_dim
|
int
|
Dimension of the state vector. |
required |
obs_dim
|
int
|
Dimension of the observation vector. |
required |
noise
|
float | ArrayLike
|
Measurement noise (scalar, 1D diagonal, or 2D covariance matrix). Default is 0.0 (no noise). |
0.0
|
Example
def custom_g(state, , t): ... # Nonlinear observation: squared position + velocity ... return jnp.array([state[0]*2, state[1]]) measure = BlackBoxMeasurement(custom_g, state_dim=4, obs_dim=2)
Methods:
| Name | Description |
|---|---|
g |
Evaluate the measurement function. |
get_noise |
Return the square root of the measurement noise covariance. |
jacobian_g |
Compute Jacobian of the measurement function via autodiff. |
linearize |
Linearize the measurement model around the given state. |
Attributes:
| Name | Type | Description |
|---|---|---|
R |
Array
|
Measurement noise covariance matrix. |
Attributes¶
Functions¶
g
¶
Evaluate the measurement function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Array
|
State vector of length state_dim. |
required |
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Observation vector of length obs_dim. |
get_noise
¶
Return the square root of the measurement noise covariance.
Returns an upper-triangular matrix L such that L.T @ L ≈ R.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
ArrayLike
|
Current time (unused, included for API consistency). |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Upper-triangular square root of the noise covariance matrix. |
jacobian_g
¶
Compute Jacobian of the measurement function via autodiff.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Array
|
State vector of length state_dim. |
required |
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Jacobian matrix of shape (obs_dim, state_dim). |
linearize
¶
Linearize the measurement model around the given state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Array
|
State vector to linearize around. |
required |
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Tuple of (H_t, c_t) where: |
Array
|
|
tuple[Array, Array]
|
|
Conservation
dataclass
¶
Conservation constraint: A @ x = p (always active).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
Array
|
Constraint matrix (shape [k, d] or [k, state_dim] if full_state=True). |
required |
p
|
Array
|
Target values (shape [k]). |
required |
full_state
|
bool
|
If True, A operates on the full state X instead of x = E0 @ X. |
False
|
Methods:
| Name | Description |
|---|---|
jacobian |
Return Jacobian (constant): A. |
residual |
Compute residual: A @ x - p. |
Attributes:
| Name | Type | Description |
|---|---|---|
dim |
int
|
Dimension of constraint output. |
Measurement
dataclass
¶
Measurement(
A: Array,
z: Array,
z_t: Array,
noise: float | Array = DEFAULT_MEASUREMENT_NOISE,
full_state: bool = False,
)
Time-varying linear measurement: A @ x = z[t] (active only at specified times).
This constraint is only active when the filter's current time matches one of the measurement times in z_t. Time matching uses tolerance-based comparison (not exact equality) to handle floating-point discrepancies.
Important
When creating time grids, use the same linspace implementation (preferably jax.numpy.linspace) for both measurement times (z_t) and filter time steps. NumPy and JAX linspace can produce slightly different values (~1e-8 differences) which may cause measurements to be missed with exact comparison.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
Array
|
Measurement matrix (shape [k, d] or [k, state_dim] if full_state=True). |
required |
z
|
Array
|
Measurement values (shape [n, k]). |
required |
z_t
|
Array
|
Measurement times (shape [n]). Should use jax.numpy.linspace for consistency with the filter's internal time grid. |
required |
noise
|
float | Array
|
Measurement noise variance (scalar or [k] or [k, k]). |
DEFAULT_MEASUREMENT_NOISE
|
full_state
|
bool
|
If True, A operates on the full state X instead of x = E0 @ X. |
False
|
Methods:
| Name | Description |
|---|---|
find_index |
Find measurement index for time t, or None if not found. |
get_noise_matrix |
Get noise covariance matrix. |
jacobian |
Return Jacobian (constant): A, or None if no measurement at t. |
residual |
Compute residual: A @ x - z[t], or None if no measurement at t. |
Attributes:
| Name | Type | Description |
|---|---|---|
dim |
int
|
Dimension of measurement output. |
Attributes¶
Functions¶
find_index
¶
find_index(
t: float,
rtol: float = MEASUREMENT_TIME_RTOL,
atol: float = MEASUREMENT_TIME_ATOL,
) -> int | None
Find measurement index for time t, or None if not found.
Uses binary search with tolerance for robust floating-point comparison. Times are assumed to be sorted.
Note: Default tolerances are set to handle discrepancies between NumPy and JAX linspace implementations, which can differ by ~1e-8 for typical time grids.
jacobian
¶
Return Jacobian (constant): A, or None if no measurement at t.
residual
¶
Compute residual: A @ x - z[t], or None if no measurement at t.
ODEInformation
¶
ODEInformation(
vf: Callable[..., Array],
E0: ArrayLike,
E1: ArrayLike,
constraints: list[Conservation] | None = None,
)
Bases: BaseODEInformation
First-order ODE measurement model: dx/dt = f(x, t).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vf
|
Callable[..., Array]
|
Vector field function vf(x, *, t) -> dx/dt. |
required |
E0
|
ArrayLike
|
State extraction matrix (shape [d, D]). |
required |
E1
|
ArrayLike
|
First derivative extraction matrix (shape [d, D]). |
required |
constraints
|
list[Conservation] | None
|
Optional list of Conservation constraints. (Data observations
are not measure constraints -- pass them as an |
None
|
ODEInformationWithHidden
¶
ODEInformationWithHidden(
vf: Callable[[Array, Array], Array],
E0: ArrayLike,
E1: ArrayLike,
E0_hidden: ArrayLike,
constraints: list[Conservation] | None = None,
)
Bases: BaseODEInformation
First-order ODE with hidden states: dx/dt = f(x, u, t).
For joint state-parameter estimation where u is a hidden parameter that appears in the dynamics but evolves according to its own prior.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vf
|
Callable[[Array, Array], Array]
|
Vector field function vf(x, u, *, t) -> dx/dt. |
required |
E0
|
ArrayLike
|
State extraction matrix for x (shape [d_x, D]). |
required |
E1
|
ArrayLike
|
First derivative extraction matrix (shape [d_x, D]). |
required |
E0_hidden
|
ArrayLike
|
Hidden state extraction matrix for u (shape [d_u, D]). |
required |
constraints
|
list[Conservation] | None
|
Optional list of Conservation constraints. (Data observations
are not measure constraints -- pass them as an |
None
|
ObsModel
¶
Bases: NamedTuple
Pre-computed observation data for sequential filtering.
Describes linear, time-invariant observations where the observation
matrix H and noise covariance are constant across time and only
the measurement values vary per step. Built by
:func:prepare_observations from a list of :class:Measurement
objects and a time grid.
Attributes:
| Name | Type | Description |
|---|---|---|
H |
Array
|
Observation Jacobian (constant), shape |
R_sqr |
Array
|
Square-root noise covariance for active observations
(constant), shape |
c_seq |
Array
|
Observation offsets per step, shape |
mask |
Array
|
Boolean mask for active dimensions, shape |
SecondOrderODEInformation
¶
SecondOrderODEInformation(
vf: Callable[[Array, Array], Array],
E0: ArrayLike,
E1: ArrayLike,
E2: ArrayLike,
constraints: list[Conservation] | None = None,
)
Bases: BaseODEInformation
Second-order ODE measurement model: d^2x/dt^2 = f(x, v, t).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vf
|
Callable[[Array, Array], Array]
|
Vector field function vf(x, v, *, t) -> d^2x/dt^2. |
required |
E0
|
ArrayLike
|
State extraction matrix (shape [d, D]). |
required |
E1
|
ArrayLike
|
First derivative extraction matrix (shape [d, D]). |
required |
E2
|
ArrayLike
|
Second derivative extraction matrix (shape [d, D]). |
required |
constraints
|
list[Conservation] | None
|
Optional list of Conservation constraints. (Data observations
are not measure constraints -- pass them as an |
None
|
SecondOrderODEInformationWithHidden
¶
SecondOrderODEInformationWithHidden(
vf: Callable[[Array, Array, Array], Array],
E0: ArrayLike,
E1: ArrayLike,
E2: ArrayLike,
E0_hidden: ArrayLike,
constraints: list[Conservation] | None = None,
)
Bases: BaseODEInformation
Second-order ODE with hidden states: d^2x/dt^2 = f(x, v, u, t).
For joint state-parameter estimation where u is a hidden parameter that appears in the dynamics but evolves according to its own prior.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vf
|
Callable[[Array, Array, Array], Array]
|
Vector field function vf(x, v, u, *, t) -> d^2x/dt^2. |
required |
E0
|
ArrayLike
|
State extraction matrix for x (shape [d_x, D]). |
required |
E1
|
ArrayLike
|
First derivative extraction matrix (shape [d_x, D]). |
required |
E2
|
ArrayLike
|
Second derivative extraction matrix (shape [d_x, D]). |
required |
E0_hidden
|
ArrayLike
|
Hidden state extraction matrix for u (shape [d_u, D]). |
required |
constraints
|
list[Conservation] | None
|
Optional list of Conservation constraints. (Data observations
are not measure constraints -- pass them as an |
None
|
TransformedMeasurement
¶
TransformedMeasurement(
base_model: BaseODEInformation | BlackBoxMeasurement,
sigma: Callable[[Array], Array],
use_autodiff_jacobian: bool = True,
sigma_jacobian: Callable[[Array], Array] | None = None,
)
Wrapper that applies a nonlinear state transformation before measurement.
Given a base measurement model and a transformation sigma(state), this class computes g(sigma(state)) with proper chain-rule Jacobian: J_total = J_g(sigma(state)) @ J_sigma(state)
This is useful for: - Nonlinear coordinate transformations - Feature extraction before measurement - Applying learned transformations to the state
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_model
|
BaseODEInformation | BlackBoxMeasurement
|
Base measurement model with g, jacobian_g, get_noise, linearize. |
required |
sigma
|
Callable[[Array], Array]
|
State transformation function sigma(state) -> transformed_state. Must be differentiable and compatible with JAX. |
required |
use_autodiff_jacobian
|
bool
|
If True (default), compute J_sigma via autodiff. If False, expect sigma_jacobian to be provided. |
True
|
sigma_jacobian
|
Callable[[Array], Array] | None
|
Optional explicit Jacobian function for sigma. If provided and use_autodiff_jacobian=False, this will be used instead of autodiff. |
None
|
Example
Apply softmax transformation to state before ODE measurement¶
def softmax_transform(state): ... # Transform first 3 components via softmax ... x = state[:3] ... x_soft = jax.nn.softmax(x) ... return state.at[:3].set(x_soft) base = ODEInformation(vf, E0, E1) transformed = TransformedMeasurement(base, softmax_transform)
Methods:
| Name | Description |
|---|---|
g |
Evaluate the measurement function on transformed state. |
get_noise |
Return the square root of the noise covariance (delegated to base model). |
jacobian_g |
Compute Jacobian with chain rule: J_g(sigma(state)) @ J_sigma(state). |
linearize |
Linearize the transformed measurement model. |
Attributes:
| Name | Type | Description |
|---|---|---|
R |
Array
|
Measurement noise covariance matrix (delegated to base model). |
Attributes¶
Functions¶
g
¶
Evaluate the measurement function on transformed state.
Computes g(sigma(state), t).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Array
|
Original state vector. |
required |
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Observation vector. |
get_noise
¶
Return the square root of the noise covariance (delegated to base model).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Upper-triangular square root of the noise covariance matrix. |
jacobian_g
¶
Compute Jacobian with chain rule: J_g(sigma(state)) @ J_sigma(state).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Array
|
Original state vector. |
required |
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Jacobian matrix of the composed measurement model. |
linearize
¶
Linearize the transformed measurement model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Array
|
Original state vector to linearize around. |
required |
t
|
ArrayLike
|
Current time. |
required |
Returns:
| Type | Description |
|---|---|
Array
|
Tuple of (H_t, c_t) where: |
Array
|
|
tuple[Array, Array]
|
|
Functions¶
ODEconservation
¶
ODEconservation(
vf: Callable,
E0: ArrayLike,
E1: ArrayLike,
A: Array,
p: Array,
) -> ODEInformation
Create first-order ODE model with conservation constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vf
|
Callable
|
Vector field function vf(x, *, t) -> dx/dt. |
required |
E0
|
ArrayLike
|
State extraction matrix. |
required |
E1
|
ArrayLike
|
Derivative extraction matrix. |
required |
A
|
Array
|
Conservation constraint matrix (shape [k, d]). |
required |
p
|
Array
|
Conservation target values (shape [k]). |
required |
Returns:
| Type | Description |
|---|---|
ODEInformation
|
ODEInformation with conservation constraint. |
SecondOrderODEconservation
¶
SecondOrderODEconservation(
vf: Callable,
E0: ArrayLike,
E1: ArrayLike,
E2: ArrayLike,
A: Array,
p: Array,
) -> SecondOrderODEInformation
Create second-order ODE model with conservation constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vf
|
Callable
|
Vector field function vf(x, v, *, t) -> d^2x/dt^2. |
required |
E0
|
ArrayLike
|
State extraction matrix. |
required |
E1
|
ArrayLike
|
First derivative extraction matrix. |
required |
E2
|
ArrayLike
|
Second derivative extraction matrix. |
required |
A
|
Array
|
Conservation constraint matrix (shape [k, d]). |
required |
p
|
Array
|
Conservation target values (shape [k]). |
required |
Returns:
| Type | Description |
|---|---|
SecondOrderODEInformation
|
SecondOrderODEInformation with conservation constraint. |
prepare_observations
¶
prepare_observations(
observations: list[Measurement],
E0: ArrayLike,
ts: ArrayLike,
) -> ObsModel | None
Build an :class:ObsModel from :class:Measurement objects.
Assumes linear, time-invariant observation matrices (A and noise
are constant across time; only the measurement values z vary).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
observations
|
list[Measurement]
|
List of Measurement constraints. |
required |
E0
|
ArrayLike
|
State extraction matrix, shape |
required |
ts
|
ArrayLike
|
Time grid of shape |
required |
Returns:
| Name | Type | Description |
|---|---|---|
An |
ObsModel | None
|
class: |