Discretization¶
Discrete-time inference methods need transitions between observation times, so a continuous-time model has to be discretized before they can use it. There are two ways to do this. Both take the same configurations and use the same automatic routing.
- Effect-handler form: wrap the model call in a
Discretizercontext. Your model code stays in continuous time and unchanged, so you can switch the discretization or the inference method by changing only the surrounding contexts. Use this with dynestyx's inference and simulation handlers, such asFilterorLatentPathBuilder. - Direct form: call
discretize_dynamicsordiscretize_state_evolutionand get the discretized model or state evolution back as an object. Use this when you need the transition distributions themselves, for example to sample or score transitions in your own inference code.
Effect-handler form¶
Place Discretizer inside the inference or simulation context, so the outer
handler receives the discretized dynamics:
import dynestyx as dsx
from dynestyx.discretizers import (
Discretizer,
MeanTrajectoryLinearizationConfig,
)
from dynestyx.inference.filters import EnKFConfig, Filter
with Filter(EnKFConfig(n_particles=100)):
with Discretizer(MeanTrajectoryLinearizationConfig()):
result = model(obs_times=obs_times, obs_values=obs_values)
The configuration, here MeanTrajectoryLinearizationConfig, selects the
discretization method. See
Discretizer configurations to
compare them.
Direct form¶
discretize_dynamics returns a complete discrete-time model:
import dynestyx as dsx
from dynestyx.discretizers import EulerMaruyamaConfig
discrete_dynamics = dsx.discretize_dynamics(
continuous_dynamics,
EulerMaruyamaConfig(),
)
The returned model keeps the initial condition, observation model, control
metadata and initial time; only the state evolution is replaced by the
discretized transition. To discretize a state evolution without building a
DynamicalModel, use discretize_state_evolution.
Sampling and scoring¶
The returned model's distributions can be sampled and scored directly:
initial_state = discrete_dynamics.initial_condition.sample(initial_key)
discrete_dynamics.initial_condition.log_prob(initial_state)
transition = discrete_dynamics.state_evolution(
x=previous_state, u=control, t_now=t_now, t_next=t_next
)
state = transition.sample(key)
transition.log_prob(state)
log_prob is available when the configuration provides a transition density.
EulerMaruyamaConfig provides the density of its Gaussian approximation;
DiffraxSampleConfig only samples.
For dense all-pairs scores, map over the transition distributions. For missing observations, use masked_observation_log_prob.
Automatic routing¶
With no configuration, the method is chosen as follows:
- a deterministic ODE is integrated with
ODEFlowConfig(), producing a Delta transition at the numerical flow endpoint; - an
AffineDriftwith constant diffusion and no potential is discretized exactly; and - other SDE models use Euler--Maruyama discretization by default.
Pass ODEFlowConfig(simulator_config=ODESimulatorConfig(...), jitter_scale=...) to customize ODE integration; all Diffrax settings are taken from the nested ODESimulatorConfig.
Local affine-Gaussian parameters¶
Use linearize_drift to construct
a local affine drift, then discretize it with ExactAffineConfig. This
composition requires structurally constant additive diffusion:
import dynestyx as dsx
from dynestyx.discretizers import ExactAffineConfig
cte = continuous_dynamics.state_evolution
local = dsx.StochasticContinuousTimeStateEvolution(
drift=dsx.linearize_drift(
cte.total_drift, x=reference_state, u=control, t=t_now
),
diffusion=cte.diffusion,
)
transition = dsx.discretize_state_evolution(
local, ExactAffineConfig(covariance_jitter=1e-8)
)
params = transition.params_at(t_now, t_next)
A = params.A
bias = params.bias
Q = params.cov
The resulting LinearGaussianParams give the approximation used by
LocalLinearizationConfig with matching covariance jitter. The drift is
linearized at reference_state, with time and control frozen at the left
endpoint. The fixed control can affect A, bias, and cov. The parameters
are conditional on that control, so B=None.
Use vmap to evaluate parameters along a reference trajectory. Trajectory
selection, observation linearization, and inference are handled separately.
For time-invariant affine drift with constant additive diffusion and no
potential term, discretization with ExactAffineConfig(covariance_jitter=0) produces a
LinearGaussianStateEvolution whose params_at(t_now, t_next) method returns
exact interval parameters up to floating-point error, assuming control is held
fixed over each interval. No linearization state is needed.
Configuration-driven discretization of continuous-time models.
discretize_dynamics(dynamics: DynamicalModel, discretizer_config: BaseDiscretizerConfig | None = None) -> DynamicalModel
¶
Build a discrete-time model from continuous-time dynamics.
Preserves the initial condition, observation model, control metadata,
observation-control alignment, and initial time. The state evolution uses
the transition selected by discretizer_config.
When no config is provided, deterministic ODEs use their numerical flow, affine SDEs with constant diffusion and no potential use an exact Gaussian transition, and other SDEs use Euler--Maruyama.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dynamics
|
DynamicalModel
|
Continuous-time model to discretize. |
required |
discretizer_config
|
BaseDiscretizerConfig | None
|
Explicit discretization config, or |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
DynamicalModel |
DynamicalModel
|
A discrete-time model with the selected interval transition. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |