예제 #1
0
    def test_inputs(self):
        """Inputs rejected or accepted according to expected types."""
        numpy_array = np.ones(3) * Constant(0.1)
        constant_list = [Constant(0.1), Constant(0.4)]
        number_list = [0.5, 0.41]
        inputs = [numpy_array, constant_list, number_list]
        inputs_acceptable = [False, True, False]

        for inputs, is_acceptable in zip(inputs, inputs_acceptable):
            with self.subTest(input=inputs, is_acceptable=is_acceptable):

                if is_acceptable:
                    _RandomVariableList(inputs)
                else:
                    with self.assertRaises(TypeError):
                        _RandomVariableList(inputs)
    def __init__(self, scipy_solution: OdeSolution, rvs: list):
        self.scipy_solution = scipy_solution

        # rvs is of the type `list` of `RandomVariable` and can therefore be
        # directly transformed into a _RandomVariableList
        rv_states = _randomvariablelist._RandomVariableList(rvs)
        super().__init__(locations=scipy_solution.ts, states=rv_states)
예제 #3
0
    def __init__(self, kalman_posterior: filtsmooth.KalmanPosterior):
        self.kalman_posterior = kalman_posterior

        # Pre-compute projection matrices.
        # The prior must be an integrator, if not, an error is thrown in 'GaussianIVPFilter'.
        self.proj_to_y = self.kalman_posterior.transition.proj2coord(coord=0)
        self.proj_to_dy = self.kalman_posterior.transition.proj2coord(coord=1)

        states = _randomvariablelist._RandomVariableList(
            [_project_rv(self.proj_to_y, rv) for rv in self.kalman_posterior.states]
        )
        derivatives = _randomvariablelist._RandomVariableList(
            [_project_rv(self.proj_to_dy, rv) for rv in self.kalman_posterior.states]
        )
        super().__init__(
            locations=kalman_posterior.locations, states=states, derivatives=derivatives
        )
def approximate_solution():
    """List of normals with irrelevant values."""

    rvlist = [
        randvars.Normal(mean=i + np.arange(2, 4), cov=np.diag(np.arange(4, 6)))
        for i in range(10)
    ]

    return _randomvariablelist._RandomVariableList(rvlist)
예제 #5
0
 def __init__(
     self,
     locations: np.ndarray,
     states: _randomvariablelist._RandomVariableList,
     derivatives: Optional[_randomvariablelist._RandomVariableList] = None,
 ):
     super().__init__(locations=locations, states=states)
     self.derivatives = (
         _randomvariablelist._RandomVariableList(derivatives)
         if derivatives is not None else None)
예제 #6
0
    def __call__(
        self, t: float
    ) -> typing.Union[randvars.RandomVariable, pnrv_list._RandomVariableList]:
        out_rv = self.kalman_posterior(t)

        if np.isscalar(t):
            return _project_rv(self.proj_to_y, out_rv)

        return pnrv_list._RandomVariableList(
            [_project_rv(self.proj_to_y, rv) for rv in out_rv]
        )
예제 #7
0
    def _state_rvs(self):
        """
        :obj:`list` of :obj:`RandomVariable`:
        Time-discrete posterior estimates over states, without preconditioning.

        Note that this does not correspond to ``self._kalman_posterior.state_rvs``:
        Here we undo the preconditioning to make the "states" interpretable.
        """
        state_rvs = _RandomVariableList([
            self._solver.undo_preconditioning(rv)
            for rv in self._kalman_posterior
        ])
        return state_rvs
예제 #8
0
 def __getitem__(self, idx):
     """Access the discrete-time solution through indexing and slicing."""
     if isinstance(idx, int):
         rv = self._kalman_posterior[idx]
         rv = self._solver.undo_preconditioning(rv)
         rv = self._proj_normal_rv(rv, 0)
         return rv
     elif isinstance(idx, slice):
         rvs = self._kalman_posterior[idx]
         rvs = [self._solver.undo_preconditioning(rv) for rv in rvs]
         f_rvs = [self._proj_normal_rv(rv, 0) for rv in rvs]
         return _RandomVariableList(f_rvs)
     else:
         raise ValueError("Invalid index")
예제 #9
0
    def __init__(self, times, rvs, solver):

        # try-except is a hotfix for now:
        # future PR is to move KalmanPosterior-info out of here, into GaussianIVPFilter
        try:
            self._kalman_posterior = KalmanPosterior(times, rvs, solver.gfilt,
                                                     solver.with_smoothing)
            self._t = None
            self._y = None
        except AttributeError:
            self._kalman_posterior = None
            self._t = times
            self._y = _RandomVariableList(rvs)
        self._solver = solver
예제 #10
0
    def y(self):
        """
        :obj:`list` of :obj:`RandomVariable`: Probabilistic discrete-time solution

        Probabilistic discrete-time solution at times :math:`t_1, ..., t_N`,
        as a list of random variables.
        To return means and covariances use ``y.mean`` and ``y.cov``.
        """
        if self._y:  # hotfix
            return self._y
        else:
            function_rvs = [
                self._proj_normal_rv(rv, 0) for rv in self._state_rvs
            ]
            return _RandomVariableList(function_rvs)
예제 #11
0
    def __call__(self, t):
        """
        Evaluate the time-continuous posterior at location `t`

        Algorithm:
        1. Find closest t_prev and t_next, with t_prev < t < t_next
        2. Predict from t_prev to t
        3. (if `self._with_smoothing=True`) Predict from t to t_next
        4. (if `self._with_smoothing=True`) Smooth from t_next to t
        5. Return random variable for time t

        Parameters
        ----------
        t : float
            Location, or time, at which to evaluate the posterior.

        Returns
        -------
        :obj:`RandomVariable`
            Estimate of the states at time ``t``.
        """
        if not np.isscalar(t):
            # recursive evaluation (t can now be any array, not just length 1!)
            return _RandomVariableList([self.__call__(t_pt) for t_pt in np.asarray(t)])

        if t < self.locations[0]:
            raise ValueError(
                "Invalid location; Can not compute posterior for a location earlier "
                "than the initial location"
            )

        if t in self.locations:
            idx = (self.locations <= t).sum() - 1
            discrete_estimate = self.state_rvs[idx]
            return discrete_estimate

        if self.locations[0] < t < self.locations[-1]:
            pred_rv = self._predict_to_loc(t)
            if self._with_smoothing:
                smoothed_rv = self._smooth_prediction(pred_rv, t)
                return smoothed_rv
            else:
                return pred_rv

        # else: t > self.locations[-1]:
        if self._with_smoothing:
            warn("`smoothed=True` is ignored for extrapolation.")
        return self._predict_to_loc(t)
예제 #12
0
    def smooth_list(self, rv_list, locations, final_rv=None, **kwargs):
        """
        Apply smoothing to a list of RVs with desired final random variable.

        Specification of a final RV is useful to compute joint samples from a KalmanPosterior object,
        because in this case, the final RV is a Dirac (over a sample from the final Normal RV)
        and not a Normal RV.

        Parameters
        ----------
        rv_list : _RandomVariableList or array_like
            List of random variables to be smoothed.
        locations : array_like
            Locations of the random variables in rv_list.
        final_rv : RandomVariable, optional.
            RandomVariable at the final point. Default is None, in which case standard smoothing is applied.
            If a random variable is specified, the smoothing iteration is based on this one, which is used
            for sampling (in which case the final random variable is a Dirac that represents a sample)

        Returns
        -------
        _RandomVariableList
            List of smoothed random variables.
        """
        if final_rv is None:
            final_rv = rv_list[-1]
        curr_rv = final_rv
        out_rvs = [curr_rv]
        for idx in reversed(range(1, len(locations))):
            unsmoothed_rv = rv_list[idx - 1]
            pred_rv, info = self.predict(
                start=locations[idx - 1],
                stop=locations[idx],
                randvar=unsmoothed_rv,
                **kwargs
            )
            if "crosscov" not in info.keys():
                raise TypeError("Cross-covariance required for smoothing.")
            curr_rv = self.smooth_step(
                unsmoothed_rv, pred_rv, curr_rv, info["crosscov"]
            )
            out_rvs.append(curr_rv)
        out_rvs.reverse()
        return _RandomVariableList(out_rvs)
예제 #13
0
    def smooth_list(self, rv_list, locations, _previous_posterior=None):
        """Apply smoothing to a list of random variables, according to the present
        transition.

        Parameters
        ----------
        rv_list : _randomvariablelist._RandomVariableList
            List of random variables to be smoothed.
        locations :
            Locations :math:`t` of the random variables in the time-domain. Used for continuous-time transitions.
        _previous_posterior
            Specify a previous posterior to improve linearisation in approximate backward passes.
            Used in iterated smoothing based on posterior linearisation.

        Returns
        -------
        _randomvariablelist._RandomVariableList
            List of smoothed random variables.
        """

        final_rv = rv_list[-1]
        curr_rv = final_rv
        out_rvs = [curr_rv]
        for idx in reversed(range(1, len(locations))):
            unsmoothed_rv = rv_list[idx - 1]

            _linearise_smooth_step_at = (None if _previous_posterior is None
                                         else _previous_posterior(
                                             locations[idx - 1]))

            # Actual smoothing step
            curr_rv, _ = self.backward_rv(
                curr_rv,
                unsmoothed_rv,
                t=locations[idx - 1],
                dt=locations[idx] - locations[idx - 1],
                _linearise_at=_linearise_smooth_step_at,
            )
            out_rvs.append(curr_rv)
        out_rvs.reverse()
        return _randomvariablelist._RandomVariableList(out_rvs)
    def __call__(self, t: DenseOutputLocationArgType) -> DenseOutputValueType:
        """Evaluate the time-continuous solution at time t.

        Parameters
        ----------
        t
        Location / time at which to evaluate the continuous ODE solution.

        Returns
        -------
        randvars.RandomVariable or _randomvariablelist._RandomVariableList
            Estimate of the states at time ``t`` based on a fourth order polynomial.
        """

        states = self.scipy_solution(t).T
        if np.isscalar(t):
            solution_as_rv = randvars.Constant(states)
        else:
            solution_as_rv = _randomvariablelist._RandomVariableList(
                [randvars.Constant(state) for state in states])
        return solution_as_rv
예제 #15
0
    def __call__(self, t):
        """Evaluate the time-continuous posterior at location `t`

        Algorithm:
        1. Find closest t_prev and t_next, with t_prev < t < t_next
        2. Predict from t_prev to t
        3. (if `self._with_smoothing=True`) Predict from t to t_next
        4. (if `self._with_smoothing=True`) Smooth from t_next to t
        5. Return random variable for time t

        Parameters
        ----------
        t : float
            Location, or time, at which to evaluate the posterior.

        Returns
        -------
        :obj:`RandomVariable`
            Estimate of the states at time ``t``.
        """

        # Recursive evaluation (t can now be any array, not just length 1)
        if not np.isscalar(t):
            return _RandomVariableList([self.__call__(t_pt) for t_pt in t])

        # t is left of our grid -- raise error
        # (this functionality is not supported yet)
        if t < self.locations[0]:
            raise ValueError(
                "Invalid location; Can not compute posterior for a location earlier "
                "than the initial location"
            )

        # Early exit if t is in our grid -- no need to interpolate
        if t in self.locations:
            idx = self._find_index(t)
            discrete_estimate = self.state_rvs[idx]
            return discrete_estimate

        return self.interpolate(t)
예제 #16
0
    def __call__(self, t):
        """
        Evaluate the time-continuous solution at time t.

        `KalmanPosterior.__call__` does the main algorithmic work to return the
        posterior for a given location. All that is left to do here is to (1) undo the
        preconditioning, and (2) to slice the state_rv in order to return only the
        rv for the function value.

        Parameters
        ----------
        t : float
            Location / time at which to evaluate the continuous ODE solution.

        Returns
        -------
        :obj:`RandomVariable`
            Probabilistic estimate of the continuous-time solution at time ``t``.
        """
        out_rv = self._kalman_posterior(t)
        if np.isscalar(t):
            out_rv = self._solver.undo_preconditioning(out_rv)
            return self._proj_normal_rv(out_rv, 0)
        return _RandomVariableList(self._project_rv_list(out_rv))
예제 #17
0
 def y(self) -> pnrv_list._RandomVariableList:
     y_rvs = [
         _project_rv(self.proj_to_y, rv) for rv in self.kalman_posterior.state_rvs
     ]
     return pnrv_list._RandomVariableList(y_rvs)
예제 #18
0
 def __init__(self, locations, state_rvs, gauss_filter, with_smoothing):
     self._locations = np.asarray(locations)
     self.gauss_filter = gauss_filter
     self._state_rvs = _RandomVariableList(state_rvs)
     self._with_smoothing = with_smoothing
예제 #19
0
 def setUp(self):
     self.rv_list = _RandomVariableList([Constant(0.1), Constant(0.2)])
예제 #20
0
 def setUp(self):
     self.rv_list = _RandomVariableList([])
예제 #21
0
 def __init__(self, locations: np.ndarray, states: np.ndarray) -> None:
     self.locations = np.asarray(locations)
     self.states = _randomvariablelist._RandomVariableList(states)
예제 #22
0
    def __call__(self, t: DenseOutputLocationArgType) -> DenseOutputValueType:
        """Evaluate the time-continuous posterior at location `t`

        Algorithm:
        1. Find closest t_prev and t_next, with t_prev < t < t_next
        2. Predict from t_prev to t
        3. (if `self._with_smoothing=True`) Predict from t to t_next
        4. (if `self._with_smoothing=True`) Smooth from t_next to t
        5. Return random variable for time t

        Parameters
        ----------
        t :
            Location, or time, at which to evaluate the posterior.

        Returns
        -------
        randvars.RandomVariable or _randomvariablelist._RandomVariableList
            Estimate of the states at time ``t``.
        """
        # The variable "squeeze_eventually" indicates whether
        # the dimension of the time-array has been promoted
        # (which is there for a well-behaved loop).
        # If this has been the case, the final result needs to be
        # reshaped ("squeezed") accordingly.
        if np.isscalar(t):
            t = np.atleast_1d(t)
            squeeze_eventually = True
        else:
            squeeze_eventually = False

        if not np.all(np.diff(t) >= 0.0):
            raise ValueError("Time-points have to be sorted.")

        # Split left-extrapolation, interpolation, right_extrapolation
        t0, tmax = np.amin(self.locations), np.amax(self.locations)
        t_extra_left = t[t < t0]
        t_extra_right = t[t > tmax]
        t_inter = t[(t0 <= t) & (t <= tmax)]

        # Indices of t where they would be inserted
        # into self.locations ("left": right-closest states)
        indices = np.searchsorted(self.locations, t_inter, side="left")
        interpolated_values = [
            self.interpolate(
                t=ti,
                previous_location=prevloc,
                previous_state=prevstate,
                next_location=nextloc,
                next_state=nextstate,
            ) for ti, prevloc, prevstate, nextloc, nextstate in zip(
                t_inter,
                self.locations[indices - 1],
                self._states_left_of_location[indices - 1],
                self.locations[indices],
                self._states_right_of_location[indices],
            )
        ]
        extrapolated_values_left = [
            self.interpolate(
                t=ti,
                previous_location=None,
                previous_state=None,
                next_location=t0,
                next_state=self._states_right_of_location[0],
            ) for ti in t_extra_left
        ]

        extrapolated_values_right = [
            self.interpolate(
                t=ti,
                previous_location=tmax,
                previous_state=self._states_left_of_location[-1],
                next_location=None,
                next_state=None,
            ) for ti in t_extra_right
        ]
        dense_output_values = extrapolated_values_left
        dense_output_values.extend(interpolated_values)
        dense_output_values.extend(extrapolated_values_right)

        if squeeze_eventually:
            return dense_output_values[0]
        return _randomvariablelist._RandomVariableList(dense_output_values)
예제 #23
0
 def setUp(self):
     self.rv_list = _RandomVariableList([Dirac(0.1), Dirac(0.2)])
예제 #24
0
 def dy(self):
     """
     :obj:`list` of :obj:`RandomVariable`: Derivatives of the discrete-time solution
     """
     dy_rvs = [self._proj_normal_rv(rv, 1) for rv in self._state_rvs]
     return _RandomVariableList(dy_rvs)
예제 #25
0
    def __init__(self, locations, state_rvs, transition):

        self._locations = np.asarray(locations)
        self.transition = transition
        self._state_rvs = _RandomVariableList(state_rvs)