Simulator¶
Bases: BaseSimulator
Generate trajectories using the simulator appropriate for the model.
Simulator is the auto-routing simulator handler. Given prediction times
\(t_{0:T-1}\), it draws n_simulations independent trajectories from the
dynamical model:
For continuous-time models, the middle term denotes a numerical ODE or SDE
solve evaluated at predict_times. For discrete-time models, it denotes
successive draws from the transition distribution between adjacent
prediction times.
Use Simulator as a context manager around model execution when the model
contains dsx.sample(name, dynamics, predict_times=...). The active NumPyro
seed supplies randomness; the rollout itself is computed with pure JAX and
its realized arrays are then attached to the NumPyro trace. For
continuous-time models, pass an
ODESimulatorConfig
or
SDESimulatorConfig
as simulator_config to control how the differential equation is solved.
Use dsx.simulate instead when no NumPyro trace is
needed and an explicit rng_key is more convenient.
Examples:
Prior-predictive trajectories with automatic backend selection:
>>> def model(predict_times=None):
... dynamics = DynamicalModel(...)
... dsx.sample("f", dynamics, predict_times=predict_times)
>>> with Simulator(n_simulations=4):
... predictive = Predictive(
... model, num_samples=20, exclude_deterministic=False
... )
... draws = predictive(jr.PRNGKey(0), predict_times=times)
>>> draws["f_states"].shape
(20, 4, T, state_dim)
Pure-JAX forward simulation returns the same trajectory fields without adding NumPyro sites:
>>> result = dsx.simulate(
... dynamics,
... rng_key=jr.PRNGKey(0),
... predict_times=times,
... n_simulations=4,
... )
For posterior state rollouts, place the simulator outside the inference
handler and retain deterministic sites from Predictive:
>>> with Simulator(n_simulations=100):
... with Filter():
... predictive = Predictive(
... model,
... posterior_samples=posterior_samples,
... exclude_deterministic=False,
... )
... forecast = predictive(
... jr.PRNGKey(1),
... obs_times=obs_times,
... obs_values=obs_values,
... predict_times=forecast_times,
... )
What this does¶
The concrete backend is selected from dynamics.state_evolution:
StochasticContinuousTimeStateEvolutionuses SDESimulator.DeterministicContinuousTimeStateEvolutionuses ODESimulator.- Discrete-time state evolution uses DiscreteTimeSimulator.
A simulator is generation-only: raw simulator calls accept
predict_times, not obs_times or obs_values. For observation-conditioned
inference, use
LatentPathBuilder,
Filter, or
Smoother. A simulator may wrap a
Filter or Smoother to draw posterior rollouts at predict_times; in
that composition, the inference handler consumes the observations and
passes posterior state distributions outward to the simulator.
Configurations and defaults¶
simulator_config accepts a
SimulatorConfig and
forwards it to the selected continuous-time backend:
- stochastic continuous-time models accept
SDESimulatorConfig; - deterministic continuous-time models accept
ODESimulatorConfig; - discrete-time models do not accept a simulator config.
If simulator_config=None, discrete transitions are sampled directly,
deterministic continuous-time models use the ODESimulatorConfig defaults
(diffrax.Tsit5() with fixed dt0=1e-3), and stochastic continuous-time
models use the SDESimulatorConfig defaults (the fixed-step "em_scan"
Euler--Maruyama backend with dt0=1e-4). n_simulations defaults to one
and must be at least one. The simulation dimension is retained even when it
has length one.
NumPyro trace¶
For a raw rollout from dsx.sample("f", ...), the following
numpyro.deterministic sites are added:
"f_x_0": initial states, shape(*plate_shape, n_simulations, state_dim);"f_times": prediction times, shape(*plate_shape, n_simulations, T);"f_states": latent states, shape(*plate_shape, n_simulations, T, state_dim);"f_observations": sampled observations, shape(*plate_shape, n_simulations, T, observation_dim).
Here "f" is replaced by the name passed to dsx.sample, T is the
length of the prediction grid, and plate_shape is absent outside a
dsx.plate. Under Predictive(..., num_samples=N), NumPyro prepends an
N axis to every shape above. Because these sites are deterministic, pass
exclude_deterministic=False to Predictive (or request the site names
explicitly) to include them in its returned dictionary.
When the simulator wraps a Filter or Smoother, the inner handler records
its own configured sites and the simulator's aggregate rollout sites are
instead "f_predicted_times", "f_predicted_states", and
"f_predicted_observations", with the corresponding time, state, and
observation shapes above. Each nonempty prediction segment also records
the state from which that segment starts, with shape
(n_simulations, state_dim): "f_0_x_0" for a segment before the first
posterior time, and "f_{j+1}_x_0" for a segment initialized from the
posterior at inference-time index j. Only segments containing at least
one requested prediction time are recorded. Inside dsx.plate, the segment
name also identifies the plate member, for example "f_p0_1_x_0".
If predict_times is omitted, the simulator performs no rollout and adds
no simulator sites. Direct calls to
Simulator().simulate and
dsx.simulate return a SimulatedResult and do not
add NumPyro sites.
Notes
- Controls are passed through to the selected backend. See the concrete simulator for its control interpolation or alignment rules.
- A
Simulatorinstance selects and caches its concrete backend on the first dynamics object it handles. Use separate instances for models requiring different backend types. Simulator().simulate(...)consumes an already allocated simulation key. The public dsx.simulate function splits its root key before dispatch.
Attributes:
| Name | Type | Description |
|---|---|---|
simulator_config |
Optional ODE or SDE solver configuration. Its type must match the model selected at first use. |
|
n_simulations |
int
|
Number of independent trajectories drawn per model execution. Defaults to one and must be greater than or equal to one. |
simulator |
BaseSimulator | None
|
Concrete auto-selected simulator cached on first use. |
simulate(dynamics: DynamicalModel, *, rng_key: PRNGKeyArray, ctrl_times: Real[Array, ' ctrl_time'] | None = None, ctrl_values: Real[Array, 'ctrl_time control_dim'] | Real[Array, ' ctrl_time'] | None = None, predict_times: Real[Array, ' predict_time'] | None = None, **kwargs) -> SimulatedResult
¶
Auto-route to the appropriate pure-JAX simulator backend.
Unlike dsx.simulate, rng_key is consumed
directly as an already-allocated simulation key and is not pre-split.
Therefore, dsx.simulate(..., rng_key=root_key) is equivalent to
Simulator().simulate(..., rng_key=jax.random.split(root_key)[1]).
Examples¶
Predictive with auto-routing
import dynestyx as dsx
import jax.numpy as jnp
import jax.random as jr
import numpyro
import numpyro.distributions as dist
from dynestyx import DynamicalModel, GaussianStateEvolution, Simulator
from numpyro.infer import Predictive
state_dim = 1
observation_dim = 1
def model(phi=None, predict_times=None):
phi = numpyro.sample("phi", dist.Uniform(0.0, 1.0), obs=phi)
dynamics = DynamicalModel(
control_dim=0,
initial_condition=dist.MultivariateNormal(
loc=jnp.zeros(state_dim),
covariance_matrix=jnp.eye(state_dim),
),
state_evolution=GaussianStateEvolution(
F=lambda x, u, t_now, t_next: phi * x + 0.1 * jnp.sin(x),
cov=0.2**2 * jnp.eye(state_dim),
),
observation_model=lambda x, u, t: dist.MultivariateNormal(
x,
0.3**2 * jnp.eye(observation_dim),
),
)
return dsx.sample("f", dynamics, predict_times=predict_times)
predict_times = jnp.arange(20.0)
with Simulator():
prior_pred = Predictive(model, num_samples=5)(
jr.PRNGKey(0),
predict_times=predict_times,
)
print("Predictive keys:", sorted(prior_pred.keys())) # e.g. ['f_observations', 'f_states', 'f_times', 'phi', ...]
print("Predictive shapes:", {k: v.shape for k, v in prior_pred.items()}) # trajectory arrays: (num_samples, n_sim, T, dim); here num_samples=5, n_sim=1
Note
Simulator only auto-routes forward generation and rollout. For explicit
latent-state inference use LatentPathBuilder; for marginalized inference
use Filter or Smoother.