コード例 #1
0
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
コード例 #2
0
def logistic_ode():

    # 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
    tmax = 2

    logistic = pnd.logistic((0, tmax),
                            initrv=Constant(np.array([0.1])),
                            params=(6, 1))
    dynamod = pnss.IBM(ordint=3, spatialdim=1)
    measmod = pnfs.DiscreteEKFComponent.from_ode(logistic,
                                                 dynamod,
                                                 np.zeros((1, 1)),
                                                 ek0_or_ek1=1)

    initmean = np.array([0.1, 0, 0.0, 0.0])
    initcov = np.diag([0.0, 1.0, 1.0, 1.0])
    initrv = Normal(initmean, initcov)

    return dynamod, measmod, initrv, {
        "dt": delta_t,
        "tmax": tmax,
        "ode": logistic
    }
コード例 #3
0
def test_initialize_with_taylormode(any_order):
    """Make sure that the values are close(ish) to the truth."""
    r2b_jax = diffeq_zoo.threebody_jax()
    ode_dim = 4
    expected = pnss.Integrator._convert_derivwise_to_coordwise(
        THREEBODY_INITS[: ode_dim * (any_order + 1)],
        ordint=any_order,
        spatialdim=ode_dim,
    )

    prior = pnss.IBM(
        ordint=any_order,
        spatialdim=ode_dim,
        forward_implementation="sqrt",
        backward_implementation="sqrt",
    )

    initrv = pnrv.Normal(np.zeros(prior.dimension), np.eye(prior.dimension))

    received_rv = pnde.initialize_odefilter_with_taylormode(
        r2b_jax.f, r2b_jax.y0, r2b_jax.t0, prior=prior, initrv=initrv
    )

    np.testing.assert_allclose(received_rv.mean, expected)
    np.testing.assert_allclose(received_rv.std, 0.0)
コード例 #4
0
def test_initialize_with_rk(lv, lv_inits, order):
    """Make sure that the values are close(ish) to the truth."""
    ode_dim = len(lv.initrv.mean)
    prior = pnss.IBM(
        ordint=order,
        spatialdim=ode_dim,
        forward_implementation="sqrt",
        backward_implementation="sqrt",
    )
    initrv = pnrv.Normal(np.zeros(prior.dimension), np.eye(prior.dimension))
    received_rv = pnde.initialize_odefilter_with_rk(
        lv.rhs,
        lv.initrv.mean,
        lv.t0,
        prior=prior,
        initrv=initrv,
        df=lv.jacobian,
        h0=1e-1,
        method="RK45",
    )
    # Extract the relevant values
    expected = lv_inits

    # The higher derivatives will have absolute difference ~8%
    # if things work out correctly
    np.testing.assert_allclose(received_rv.mean, expected, rtol=0.25)
    assert np.linalg.norm(received_rv.std) > 0
コード例 #5
0
 def _setup(
     self,
     forw_impl_string_linear_gauss,
     backw_impl_string_linear_gauss,
 ):
     spatialdim = 1  # make tests compatible with some_normal_rv1, etc.
     self.transition = pnss.IBM(
         ordint=2,
         spatialdim=spatialdim,
         forward_implementation=forw_impl_string_linear_gauss,
         backward_implementation=backw_impl_string_linear_gauss,
     )
コード例 #6
0
def kalpost():
    """Kalman posterior with irrelevant values.

    Used to test calibration measures
    """
    rvlist = [
        randvars.Normal(mean=i + np.arange(2, 4), cov=np.diag(np.arange(4, 6)))
        for i in range(10)
    ]
    locs = np.linspace(0.0, 1.0, 10)
    return filtsmooth.FilteringPosterior(states=rvlist,
                                         locations=locs,
                                         transition=statespace.IBM(1, 1))
コード例 #7
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
コード例 #8
0
    def _setup(
        self,
        some_ordint,
        forw_impl_string_linear_gauss,
        backw_impl_string_linear_gauss,
    ):
        self.some_ordint = some_ordint
        spatialdim = 1  # make tests compatible with some_normal_rv1, etc.
        self.transition = pnss.IBM(
            ordint=self.some_ordint,
            spatialdim=spatialdim,
            forward_implementation=forw_impl_string_linear_gauss,
            backward_implementation=backw_impl_string_linear_gauss,
        )

        self.G = lambda t: self.transition.driftmat
        self.v = lambda t: self.transition.forcevec
        self.L = lambda t: self.transition.dispmat

        self.g = lambda t, x: self.G(t) @ x + self.v(t)
        self.dg = lambda t, x: self.G(t)
コード例 #9
0
def both_transitions_ibm():
    ibm = pnss.IBM(ordint=2, spatialdim=1)
    ibm2 = pnss.IBM(ordint=2, spatialdim=1)
    ibm_as_ltisde = pnss.LTISDE(ibm2.driftmat, ibm2.forcevec, ibm2.dispmat)
    return ibm, ibm_as_ltisde
コード例 #10
0
def logistic_ode(
    y0: Optional[Union[np.ndarray, FloatArgType]] = None,
    timespan: Tuple[FloatArgType, FloatArgType] = (0.0, 2.0),
    step: FloatArgType = 0.1,
    params: Tuple[FloatArgType, FloatArgType] = (6.0, 1.0),
    initrv: Optional[randvars.RandomVariable] = None,
    evlvar: Optional[Union[np.ndarray, FloatArgType]] = None,
    ek0_or_ek1: IntArgType = 1,
    order: IntArgType = 3,
    forward_implementation: str = "classic",
    backward_implementation: str = "classic",
):
    r"""Filtering/smoothing setup for a probabilistic ODE solver for the logistic ODE.

    This state space model assumes an integrated Brownian motion prior on the dynamics
    and constructs the ODE likelihood based on the vector field defining the
    logistic ODE.

    Parameters
    ----------
    y0
        Initial conditions of the Initial Value Problem
    timespan
        Time span of the problem
    params
        Parameters for the logistic ODE
    initrv
        Initial random variable of the probabilistic ODE solver
    evlvar
        See :py:class:`probnum.diffeq.GaussianIVPFilter`
    ek0_or_ek1
        See :py:class:`probnum.diffeq.GaussianIVPFilter`
    order
        Order of integration for the Integrated Brownian Motion prior of the solver.

    Returns
    -------
    regression_problem
        ``RegressionProblem`` object with time points and zero-observations.

    statespace_components
        Dictionary containing

        * dynamics model
        * measurement model
        * initial random variable
        * The initial value problem based on the logistic ODE.

    See Also
    --------
    :py:class:`probnum.diffeq.GaussianIVPFilter`

    """

    if y0 is None:
        y0 = np.array([0.1])
    y0 = np.atleast_1d(y0)

    if evlvar is None:
        evlvar = np.zeros((1, 1))

    logistic_ivp = diffeq.logistic(
        timespan=timespan, initrv=randvars.Constant(y0), params=params
    )
    dynamics_model = statespace.IBM(
        ordint=order,
        spatialdim=1,
        forward_implementation=forward_implementation,
        backward_implementation=backward_implementation,
    )
    measurement_model = filtsmooth.DiscreteEKFComponent.from_ode(
        logistic_ivp,
        prior=dynamics_model,
        evlvar=evlvar,
        ek0_or_ek1=ek0_or_ek1,
        forward_implementation=forward_implementation,
        backward_implementation=backward_implementation,
    )

    if initrv is None:
        initmean = np.array([0.1, 0, 0.0, 0.0])
        initcov = np.diag([0.0, 1.0, 1.0, 1.0])
        initrv = randvars.Normal(initmean, initcov)

    # Generate zero-data
    time_grid = np.arange(*timespan, step=step)
    solution = logistic_ivp.solution(time_grid)
    regression_problem = problems.RegressionProblem(
        observations=np.zeros(shape=(time_grid.size, 1)),
        locations=time_grid,
        solution=solution,
    )

    statespace_components = dict(
        dynamics_model=dynamics_model,
        measurement_model=measurement_model,
        initrv=initrv,
        ivp=logistic_ivp,
    )
    return regression_problem, statespace_components
コード例 #11
0
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
コード例 #12
0
def probsolve_ivp(
    f,
    t0,
    tmax,
    y0,
    df=None,
    method="EK0",
    dense_output=True,
    algo_order=2,
    adaptive=True,
    atol=1e-2,
    rtol=1e-2,
    step=None,
):
    r"""Solve initial value problem with Gaussian filtering and smoothing.

    Numerically computes a Gauss-Markov process which solves numerically
    the initial value problem (IVP) based on a system of first order
    ordinary differential equations (ODEs)

    .. math:: \\dot x(t) = f(t, x(t)), \\quad x(t_0) = x_0,
        \\quad t \\in [t_0, T]

    by regarding it as a (nonlinear) Gaussian filtering (and smoothing)
    problem [3]_. For some configurations it recovers certain multistep
    methods [1]_.
    Convergence rates of filtering [2]_ and smoothing [4]_ are
    comparable to those of methods of Runge-Kutta type.


    This function turns a prior-string into an :class:`ODEPrior`, a
    method-string into a filter/smoother of class :class:`GaussFiltSmooth`, creates a
    :class:`GaussianIVPFilter` object and calls the :meth:`solve()` method. For
    advanced usage we recommend to do this process manually which
    enables advanced methods of tuning the algorithm.

    This function supports the methods:
    extended Kalman filtering based on a zero-th order Taylor
    approximation (EKF0),
    extended Kalman filtering (EKF1),
    unscented Kalman filtering (UKF),
    extended Kalman smoothing based on a zero-th order Taylor
    approximation (EKS0),
    extended Kalman smoothing (EKS1), and
    unscented Kalman smoothing (UKS).

    For adaptive step-size selection of ODE filters, we implement the
    scheme proposed by Schober et al. (2019), and further examined by Bosch et al (2021),
    where the local error estimate is derived from the local, calibrated
    uncertainty estimate.

    Arguments
    ---------
    f :
        ODE vector field.
    t0 :
        Initial time point.
    tmax :
        Final time point.
    y0 :
        Initial value.
    df :
        Jacobian of the ODE vector field.
    adaptive :
        Whether to use adaptive steps or not. Default is `True`.
    atol : float
        Absolute tolerance  of the adaptive step-size selection scheme.
        Optional. Default is ``1e-4``.
    rtol : float
        Relative tolerance   of the adaptive step-size selection scheme.
        Optional. Default is ``1e-4``.
    step :
        Step size. If atol and rtol are not specified, this step-size is used for a fixed-step ODE solver.
        If they are specified, this only affects the first step. Optional.
        Default is None, in which case the first step is chosen as :math:`0.01 \cdot |y_0|/|f(t_0, y_0)|`.
    algo_order
        Order of the algorithm. This amounts to choosing the order of integration (``ordint``) of an integrated Brownian motion prior.
        For too high orders, process noise covariance matrices become singular. For IBM, this maximum seems to be :`q=11` (using standard ``float64``).
        It is possible that higher orders may work for you.
        The type of prior relates to prior assumptions about the
        derivative of the solution.
        The higher the order of the algorithm, the faster the convergence, but also, the higher-dimensional (and thus the costlier) the state space.
    method : str, optional
        Which method is to be used. Default is ``EK0`` which is the
        method proposed by Schober et al.. The available
        options are

        ================================================  ==============
         Extended Kalman filtering/smoothing (0th order)  ``'EK0'``
         Extended Kalman filtering/smoothing (1st order)  ``'EK1'``
        ================================================  ==============

        First order extended Kalman filtering and smoothing methods (``EK1``)
        require Jacobians of the RHS-vector field of the IVP.
        That is, the argument ``df`` needs to be specified.
        They are likely to perform better than zeroth order methods in
        terms of (A-)stability and "meaningful uncertainty estimates".
        While we recommend to use correct capitalization for the method string,
        lower-case letters will be capitalized internally.
    dense_output : bool
        Whether we want dense output. Optional. Default is ``True``. For the ODE filter,
        dense output requires smoothing, so if ``dense_output`` is False, no smoothing is performed;
        but when it is ``True``, the filter solution is smoothed.

    Returns
    -------
    solution : KalmanODESolution
        Solution of the ODE problem.

        Can be evaluated at and sampled from at arbitrary grid points.
        Further, it contains fields:

        t : :obj:`np.ndarray`, shape=(N,)
            Mesh used by the solver to compute the solution.
            It includes the initial time :math:`t_0` but not necessarily the
            final time :math:`T`.
        y : :obj:`list` of :obj:`RandomVariable`, length=N
            Discrete-time solution at times :math:`t_1, ..., t_N`,
            as a list of random variables.
            The means and covariances can be accessed with ``solution.y.mean``
            and ``solution.y.cov``.

    See Also
    --------
    GaussianIVPFilter : Solve IVPs with Gaussian filtering and smoothing
    KalmanODESolution : Solution of ODE problems based on Gaussian filtering and smoothing.

    References
    ----------
    .. [1] Schober, M., Särkkä, S. and Hennig, P..
        A probabilistic model for the numerical solution of initial
        value problems.
        Statistics and Computing, 2019.
    .. [2] Kersting, H., Sullivan, T.J., and Hennig, P..
        Convergence rates of Gaussian ODE filters.
        2019.
    .. [3] Tronarp, F., Kersting, H., Särkkä, S., and Hennig, P..
        Probabilistic solutions to ordinary differential equations as
        non-linear Bayesian filtering: a new perspective.
        Statistics and Computing, 2019.
    .. [4] Tronarp, F., Särkkä, S., and Hennig, P..
        Bayesian ODE solvers: the maximum a posteriori estimate.
        2019.


    Examples
    --------
    >>> from probnum.diffeq import logistic, probsolve_ivp
    >>> from probnum import randvars
    >>> import numpy as np

    Solve a simple logistic ODE with fixed steps.

    >>> def f(t, x):
    ...     return 4*x*(1-x)
    >>>
    >>> y0 = np.array([0.15])
    >>> t0, tmax = 0., 1.5
    >>> solution = probsolve_ivp(f, t0, tmax, y0, step=0.1, adaptive=False)
    >>> print(np.round(solution.y.mean, 2))
    [[0.15]
     [0.21]
     [0.28]
     [0.37]
     [0.47]
     [0.57]
     [0.66]
     [0.74]
     [0.81]
     [0.87]
     [0.91]
     [0.94]
     [0.96]
     [0.97]
     [0.98]
     [0.99]]


    Other methods are easily accessible.

    >>> def df(t, x):
    ...     return np.array([4. - 8 * x])
    >>> solution = probsolve_ivp(f, t0, tmax, y0, df=df, method="EK1", algo_order=2, step=0.1, adaptive=False)
    >>> print(np.round(solution.y.mean, 2))
        [[0.15]
     [0.21]
     [0.28]
     [0.37]
     [0.47]
     [0.57]
     [0.66]
     [0.74]
     [0.81]
     [0.87]
     [0.91]
     [0.93]
     [0.96]
     [0.97]
     [0.98]
     [0.99]]

    """

    # Create IVP object
    ivp = IVP(timespan=(t0, tmax),
              initrv=randvars.Constant(np.asarray(y0)),
              rhs=f,
              jac=df)

    # Create steprule
    if adaptive is True:
        if atol is None or rtol is None:
            raise ValueError(
                "Please provide absolute and relative tolerance for adaptive steps."
            )
        firststep = step if step is not None else steprule.propose_firststep(
            ivp)
        stprl = steprule.AdaptiveSteps(firststep=firststep,
                                       atol=atol,
                                       rtol=rtol)
    else:
        stprl = steprule.ConstantSteps(step)

    # Create solver
    prior = statespace.IBM(
        ordint=algo_order,
        spatialdim=ivp.dimension,
        forward_implementation="sqrt",
        backward_implementation="sqrt",
    )

    if method.upper() not in ["EK0", "EK1"]:
        raise ValueError("Method is not supported.")
    measmod = GaussianIVPFilter.string_to_measurement_model(method, ivp, prior)
    solver = GaussianIVPFilter.construct_with_rk_init(
        ivp, prior, measmod, with_smoothing=dense_output)

    return solver.solve(steprule=stprl)
コード例 #13
0
def prior(ivp):
    ode_dim = ivp.dimension
    prior = statespace.IBM(ordint=2, spatialdim=ode_dim)
    return prior
コード例 #14
0
ファイル: conftest.py プロジェクト: schmidtjonathan/probnum
def fixture_random_process(request) -> randprocs.RandomProcess:
    """Random process."""
    return request.param[1]


@pytest.fixture(name="gaussian_process")
def fixture_gaussian_process(mean, cov) -> randprocs.GaussianProcess:
    """Gaussian process."""
    return randprocs.GaussianProcess(mean=mean, cov=cov)


@pytest.fixture(
    params=[
        pytest.param(gmpdef, id=gmpdef[0]) for gmpdef in [(
            "ibm",
            statespace.IBM(ordint=1, spatialdim=1),
            0.0,
            randvars.Normal(0.0, 1.0),
        )]
    ],
    name="gauss_markov_process",
)
def fixture_gauss_markov_process(request) -> randprocs.MarkovProcess:
    """Gauss-Markov process."""
    return randprocs.MarkovProcess(transition=request.param[1],
                                   initarg=request.param[2],
                                   initrv=request.param[3])


@pytest.fixture(params=[pytest.param(n, id=f"n{n}") for n in [1, 10]],
                name="args0")