SSMProblems

Installation

In the julia REPL:

] add SSMProblems

Documentation

SSMProblems defines a minimal interface for state space models (SSMs). It defines three process abstract types, distribution adapters, the distribution/simulate/logdensity generics, and a model interface with a standard container. Inference packages such as GeneralisedFilters build their algorithms, parameter types and conditioning mechanisms on top of it, so a model written against this interface can be used by algorithms that support its components.

Consider a standard (Markovian) state-space model from[Murray]: state space model

The following three distributions fully specify the model:

  • The initialisation distribution, $f_0$, for the initial latent state $X_0$
  • The transition distribution, $f$, for the latent state $X_t$ given the previous $X_{t-1}$
  • The observation distribution, $g$, for an observation $Y_t$ given the state $X_t$

The dynamics of the model are given by,

\[\begin{aligned} x_0 &\sim f_0(x_0) \\ x_t | x_{t-1} &\sim f(x_t | x_{t-1}) \\ y_t | x_t &\sim g(y_t | x_{t}) \end{aligned}\]

and the joint law is,

\[p(x_{0:T}, y_{1:T}) = f_0(x_0) \prod_{t=1}^{T} g(y_t | x_t) f(x_t | x_{t-1}).\]

We can consider a state space model as being made up of two components:

  • A latent Markov chain describing the evolution of the latent state
  • An observation process describing the relationship between the latent states and the observations

Through this lens, we see that the distributions $f_0$, $f$ fully describe the latent Markov chain, whereas $g$ describes the observation process.

A user of SSMProblems may define these three distributions directly. Alternatively, they can define a subset of methods for sampling and evaluating log-densities of the distributions, depending on the requirements of the filtering/smoothing algorithms they intend to use.

For a model defined by distributions, use the adapters:

using Distributions, Random, SSMProblems

model = StateSpaceModel(
    DistributionPrior(Normal(0.0, 1.0)),
    DistributionDynamics((t, state) -> Normal(state, 0.1)),
    DistributionObservation((t, state) -> Normal(state, 0.5)),
)
x0, xs, ys = simulate(Xoshiro(42), model, 10)
(0.7883556016042917, [0.7003697420088518, 0.6129903937878892, 0.5396648973443999, 0.4694453774686319, 0.4621871906640449, 0.37089485203005224, 0.4340569351417275, 0.5779252627128688, 0.5425082783758242, 0.6221209703036275], [0.11268417540005837, 0.3162928741345061, 0.754468079830376, 0.9456096090292683, 0.526756796494217, 0.2235079052494844, 0.24692271753250028, 1.1626811328363882, 0.670932526430488, 0.7225835146231031])

The functions receive the time index, so time-varying coefficients can be captured in a closure. For example, DistributionDynamics((t, state) -> Normal(a[t] * state, 0.1)) uses a vector of coefficients a from its enclosing scope.

You can also define a process type when you want to reuse it or provide specialised methods. This dynamics component describes the same transition as the closure above:

struct RandomWalk <: LatentDynamics end
SSMProblems.distribution(::RandomWalk, t::Integer, state) = Normal(state, 0.1)
reusable_model = StateSpaceModel(prior(model), RandomWalk(), obs(model))
simulate(Xoshiro(42), reusable_model, 10)
(0.7883556016042917, [0.7003697420088518, 0.6129903937878892, 0.5396648973443999, 0.4694453774686319, 0.4621871906640449, 0.37089485203005224, 0.4340569351417275, 0.5779252627128688, 0.5425082783758242, 0.6221209703036275], [0.11268417540005837, 0.3162928741345061, 0.754468079830376, 0.9456096090292683, 0.526756796494217, 0.2235079052494844, 0.24692271753250028, 1.1626811328363882, 0.670932526430488, 0.7225835146231031])

There are a few things to note here:

  • The prior takes no time or state arguments. The dynamics and observation process take both.
  • Parameters, controls and external inputs are stored in the component or captured by its function. The process methods do not forward keyword arguments. Inference packages can provide further component types, such as linear-Gaussian transitions whose matrix structure can be used by a Kalman filter.
  • If your latent dynamics or observation process cannot be represented as a Distribution object, implement simulate and/or logdensity directly instead, as documented below.

These distribution definitions are used to derive the simulate and logdensity methods for each component. Package users then interact with the state space model through those functions.

For example, a bootstrap filter targeting the filtering distribution $p(x_t | y_{1:t})$ using N particles would roughly follow:

dynamics, observations_process = dyn(model), obs(model)

for (t, observation) in enumerate(observations)
    idx = resample(rng, log_weights)
    particles = particles[idx]
    fill!(log_weights, 0)
    for i in 1:N
        particles[i] = simulate(rng, dynamics, t, particles[i])
        log_weights[i] += logdensity(observations_process, t, particles[i], observation)
    end
end

For more thorough examples, see the provided example scripts.

Custom model containers

StateSpaceModel stores the three components directly. A custom model container can subtype AbstractStateSpaceModel and implement prior(model), dyn(model) and obs(model). These accessors let simulation and inference code use the components without requiring particular field names. StateSpaceModel(model) builds the standard container from these accessors and retains the component objects.

AbstractStateSpaceModel has no AbstractMCMC dependency. Packages implementing samplers can provide their own wrappers for that integration.

Interface

SSMProblems.AbstractStateSpaceModel — Type
AbstractStateSpaceModel

Interface for a state-space model. Subtypes implement prior, dyn, and obs to expose their model components. A custom model need not store components in fields with those names. Inference algorithms may require additional structure from the returned components.

source
SSMProblems.LatentDynamics — Type
LatentDynamics

Transition dynamics of a state-space model. A concrete subtype should implement simulate and/or logdensity, or supply a distribution(dyn, t, x) method from which both are derived. Which methods are required depends on the inference algorithm (e.g. a bootstrap filter needs only simulate, a guided proposal also needs logdensity).

source
SSMProblems.ObservationProcess — Type
ObservationProcess

Emission process of a state-space model. A concrete subtype should implement logdensity (and optionally simulate for forward simulation), or supply a distribution(obs, t, x) method from which both are derived.

source
SSMProblems.StatePrior — Type
StatePrior

Initial state distribution of a state-space model. A concrete subtype should implement simulate and/or logdensity, or supply a distribution(prior) method from which both are derived. Which methods are required depends on the inference algorithm.

source
SSMProblems.StateSpaceModel — Type
StateSpaceModel(prior, dyn, obs)
StateSpaceModel(model::AbstractStateSpaceModel)

A state-space model composed of an initial state prior, latent dynamics, and an observation process. The one-argument constructor obtains the components through prior, dyn, and obs. It retains those components without copying them; an existing StateSpaceModel is returned unchanged.

source
SSMProblems.distribution — Function
distribution(prior::StatePrior)
distribution(dyn::LatentDynamics, t::Integer, x)
distribution(obs::ObservationProcess, t::Integer, x)

Return the distribution associated with a model component. Implementing this method derives simulate and logdensity for free; components may instead implement those directly when no tractable distribution object is available.

Components take no keyword arguments. Dependence on parameters, controls or an outer state is expressed by the component object itself, which downstream packages may build per step.

source
SSMProblems.logdensity — Function
logdensity(prior::StatePrior, x0)
logdensity(dyn::LatentDynamics, t::Integer, x_prev, x_new)
logdensity(obs::ObservationProcess, t::Integer, x, y)

Evaluate the log-density of a model component, by default that of its distribution.

source
SSMProblems.simulate — Function
simulate(rng::AbstractRNG, prior::StatePrior)
simulate(rng::AbstractRNG, dyn::LatentDynamics, t::Integer, x)
simulate(rng::AbstractRNG, obs::ObservationProcess, t::Integer, x)

Draw from a model component, by default from its distribution.

source
SSMProblems.simulate — Method
simulate([rng,] model::AbstractStateSpaceModel, T::Integer)

Simulate a trajectory of length T, returning (x0, xs, ys) where x0 is the initial state, xs the states at times 1:T, and ys the observations at times 1:T.

source
SSMProblems.simulate_from_dist — Method
simulate_from_dist(rng::AbstractRNG, d)

Draw a sample from a distribution object. Defaults to rand(rng, d); specialise it to return static arrays or otherwise control the sample type.

source
SSMProblems.SSMProblems — Module

A minimal interface for defining state-space models.

This package defines process and model interfaces, distribution adapters, and the distribution/simulate/logdensity generics. Inference packages build on these without depending on each other. Conditioning mechanisms, parameter atoms and algorithm-specific machinery deliberately live downstream.

source
  • Murray

    Murray, Lawrence & Lee, Anthony & Jacob, Pierre. (2013). Rethinking resampling in the particle filter on graphics processing units.