Source code for smolgp.solvers.parallel.solver

from __future__ import annotations

from typing import Any

import equinox as eqx
import jax
import jax.numpy as jnp
from tinygp.helpers import JAXArray
from tinygp.solvers.quasisep.solver import QuasisepSolver

from smolgp.kernels.base import StateSpaceModel
from smolgp.solvers.solver import StateSpaceSolver
from smolgp.solvers.parallel.kalman import ParallelKalmanFilter
from smolgp.solvers.parallel.rts import ParallelRTSSmoother
from smolgp.solvers.rts import rts_gains
from smolgp.solvers.state_coords import StateCoords


[docs] class ParallelStateSpaceSolver(StateSpaceSolver): """ A solver that uses ``jax.lax.associative_scan`` to implement parallel Kalman filtering and RTS smoothing. Inherits from :class:`StateSpaceSolver` and overrides the Kalman and RTS methods to use the parallel implementations. Methods which do not benefit from associative scans are inherited from :class:`StateSpaceSolver`. """
[docs] def Kalman(self, y, return_v_S=True) -> Any: """Wrapper for Kalman filter used with this solver""" # noise (N, D, D) → R (N, D, D); y (..., N) → (N, D) y_nd = y[:, None] if y.ndim == 1 else y X_sorted, y_sorted, noise_sorted = self._to_state_order( self.X, y_nd, self.noise ) return ParallelKalmanFilter( self.kernel, X_sorted, y_sorted, noise_sorted, return_v_S=return_v_S )
[docs] def log_probability(self, y) -> JAXArray: """The marginal log likelihood, reduced from this solver's own filter. Overrides :meth:`StateSpaceSolver.log_probability` deliberately, as that is an optimized *sequential* scan. The generic path to reuse the Kalman filtered ``v`` and ``S`` is better here, as those are determined via associative scan, hence the likelihood stays log-depth. """ return self._log_probability_from_filter(y)
[docs] def RTS(self, kalman_results) -> Any: """Wrapper for RTS smoother used with this solver""" (X_sorted,) = self._to_state_order(self.X) return ParallelRTSSmoother(self.kernel, X_sorted, kalman_results)
[docs] def condition(self, y, return_v_S=False) -> JAXArray: """ Compute the Kalman predicted, filtered, and RTS smoothed means and covariances at each of the input coordinates """ # Kalman filtering kalman_results = self.Kalman(y, return_v_S=return_v_S) if return_v_S: m_filtered, P_filtered, m_predicted, P_predicted, v, S = kalman_results v_S = (v, S) else: m_filtered, P_filtered, m_predicted, P_predicted = kalman_results v_S = None # RTS smoothing rts_results = self.RTS((m_filtered, P_filtered)) _, m_smoothed, P_smoothed = rts_results # Pack-up results and return conditioned_states = ( (m_predicted, P_predicted), (m_filtered, P_filtered), (m_smoothed, P_smoothed), ) return self.state_coords, conditioned_states, v_S