Example #1
0
def car_tracking():

    # Below is for consistency with pytest & unittest.
    # Without a seed, unittest passes but pytest fails.
    # I tried multiple seeds, they all work equally well.
    np.random.seed(12345)

    delta_t = 0.2
    var = 0.5
    dynamat = np.eye(4) + delta_t * np.diag(np.ones(2), 2)
    dynadiff = (
        np.diag(np.array([delta_t**3 / 3, delta_t**3 / 3, delta_t, delta_t])) +
        np.diag(np.array([delta_t**2 / 2, delta_t**2 / 2]), 2) +
        np.diag(np.array([delta_t**2 / 2, delta_t**2 / 2]), -2))
    measmat = np.eye(2, 4)
    measdiff = var * np.eye(2)
    mean = np.zeros(4)
    cov = 0.5 * var * np.eye(4)

    dynmod = pnss.DiscreteLTIGaussian(state_trans_mat=dynamat,
                                      shift_vec=np.zeros(4),
                                      proc_noise_cov_mat=dynadiff)
    measmod = pnss.DiscreteLTIGaussian(
        state_trans_mat=measmat,
        shift_vec=np.zeros(2),
        proc_noise_cov_mat=measdiff,
    )
    initrv = Normal(mean, cov)
    return dynmod, measmod, initrv, {"dt": delta_t, "tmax": 20}
def test_sampling_shapes_1d(locs, size):
    """Make the sampling tests for a 1d posterior."""
    locations = np.linspace(0, 2 * np.pi, 100)
    data = 0.5 * np.random.randn(100) + np.sin(locations)

    prior = statespace.IBM(0, 1)
    measmod = statespace.DiscreteLTIGaussian(state_trans_mat=np.eye(1),
                                             shift_vec=np.zeros(1),
                                             proc_noise_cov_mat=np.eye(1))
    initrv = randvars.Normal(np.zeros(1), np.eye(1))

    kalman = filtsmooth.Kalman(prior, measmod, initrv)
    regression_problem = problems.RegressionProblem(observations=data,
                                                    locations=locations)
    posterior, _ = kalman.filtsmooth(regression_problem)

    size = utils.as_shape(size)
    if locs is None:
        base_measure_reals = np.random.randn(
            *(size + posterior.locations.shape + (1, )))
        samples = posterior.transform_base_measure_realizations(
            base_measure_reals, t=posterior.locations)
    else:
        locs = np.union1d(locs, posterior.locations)
        base_measure_reals = np.random.randn(*(size + (len(locs), )) + (1, ))
        samples = posterior.transform_base_measure_realizations(
            base_measure_reals, t=locs)

    assert samples.shape == base_measure_reals.shape
    def _setup(
        self,
        test_ndim,
        spdmat1,
        spdmat2,
        forw_impl_string_linear_gauss,
        backw_impl_string_linear_gauss,
    ):

        self.G_const = spdmat1
        self.S_const = spdmat2
        self.v_const = np.arange(test_ndim)
        self.transition = pnss.DiscreteLTIGaussian(
            self.G_const,
            self.v_const,
            self.S_const,
            forward_implementation=forw_impl_string_linear_gauss,
            backward_implementation=backw_impl_string_linear_gauss,
        )

        # Compatibility with superclass' test
        self.G = lambda t: self.G_const
        self.S = lambda t: self.S_const
        self.v = lambda t: self.v_const
        self.g = lambda t, x: self.G(t) @ x + self.v(t)
        self.dg = lambda t, x: self.G(t)
Example #4
0
def setup(num_particles):
    prior = statespace.IBM(1, 1)
    measmod = statespace.DiscreteLTIGaussian(
        state_trans_mat=np.eye(1, 2),
        shift_vec=np.zeros(1),
        proc_noise_cov_mat=0.00025 * np.eye(1),
    )
    initrv = random_variables.Normal(np.zeros(2), 0.01 * np.eye(2))

    particle = filtsmooth.ParticleFilter(
        prior,
        measmod,
        initrv,
        num_particles=num_particles,
        importance_density_choice="bootstrap",
    )
    return prior, measmod, initrv, particle
Example #5
0
def benes_daum():
    """Benes-Daum testcase, example 10.17 in Applied SDEs."""
    def f(t, x):
        return np.tanh(x)

    def df(t, x):
        return 1.0 - np.tanh(x)**2

    def l(t):
        return np.ones(1)

    initmean = np.zeros(1)
    initcov = 3.0 * np.eye(1)
    initrv = Normal(initmean, initcov)
    dynamod = pnss.SDE(dimension=1, driftfun=f, dispmatfun=l, jacobfun=df)
    measmod = pnss.DiscreteLTIGaussian(np.eye(1), np.zeros(1), np.eye(1))
    return dynamod, measmod, initrv, {}
Example #6
0
def ornstein_uhlenbeck():

    # Below is for consistency with pytest & unittest.
    # Without a seed, unittest passes but pytest fails.
    # I tried multiple seeds, they all work equally well.
    np.random.seed(12345)

    delta_t = 0.2
    lam, q, r = 0.21, 0.5, 0.1
    drift = -lam * np.eye(1)
    force = np.zeros(1)
    disp = np.sqrt(q) * np.eye(1)
    dynmod = pnss.LTISDE(
        driftmat=drift,
        forcevec=force,
        dispmat=disp,
    )
    measmod = pnss.DiscreteLTIGaussian(
        state_trans_mat=np.eye(1),
        shift_vec=np.zeros(1),
        proc_noise_cov_mat=r * np.eye(1),
    )
    initrv = Normal(10 * np.ones(1), np.eye(1))
    return dynmod, measmod, initrv, {"dt": delta_t, "tmax": 20}
Example #7
0
def initialize_odefilter_with_rk(f,
                                 y0,
                                 t0,
                                 prior,
                                 initrv,
                                 df=None,
                                 h0=1e-2,
                                 method="DOP853"):
    r"""Initialize an ODE filter by fitting the prior process to a few steps of an approximate ODE solution computed with Scipy's RK.

    It goes as follows:

    1. The ODE integration problem is set up on the interval ``[t0, t0 + (2*order+1)*h0]``
    and solved with a call to ``scipy.integrate.solve_ivp``. The solver is uses adaptive steps with ``atol=rtol=1e-12``,
    but is forced to pass through the
    events ``(t0, t0+h0, t0 + 2*h0, ..., t0 + (2*order+1)*h0)``.
    The result is a vector of time points and states, with at least ``(2*order+1)``.
    Potentially, the adaptive steps selected many more steps, but because of the events, fewer steps cannot have happened.

    2. A prescribed prior is fitted to the first ``(2*order+1)`` (t, y) pairs of the solution. ``order`` is the order of the prior.

    3. The value of the resulting posterior at time ``t=t0`` is an estimate of the state and all its derivatives.
    The resulting marginal standard deviations estimate the error. This random variable is returned.

    Parameters
    ----------
    f
        ODE vector field.
    y0
        Initial value.
    t0
        Initial time point.
    prior
        Prior distribution used for the ODE solver. For instance an integrated Brownian motion prior (``IBM``).
    initrv
        Initial random variable.
    df
        Jacobian of the ODE vector field. Optional. If specified, more components of the result will be exact.
    h0
        Maximum step-size to use for computing the approximate ODE solution. The smaller, the more accurate, but also, the smaller, the less stable.
        The best value here depends on the ODE problem, and probably the chosen method. Optional. Default is ``1e-2``.
    method
        Which solver to use. This is communicated as a string that is compatible with ``scipy.integrate.solve_ivp(..., method=method)``.
        Optional. Default is `DOP853`.

    Returns
    -------
    Normal
        Estimated (improved) initial random variable. Compatible with the specified prior.

    Examples
    --------

    >>> from dataclasses import astuple
    >>> from probnum.randvars import Normal
    >>> from probnum.statespace import IBM
    >>> from probnum.problems.zoo.diffeq import vanderpol

    Compute the initial values of the van-der-Pol problem as follows

    >>> f, t0, tmax, y0, df, *_ = astuple(vanderpol())
    >>> print(y0)
    [2. 0.]
    >>> prior = IBM(ordint=3, spatialdim=2)
    >>> initrv = Normal(mean=np.zeros(prior.dimension), cov=np.eye(prior.dimension))
    >>> improved_initrv = initialize_odefilter_with_rk(f, y0, t0, prior=prior, initrv=initrv, df=df)
    >>> print(prior.proj2coord(0) @ improved_initrv.mean)
    [2. 0.]
    >>> print(np.round(improved_initrv.mean, 1))
    [    2.      0.     -2.     58.2     0.     -2.     60.  -1745.7]
    >>> print(np.round(np.log10(improved_initrv.std), 1))
    [-13.8 -11.3  -9.   -1.5 -13.8 -11.3  -9.   -1.5]
    """
    y0 = np.asarray(y0)
    ode_dim = y0.shape[0] if y0.ndim > 0 else 1
    order = prior.ordint
    proj_to_y = prior.proj2coord(0)
    zeros_shift = np.zeros(ode_dim)
    zeros_cov = np.zeros((ode_dim, ode_dim))
    measmod = statespace.DiscreteLTIGaussian(
        proj_to_y,
        zeros_shift,
        zeros_cov,
        proc_noise_cov_cholesky=zeros_cov,
        forward_implementation="sqrt",
        backward_implementation="sqrt",
    )

    # order + 1 would suffice in theory, 2*order + 1 is for good measure
    # (the "+1" is a safety factor for order=1)
    num_steps = 2 * order + 1
    t_eval = np.arange(t0, t0 + (num_steps + 1) * h0, h0)
    sol = sci.solve_ivp(
        f,
        (t0, t0 + (num_steps + 1) * h0),
        y0=y0,
        atol=1e-12,
        rtol=1e-12,
        t_eval=t_eval,
        method=method,
    )

    ts = sol.t[:num_steps]
    ys = sol.y[:, :num_steps].T

    initmean = initrv.mean.copy()
    initmean[0::(order + 1)] = y0
    initmean[1::(order + 1)] = f(t0, y0)

    initcov_diag = np.diag(initrv.cov).copy()
    initcov_diag[0::(order + 1)] = SMALL_VALUE
    initcov_diag[1::(order + 1)] = SMALL_VALUE

    if df is not None:
        if order > 1:
            initmean[2::(order + 1)] = df(t0, y0) @ f(t0, y0)
            initcov_diag[2::(order + 1)] = SMALL_VALUE

    initcov = np.diag(initcov_diag)
    initcov_cholesky = np.diag(np.sqrt(initcov_diag))
    initrv = randvars.Normal(initmean, initcov, cov_cholesky=initcov_cholesky)
    kalman = filtsmooth.Kalman(prior, measmod, initrv)

    regression_problem = problems.RegressionProblem(observations=ys,
                                                    locations=ts)
    out, _ = kalman.filtsmooth(regression_problem)

    estimated_initrv = out.states[0]

    return estimated_initrv
def benes_daum(
    measurement_variance: FloatArgType = 0.1,
    process_diffusion: FloatArgType = 1.0,
    time_grid: Optional[np.ndarray] = None,
    initrv: Optional[randvars.RandomVariable] = None,
):
    r"""Filtering/smoothing setup based on the Beneš SDE.

    A non-linear state space model for the dynamics of a Beneš SDE.
    Here, we formulate a continuous-discrete state space model:

    .. math::

        d x(t) &= \tanh(x(t)) d t + L d w(t) \\
        y_n &= x(t_n) + r_n

    for a driving Wiener process :math:`w(t)` and Gaussian distributed measurement noise
    :math:`r_n \sim \mathcal{N}(0, R)` with measurement noise
    covariance matrix :math:`R`.

    Parameters
    ----------
    measurement_variance
        Marginal measurement variance.
    process_diffusion
        Diffusion constant for the dynamics
    time_grid
        Time grid for the filtering/smoothing problem.
    initrv
        Initial random variable.

    Returns
    -------
    regression_problem
        ``RegressionProblem`` object with time points and noisy observations.
    statespace_components
        Dictionary containing

        * dynamics model
        * measurement model
        * initial random variable

    Notes
    -----
    In order to generate observations for the returned ``RegressionProblem`` object,
    the non-linear Beneš SDE has to be linearized.
    Here, a ``ContinuousEKFComponent`` is used, which corresponds to a first-order
    linearization as used in the extended Kalman filter.
    """

    def f(t, x):
        return np.tanh(x)

    def df(t, x):
        return 1.0 - np.tanh(x) ** 2

    def l(t):
        return process_diffusion * np.ones(1)

    if initrv is None:
        initrv = randvars.Normal(np.zeros(1), 3.0 * np.eye(1))

    dynamics_model = statespace.SDE(dimension=1, driftfun=f, dispmatfun=l, jacobfun=df)
    measurement_model = statespace.DiscreteLTIGaussian(
        state_trans_mat=np.eye(1),
        shift_vec=np.zeros(1),
        proc_noise_cov_mat=measurement_variance * np.eye(1),
    )

    # Generate data
    if time_grid is None:
        time_grid = np.arange(0.0, 4.0, step=0.2)
    # The non-linear dynamics are linearized according to an EKF in order
    # to generate samples.
    linearized_dynamics_model = filtsmooth.ContinuousEKFComponent(
        non_linear_model=dynamics_model
    )
    states, obs = statespace.generate_samples(
        dynmod=linearized_dynamics_model,
        measmod=measurement_model,
        initrv=initrv,
        times=time_grid,
    )
    regression_problem = problems.RegressionProblem(
        observations=obs, locations=time_grid, solution=states
    )

    statespace_components = dict(
        dynamics_model=dynamics_model,
        measurement_model=measurement_model,
        initrv=initrv,
    )
    return regression_problem, statespace_components
def car_tracking(
    measurement_variance: FloatArgType = 0.5,
    process_diffusion: FloatArgType = 1.0,
    model_ordint: IntArgType = 1,
    timespan: Tuple[FloatArgType, FloatArgType] = (0.0, 20.0),
    step: FloatArgType = 0.2,
    initrv: Optional[randvars.RandomVariable] = None,
    forward_implementation: str = "classic",
    backward_implementation: str = "classic",
):
    r"""Filtering/smoothing setup for a simple car-tracking scenario.

    A discrete, linear, time-invariant Gaussian state space model for car-tracking,
    based on Example 3.6 in Särkkä, 2013. [1]_
    Let :math:`X = (\dot{x}_1, \dot{x}_2, \ddot{x}_1, \ddot{x}_2)`. Then the state
    space model has the following discretized formulation

    .. math::

        X(t_{n}) &=
        \begin{pmatrix}
          1 & 0 & \Delta t& 0 \\
          0 & 1 & 0 & \Delta t \\
          0 & 0 & 1 & 0 \\
          0 & 0 & 0 & 1
        \end{pmatrix} X(t_{n-1})
        +
        q_n \\
        y_{n} &=
        \begin{pmatrix}
          1 & 0 & 0 & 0 \\
          0 & 1 & 0 & 0 \\
        \end{pmatrix} X(t_{n})
        + r_n

    where :math:`q_n \sim \mathcal{N}(0, Q)` and :math:`r_n \sim \mathcal{N}(0, R)`
    for process noise covariance matrix :math:`Q` and measurement noise covariance
    matrix :math:`R`.

    Parameters
    ----------
    measurement_variance
        Marginal measurement variance.
    process_diffusion
        Diffusion constant for the dynamics.
    model_ordint
        Order of integration for the dynamics model. Defaults to one, which corresponds
        to a Wiener velocity model.
    timespan
        :math:`t_0` and :math:`t_{\max}` of the time grid.
    step
        Step size of the time grid.
    initrv
        Initial random variable.

    Returns
    -------
    regression_problem
        ``RegressionProblem`` object with time points and noisy observations.
    statespace_components
        Dictionary containing

        * dynamics model
        * measurement model
        * initial random variable

    References
    ----------
    .. [1] Särkkä, Simo. Bayesian Filtering and Smoothing. Cambridge University Press,
        2013.

    """
    state_dim = 2
    model_dim = state_dim * (model_ordint + 1)
    measurement_dim = 2
    dynamics_model = statespace.IBM(
        ordint=model_ordint,
        spatialdim=state_dim,
        forward_implementation=forward_implementation,
        backward_implementation=backward_implementation,
    )
    dynamics_model.dispmat *= process_diffusion

    discrete_dynamics_model = dynamics_model.discretise(dt=step)

    measurement_matrix = np.eye(measurement_dim, model_dim)
    measurement_cov = measurement_variance * np.eye(measurement_dim)
    measurement_cov_cholesky = np.sqrt(measurement_variance) * np.eye(measurement_dim)
    measurement_model = statespace.DiscreteLTIGaussian(
        state_trans_mat=measurement_matrix,
        shift_vec=np.zeros(measurement_dim),
        proc_noise_cov_mat=measurement_cov,
        proc_noise_cov_cholesky=measurement_cov_cholesky,
        forward_implementation=forward_implementation,
        backward_implementation=backward_implementation,
    )

    if initrv is None:
        initrv = randvars.Normal(
            np.zeros(model_dim),
            measurement_variance * np.eye(model_dim),
            cov_cholesky=np.sqrt(measurement_variance) * np.eye(model_dim),
        )

    # Set up regression problem
    time_grid = np.arange(*timespan, step=step)
    states, obs = statespace.generate_samples(
        dynmod=discrete_dynamics_model,
        measmod=measurement_model,
        initrv=initrv,
        times=time_grid,
    )
    regression_problem = problems.RegressionProblem(
        observations=obs, locations=time_grid, solution=states
    )

    statespace_components = dict(
        dynamics_model=discrete_dynamics_model,
        measurement_model=measurement_model,
        initrv=initrv,
    )
    return regression_problem, statespace_components
def ornstein_uhlenbeck(
    measurement_variance: FloatArgType = 0.1,
    driftspeed: FloatArgType = 0.21,
    process_diffusion: FloatArgType = 0.5,
    time_grid: Optional[np.ndarray] = None,
    initrv: Optional[randvars.RandomVariable] = None,
    forward_implementation: str = "classic",
    backward_implementation: str = "classic",
):
    r"""Filtering/smoothing setup based on an Ornstein Uhlenbeck process.

    A linear, time-invariant state space model for the dynamics of a time-invariant
    Ornstein-Uhlenbeck process. See e.g. Example 10.19 in Särkkä et. al, 2019. [1]_
    Here, we formulate a continuous-discrete state space model:

    .. math::

        d x(t) &= \lambda x(t) d t + L d w(t) \\
        y_n &= x(t_n) + r_n

    for a drift constant :math:`\lambda` and a driving Wiener process :math:`w(t)`.
    :math:`r_n \sim \mathcal{N}(0, R)` is Gaussian distributed measurement noise
    with covariance matrix :math:`R`.
    Note that the linear, time-invariant dynamics have an equivalent discretization.

    Parameters
    ----------
    measurement_variance
        Marginal measurement variance.
    driftspeed
        Drift parameter of the Ornstein-Uhlenbeck process.
    process_diffusion
        Diffusion constant for the dynamics
    time_grid
        Time grid for the filtering/smoothing problem.
    initrv
        Initial random variable.


    Returns
    -------
    regression_problem
        ``RegressionProblem`` object with time points and noisy observations.
    statespace_components
        Dictionary containing

        * dynamics model
        * measurement model
        * initial random variable


    References
    ----------
    .. [1] Särkkä, Simo, and Solin, Arno. Applied Stochastic Differential Equations.
        Cambridge University Press, 2019
    """

    dynamics_model = statespace.IOUP(
        ordint=0,
        spatialdim=1,
        driftspeed=driftspeed,
        forward_implementation=forward_implementation,
        backward_implementation=backward_implementation,
    )
    dynamics_model.dispmat *= process_diffusion

    measurement_model = statespace.DiscreteLTIGaussian(
        state_trans_mat=np.eye(1),
        shift_vec=np.zeros(1),
        proc_noise_cov_mat=measurement_variance * np.eye(1),
        forward_implementation=forward_implementation,
        backward_implementation=backward_implementation,
    )

    if initrv is None:
        initrv = randvars.Normal(10.0 * np.ones(1), np.eye(1))

    # Set up regression problem
    if time_grid is None:
        time_grid = np.arange(0.0, 20.0, step=0.2)
    states, obs = statespace.generate_samples(
        dynmod=dynamics_model, measmod=measurement_model, initrv=initrv, times=time_grid
    )
    regression_problem = problems.RegressionProblem(
        observations=obs, locations=time_grid, solution=states
    )

    statespace_components = dict(
        dynamics_model=dynamics_model,
        measurement_model=measurement_model,
        initrv=initrv,
    )
    return regression_problem, statespace_components