Example #1
0
class SigmaPointIplf(Filter):
    """Iterated posterior linearisation filter (IPLF)"""
    def __init__(self, motion_model, meas_model, sigma_point_method):
        self._motion_model = motion_model
        self._meas_model = meas_model
        self._slr = SigmaPointSlr(sigma_point_method)
        self._current_means = None
        self._current_covs = None
        self._cache = SlrCache(self._motion_model.map_set,
                               self._meas_model.map_set, self._slr)

    def _update_estimates(self, means, covs, cache=None):
        self._current_means = means.copy()
        self._current_covs = covs.copy()
        if cache is None:
            self._update_slr_cache()
        else:
            self._cache = cache

    def _update_slr_cache(self):
        self._cache.update(self._current_means, self._current_covs)

    def _motion_lin(self, _mean, _cov, time_step):
        return self._cache.proc_lin[time_step - 1]

    def _meas_lin(self, _mean, _cov, time_step):
        return self._cache.meas_lin[time_step - 1]

    def _proc_noise(self, time_step):
        return self._motion_model.proc_noise(time_step)

    def _meas_noise(self, time_step):
        return self._meas_model.meas_noise(time_step)
Example #2
0
def check_cost(estimates, cost_fn_prototype, motion_model, meas_model,
               slr_method):
    costs = []
    for ms, Ps, label in estimates:
        cache = SlrCache(motion_model.map_set, meas_model.map_set, slr_method)
        cache.update(ms, Ps)
        cost_fn = partial(
            cost_fn_prototype,
            motion_bar=cache.proc_bar,
            meas_bar=cache.meas_bar,
            motion_cov=[
                lin[2] + motion_model.proc_noise(k)
                for k, lin in enumerate(cache.proc_lin, 1)
            ],
            meas_cov=[
                lin[2] + meas_model.meas_noise(k)
                for k, lin in enumerate(cache.meas_lin, 1)
            ],
        )
        costs.append[(cost_fn(ms), label)]
Example #3
0
class SigmaPointLsIpls(IteratedSmoother):
    """Line-search Iterated Posterior Linearisation Smoother (LS-IPLS)"""
    def __init__(self, motion_model, meas_model, sigma_point_method, num_iter,
                 line_search_method, num_points):
        super().__init__()
        self._motion_model = motion_model
        self._meas_model = meas_model
        self._slr = SigmaPointSlr(sigma_point_method)
        self._sigma_point_method = sigma_point_method
        self.num_iter = num_iter
        self._ls_method = line_search_method
        self._cache = SlrCache(self._motion_model, self._meas_model, self._slr)

    def _motion_lin(self, _mean, _cov, time_step):
        return self._cache.proc_lin[time_step - 1]

    def _first_iter(self, measurements, m_1_0, P_1_0, cost_fn_prototype):
        smoother = SigmaPointPrLs(self._motion_model, self._meas_model,
                                  self._sigma_point_method)
        # Hack to use the generic `filter_and_smooth` method
        # The cost function prototype (variable covs) cannot be specialised until the `smooth_covs` are known
        # but the cost is calculated within the function (where it must be specialised).
        # Solution: - Use 'None' in the `filter_and_smooth` method => conforms to the base API.
        #           - Calculate the actual cost here, when the smooth_covs are available.
        filter_means, filter_covs, smooth_means, smooth_covs, _ = smoother.filter_and_smooth(
            measurements, m_1_0, P_1_0, None)
        self._update_estimates(smooth_means, smooth_covs)
        cost_fn = self._specialise_cost_fn(
            cost_fn_prototype, (smooth_covs, self._cache.inv_cov()))
        cost = cost_fn(smooth_means)
        return filter_means, filter_covs, smooth_means, smooth_covs, cost

    def filter_and_smooth_with_init_traj(self, measurements, m_1_0, P_1_0,
                                         init_traj, start_iter,
                                         cost_fn_prototype):
        """Filter and smoothing given an initial trajectory"""
        current_ms, current_Ps = init_traj
        # If self.num_iter is too low to enter the iter loop
        mf, Pf = init_traj
        cost_iter = []
        if not self._is_initialised():
            self._update_estimates(current_ms, current_Ps)
        cost_fn = self._specialise_cost_fn(
            cost_fn_prototype, (self._current_covs, self._cache.inv_cov()))
        # Temporary fix which assumes that we use inexact linesearch, i.e. it breaks GridSearch.
        dir_der_prototype = partial(
            dir_der_slr_smoothing_cost,
            measurements=measurements,
            m_1_0=m_1_0,
            P_1_0=P_1_0,
            motion_fn=self._motion_model.map_set,
            meas_fn=self._meas_model.map_set,
            slr_method=self._slr,
        )
        dir_der = self._specialise_dir_der(
            dir_der_prototype, (self._current_covs, self._cache.inv_cov()))
        prev_cost = cost_fn(current_ms)
        for iter_ in range(start_iter, self.num_iter + 1):
            self._log.debug(f"Iter: {iter_}")
            cost_iter.append(prev_cost)
            num_iter_with_same_cost = 1
            while self._terminate_inner_loop(num_iter_with_same_cost):
                num_iter_with_same_cost += 1
                # Note: here we want to run the base `Smoother` class method.
                # I.e. we're getting the grandparent's method.
                mf, Pf, current_ms, current_Ps, cost = super(
                    IteratedSmoother,
                    self).filter_and_smooth(measurements, m_1_0, P_1_0,
                                            cost_fn)
                line_search = self._ls_method(cost_fn, dir_der)
                ls_ms, alpha, ls_cost = line_search.search_next(
                    self._current_means, current_ms)
                if ls_cost > cost:
                    self._log.warning(
                        f"Line search did not decrease, defaulting to plain IPLS."
                    )
                    self._update_means_only(current_ms, None)
                    # Stored for later update.
                    Ps = self._current_covs
                    prev_cost = cost
                else:
                    self._update_means_only(ls_ms, None)
                    # Stored for later update.
                    Ps = line_search.next_estimate(self._current_covs,
                                                   current_Ps, alpha)
                    prev_cost = ls_cost
                # Update cost function with new covariances.
                self._update_estimates(self._current_means, Ps)
                cost_fn = self._specialise_cost_fn(
                    cost_fn_prototype,
                    (self._current_covs, self._cache.inv_cov()))
                dir_der = self._specialise_dir_der(
                    dir_der_prototype,
                    (self._current_covs, self._cache.inv_cov()))
            self._log.debug(f"Cost: {cost}, alpha: {alpha}")
            cost_fn = self._specialise_cost_fn(
                cost_fn_prototype, (self._current_covs, self._cache.inv_cov()))
        return mf, Pf, current_ms, current_Ps, np.array(cost_iter)

    def _filter_seq(self, measurements, m_1_0, P_1_0):
        iplf = SigmaPointIplf(self._motion_model, self._meas_model,
                              self._sigma_point_method)
        iplf._update_estimates(self._current_means, self._current_covs,
                               self._cache)
        return iplf.filter_seq(measurements, m_1_0, P_1_0)

    # traj, measurements, m_1_0, P_1_0, estimated_covs, motion_fn, meas_fn, motion_cov, meas_cov, slr_method
    def _specialise_cost_fn(self, cost_fn_prototype, params):
        (
            estimated_covs,
            (
                motion_cov_inv,
                meas_cov_inv,
            ),
        ) = params
        return partial(
            cost_fn_prototype,
            estimated_covs=estimated_covs,
            motion_cov_inv=motion_cov_inv,
            meas_cov_inv=meas_cov_inv,
        )

    def _specialise_dir_der(self, dir_der_prototype, params):
        (
            estimated_covs,
            (
                motion_cov_inv,
                meas_cov_inv,
            ),
        ) = params
        return partial(
            dir_der_prototype,
            estimated_covs=estimated_covs,
            motion_cov_inv=motion_cov_inv,
            meas_cov_inv=meas_cov_inv,
        )

    def _is_initialised(self):
        return self._cache.is_initialized(
        ) and self._current_means is not None and self._current_covs is not None

    def _cost_fn_params(self):
        return self._current_covs

    def _terminate_inner_loop(self, inner_loop_iter):
        return inner_loop_iter < 2

    def _update_means_only(self, means, pre_comp_cache=None):
        self._current_means = means.copy()
        if pre_comp_cache is None:
            self._cache.update(self._current_means, self._current_covs)
        else:
            self._cache = pre_comp_cache

    def _update_estimates(self, means, covs):
        """The 'previous estimates' which are used in the current iteration are stored in the smoother instance.
        They should only be modified through this method.
        """
        super()._update_estimates(means, covs)
        self._cache.update(self._current_means, self._current_covs)
Example #4
0
class SigmaPointIpls(IteratedSmoother):
    """Iterated posterior linearisation filter"""
    def __init__(self, motion_model, meas_model, sigma_point_method, num_iter):
        super().__init__()
        self._motion_model = motion_model
        self._meas_model = meas_model
        self._slr = SigmaPointSlr(sigma_point_method)
        self._sigma_point_method = sigma_point_method
        self.num_iter = num_iter
        self._cache = SlrCache(self._motion_model, self._meas_model, self._slr)

    def _motion_lin(self, _mean, _cov, time_step):
        return self._cache.proc_lin[time_step - 1]

    def _first_iter(self, measurements, m_1_0, P_1_0, cost_fn_prototype):
        smoother = SigmaPointPrLs(self._motion_model, self._meas_model,
                                  self._sigma_point_method)
        mf, Pf, ms, Ps, _ = smoother.filter_and_smooth(measurements, m_1_0,
                                                       P_1_0, None)
        self._update_estimates(ms, Ps)
        if cost_fn_prototype is not None:
            cost_fn = self._specialise_cost_fn(cost_fn_prototype,
                                               self._cost_fn_params())
            cost = cost_fn(ms)
        else:
            cost = None
        return mf, Pf, ms, Ps, cost

    def _filter_seq(self, measurements, m_1_0, P_1_0):
        iplf = SigmaPointIplf(self._motion_model, self._meas_model,
                              self._sigma_point_method)
        iplf._update_estimates(self._current_means, self._current_covs,
                               self._cache)
        return iplf.filter_seq(measurements, m_1_0, P_1_0)

    def _specialise_cost_fn(self, cost_fn_prototype, params):
        if cost_fn_prototype is not None:
            (
                (motion_bar, meas_bar),
                (
                    motion_cov_inv,
                    meas_cov_inv,
                ),
            ) = params
            return partial(
                cost_fn_prototype,
                motion_bar=motion_bar,
                meas_bar=meas_bar,
                motion_cov_inv=motion_cov_inv,
                meas_cov_inv=meas_cov_inv,
            )
        else:
            return None

    def _cost_fn_params(self):
        return self._cache.bars(), self._cache.error_covs()

    def _update_estimates(self, means, covs):
        """The 'previous estimates' which are used in the current iteration are stored in the smoother instance.
        They should only be modified through this method.
        """
        super()._update_estimates(means, covs)
        self._cache.update(self._current_means, self._current_covs)

    def _is_initialised(self):
        return self._cache.is_initialized(
        ) and self._current_means is not None and self._current_covs is not None
Example #5
0
    def filter_and_smooth_with_init_traj(self, measurements, m_1_0, P_1_0,
                                         init_traj, start_iter,
                                         cost_fn_prototype):
        """Filter and smoothing given an initial trajectory"""
        current_ms, current_Ps = init_traj
        # If self.num_iter is too low to enter the iter loop
        mf, Pf = init_traj
        cost_iter = []
        # Optimisation to only update the estimates when the estimates have changed.
        # This method can be called either as part of the filter_and_smooth method,
        # then the estimates are already updated in the _first_iter method,
        # Or it can be called directly with an init_traj, then the update is needed
        if not self._is_initialised():
            self._update_estimates(current_ms, current_Ps)
        cost_fn = self._specialise_cost_fn(
            cost_fn_prototype, (self._cache.bars(), self._cache.inv_cov()))
        prev_cost = cost_fn(current_ms)
        for iter_ in range(start_iter, self.num_iter + 1):
            self._log.debug(f"Iter: {iter_}")

            loss_cand_no = 1
            has_improved = False

            # TODO: Impl inner loop for multiple opt of same cost fn.
            while has_improved is False and loss_cand_no <= self._cost_improv_iter_lim:
                # Note: here we want to run the base `Smoother` class method.
                # I.e. we're getting the grandparent's method.
                mf, Pf, current_ms, current_Ps, cost = super(
                    IteratedSmoother,
                    self).filter_and_smooth(measurements, m_1_0, P_1_0, None)

                # Create a temporary cache. Linearisation is still done with self._cache until the present iterate
                # is accepted. The cost is calculated with a hybrid: proc_bar, meas_bar from the tmp_cache but
                # linearisation err. covariance from self._cache.
                tmp_cache = SlrCache(self._motion_model, self._meas_model,
                                     self._slr)
                # TODO: Since we only care about the bars here, we could potentially optimise and only calc. the
                # bars and skip the linearisation.
                # This would only be faster if the iterate is rejected since we still do the remaining calc.
                # (the linearisation) when the iterate is accepted.
                tmp_cache.update(
                    current_ms,
                    self._current_covs,
                )
                # Note: here we take the new bars but the old error_covs.
                tmp_cost_fn = self._specialise_cost_fn(
                    cost_fn_prototype,
                    (tmp_cache.bars(), self._cache.inv_cov()))
                cost = tmp_cost_fn(current_ms)
                self._log.debug(
                    f"Cost: {cost}, lambda: {self._lambda}, loss_cand_no: {loss_cand_no}"
                )
                if not self._lambda > 0.0:
                    self._log.info("lambda=0, skipping cost check")
                    has_improved = True
                elif cost < prev_cost:
                    self._lambda /= self._nu
                    has_improved = True
                else:
                    self._lambda *= self._nu
                loss_cand_no += 1
            if loss_cand_no == self._cost_improv_iter_lim + 1:
                self._log.info(
                    f"No cost improvement for {self._cost_improv_iter_lim} iterations, returning"
                )
                # Update to get the most recent estimates into the stored_estimates
                self._update_estimates(self._current_means, self._current_covs)
                return mf, Pf, self._current_means, self._current_covs, np.array(
                    cost_iter)

            # Full update, updating means and covs estimates.
            # Which also requires an update of the cost fn.
            self._update_estimates(current_ms, current_Ps)
            cost_fn = self._specialise_cost_fn(
                cost_fn_prototype, (self._cache.bars(), self._cache.inv_cov()))
            prev_cost = cost_fn(current_ms)
            cost_iter.append(prev_cost)
        return mf, Pf, current_ms, current_Ps, np.array(cost_iter)
Example #6
0
class SigmaPointLmIpls(IteratedSmoother):
    """Levenberg-Marquardt regularised Iterated Posterior Linearisation Smoother (LM-IPLS)"""
    def __init__(self, motion_model, meas_model, sigma_point_method, num_iter,
                 cost_improv_iter_lim, lambda_, nu):
        super().__init__()
        self._motion_model = motion_model
        self._meas_model = meas_model
        self._slr = SigmaPointSlr(sigma_point_method)
        self._sigma_point_method = sigma_point_method
        self.num_iter = num_iter
        self._cost_improv_iter_lim = cost_improv_iter_lim
        self._lambda = lambda_
        self._nu = nu
        self._cache = SlrCache(self._motion_model, self._meas_model, self._slr)

    def _motion_lin(self, _mean, _cov, time_step):
        return self._cache.proc_lin[time_step - 1]

    # TODO: This should also have inner LM check
    def _first_iter(self, measurements, m_1_0, P_1_0, cost_fn_prototype):
        smoother = SigmaPointPrLs(self._motion_model, self._meas_model,
                                  self._sigma_point_method)
        # Hack to use the generic `filter_and_smooth` method
        # The cost function prototype (variable covs) cannot be specialised until the `smooth_covs` are known
        # but the cost is calculated within the function (where it must be specialised).
        # Solution: - Use 'None' in the `filter_and_smooth` method => conforms to the base API.
        #           - Calculate the actual cost here, when the smooth_covs are available.
        filter_means, filter_covs, smooth_means, smooth_covs, _ = smoother.filter_and_smooth(
            measurements, m_1_0, P_1_0, None)
        self._update_estimates(smooth_means, smooth_covs)
        cost_fn = self._specialise_cost_fn(
            cost_fn_prototype, (self._cache.bars(), self._cache.inv_cov()))
        cost = cost_fn(smooth_means)
        return filter_means, filter_covs, smooth_means, smooth_covs, cost

    def filter_and_smooth_with_init_traj(self, measurements, m_1_0, P_1_0,
                                         init_traj, start_iter,
                                         cost_fn_prototype):
        """Filter and smoothing given an initial trajectory"""
        current_ms, current_Ps = init_traj
        # If self.num_iter is too low to enter the iter loop
        mf, Pf = init_traj
        cost_iter = []
        # Optimisation to only update the estimates when the estimates have changed.
        # This method can be called either as part of the filter_and_smooth method,
        # then the estimates are already updated in the _first_iter method,
        # Or it can be called directly with an init_traj, then the update is needed
        if not self._is_initialised():
            self._update_estimates(current_ms, current_Ps)
        cost_fn = self._specialise_cost_fn(
            cost_fn_prototype, (self._cache.bars(), self._cache.inv_cov()))
        prev_cost = cost_fn(current_ms)
        for iter_ in range(start_iter, self.num_iter + 1):
            self._log.debug(f"Iter: {iter_}")

            loss_cand_no = 1
            has_improved = False

            # TODO: Impl inner loop for multiple opt of same cost fn.
            while has_improved is False and loss_cand_no <= self._cost_improv_iter_lim:
                # Note: here we want to run the base `Smoother` class method.
                # I.e. we're getting the grandparent's method.
                mf, Pf, current_ms, current_Ps, cost = super(
                    IteratedSmoother,
                    self).filter_and_smooth(measurements, m_1_0, P_1_0, None)

                # Create a temporary cache. Linearisation is still done with self._cache until the present iterate
                # is accepted. The cost is calculated with a hybrid: proc_bar, meas_bar from the tmp_cache but
                # linearisation err. covariance from self._cache.
                tmp_cache = SlrCache(self._motion_model, self._meas_model,
                                     self._slr)
                # TODO: Since we only care about the bars here, we could potentially optimise and only calc. the
                # bars and skip the linearisation.
                # This would only be faster if the iterate is rejected since we still do the remaining calc.
                # (the linearisation) when the iterate is accepted.
                tmp_cache.update(
                    current_ms,
                    self._current_covs,
                )
                # Note: here we take the new bars but the old error_covs.
                tmp_cost_fn = self._specialise_cost_fn(
                    cost_fn_prototype,
                    (tmp_cache.bars(), self._cache.inv_cov()))
                cost = tmp_cost_fn(current_ms)
                self._log.debug(
                    f"Cost: {cost}, lambda: {self._lambda}, loss_cand_no: {loss_cand_no}"
                )
                if not self._lambda > 0.0:
                    self._log.info("lambda=0, skipping cost check")
                    has_improved = True
                elif cost < prev_cost:
                    self._lambda /= self._nu
                    has_improved = True
                else:
                    self._lambda *= self._nu
                loss_cand_no += 1
            if loss_cand_no == self._cost_improv_iter_lim + 1:
                self._log.info(
                    f"No cost improvement for {self._cost_improv_iter_lim} iterations, returning"
                )
                # Update to get the most recent estimates into the stored_estimates
                self._update_estimates(self._current_means, self._current_covs)
                return mf, Pf, self._current_means, self._current_covs, np.array(
                    cost_iter)

            # Full update, updating means and covs estimates.
            # Which also requires an update of the cost fn.
            self._update_estimates(current_ms, current_Ps)
            cost_fn = self._specialise_cost_fn(
                cost_fn_prototype, (self._cache.bars(), self._cache.inv_cov()))
            prev_cost = cost_fn(current_ms)
            cost_iter.append(prev_cost)
        return mf, Pf, current_ms, current_Ps, np.array(cost_iter)

    def _filter_seq(self, measurements, m_1_0, P_1_0):
        lm_iplf = _LmIplf(self._motion_model, self._meas_model,
                          self._sigma_point_method, self._lambda)
        lm_iplf._update_estimates(self._current_means, self._current_covs,
                                  self._cache)
        return lm_iplf.filter_seq(measurements, m_1_0, P_1_0)

    def _specialise_cost_fn(self, cost_fn_prototype, params):
        (
            (motion_bar, meas_bar),
            (
                motion_cov_inv,
                meas_cov_inv,
            ),
        ) = params
        return partial(
            cost_fn_prototype,
            motion_bar=motion_bar,
            meas_bar=meas_bar,
            motion_cov_inv=motion_cov_inv,
            meas_cov_inv=meas_cov_inv,
        )

    def _is_initialised(self):
        return self._cache.is_initialized(
        ) and self._current_means is not None and self._current_covs is not None

    def _cost_fn_params(self):
        return self._current_covs

    def _terminate_inner_loop(self, loss_cand_no):
        return loss_cand_no > 1

    def _update_means_only(self, means, pre_comp_cache=None):
        self._current_means = means.copy()
        if pre_comp_cache is None:
            self._cache.update(self._current_means, self._current_covs)
        else:
            self._cache = pre_comp_cache

    def _update_estimates(self, means, covs):
        """The 'previous estimates' which are used in the current iteration are stored in the smoother instance.
        They should only be modified through this method.
        """
        super()._update_estimates(means, covs)
        self._cache.update(self._current_means, self._current_covs)
def main():
    dt = 0.01
    qc = 0.01
    qw = 10
    Q = np.array(
        [
            [qc * dt ** 3 / 3, 0, qc * dt ** 2 / 2, 0, 0],
            [0, qc * dt ** 3 / 3, 0, qc * dt ** 2 / 2, 0],
            [qc * dt ** 2 / 2, 0, qc * dt, 0, 0],
            [0, qc * dt ** 2 / 2, 0, qc * dt, 0],
            [0, 0, 0, 0, dt * qw],
        ]
    )
    motion_model = CoordTurn(dt, Q)

    sens_pos_1 = np.array([-1.5, 0.5])
    sens_pos_2 = np.array([1, 1])
    sensors = np.row_stack((sens_pos_1, sens_pos_2))
    std = 0.5
    R = std ** 2 * np.eye(2)
    meas_model = MultiSensorRange(sensors, R)

    prior_mean = np.array([0, 0, 1, 0, 0])
    prior_cov = np.diag([0.1, 0.1, 1, 1, 1])

    _, measurements, _, ss_ms = get_specific_states_from_file(Path.cwd() / "data/lm_ieks_paper", Type.GN, 10)
    measurements = measurements[:, :2]
    K = measurements.shape[0]
    covs = np.array([prior_cov] * K)
    slr_cache = SlrCache(motion_model.map_set, meas_model.map_set, SigmaPointSlr(SphericalCubature()))
    slr_cache.update(ss_ms, covs)
    new_proto = partial(
        slr_smoothing_cost_pre_comp,
        measurements=measurements,
        m_1_0=prior_mean,
        P_1_0=prior_cov,
    )
    new = partial(
        new_proto,
        traj=ss_ms,
        proc_bar=slr_cache.proc_bar,
        meas_bar=slr_cache.meas_bar,
        proc_cov=np.array(
            [err_cov_k + motion_model.proc_noise(k) for k, (_, _, err_cov_k) in enumerate(slr_cache.proc_lin)]
        ),
        meas_cov=np.array(
            [err_cov_k + meas_model.meas_noise(k) for k, (_, _, err_cov_k) in enumerate(slr_cache.meas_lin)]
        ),
    )
    old = partial(
        slr_smoothing_cost,
        ss_ms,
        covs,
        measurements,
        prior_mean,
        prior_cov,
        motion_model,
        meas_model,
        SigmaPointSlr(SphericalCubature()),
    )
    num_samples = 10
    old_time = timeit(old, number=num_samples) / num_samples
    print(old_time)
    new_time = timeit(new, number=num_samples) / num_samples
    print(new_time)
    assert np.allclose(new(), old())
Example #8
0
    def test_cmp_slr_costs(self):
        dt = 0.01
        qc = 0.01
        qw = 10
        Q = np.array([
            [qc * dt**3 / 3, 0, qc * dt**2 / 2, 0, 0],
            [0, qc * dt**3 / 3, 0, qc * dt**2 / 2, 0],
            [qc * dt**2 / 2, 0, qc * dt, 0, 0],
            [0, qc * dt**2 / 2, 0, qc * dt, 0],
            [0, 0, 0, 0, dt * qw],
        ])
        motion_model = CoordTurn(dt, Q)

        sens_pos_1 = np.array([-1.5, 0.5])
        sens_pos_2 = np.array([1, 1])
        sensors = np.row_stack((sens_pos_1, sens_pos_2))
        std = 0.5
        R = std**2 * np.eye(2)
        meas_model = MultiSensorRange(sensors, R)

        prior_mean = np.array([0, 0, 1, 0, 0])
        prior_cov = np.diag([0.1, 0.1, 1, 1, 1])

        _, measurements, _, ss_ms = get_specific_states_from_file(
            Path.cwd() / "data/lm_ieks_paper", Type.LM, 10)
        measurements = measurements[:, :2]
        K = measurements.shape[0]
        np.random.seed(0)
        covs = np.array([prior_cov] * K) * (0.90 + np.random.rand() / 5)
        slr_cache = SlrCache(motion_model, meas_model,
                             SigmaPointSlr(SphericalCubature()))
        pre_comp_proto = partial(
            slr_smoothing_cost_pre_comp,
            measurements=measurements,
            m_1_0=prior_mean,
            P_1_0_inv=np.linalg.inv(prior_cov),
        )

        on_the_fly = partial(
            slr_smoothing_cost,
            covs=covs,
            measurements=measurements,
            m_1_0=prior_mean,
            P_1_0=prior_cov,
            motion_model=motion_model,
            meas_model=meas_model,
            slr=SigmaPointSlr(SphericalCubature()),
        )

        slr_cache.update(ss_ms, covs)

        varying_means_proto = partial(
            slr_smoothing_cost_means,
            measurements=measurements,
            m_1_0=prior_mean,
            P_1_0_inv=np.linalg.inv(prior_cov),
            estimated_covs=covs,
            motion_fn=motion_model.map_set,
            meas_fn=meas_model.map_set,
            slr_method=SigmaPointSlr(SphericalCubature()),
        )
        varying_means = partial(
            varying_means_proto,
            motion_cov_inv=slr_cache.proc_cov_inv,
            meas_cov_inv=slr_cache.meas_cov_inv,
        )

        proc_cov_inv, meas_cov_inv = slr_cache.inv_cov()
        pre_comp = partial(
            pre_comp_proto,
            motion_bar=slr_cache.proc_bar,
            meas_bar=slr_cache.meas_bar,
            motion_cov_inv=proc_cov_inv,
            meas_cov_inv=meas_cov_inv,
        )

        self.assertAlmostEqual(pre_comp(ss_ms), on_the_fly(ss_ms))
        self.assertAlmostEqual(pre_comp(ss_ms), varying_means(ss_ms))

        _, measurements, _, ss_ms = get_specific_states_from_file(
            Path.cwd() / "data/lm_ieks_paper", Type.GN, 1)
        slr_cache.update(ss_ms, covs)
        pre_comp = partial(
            pre_comp_proto,
            motion_bar=slr_cache.proc_bar,
            meas_bar=slr_cache.meas_bar,
            motion_cov_inv=slr_cache.proc_cov_inv,
            meas_cov_inv=slr_cache.meas_cov_inv,
        )
        varying_means = partial(
            varying_means_proto,
            motion_cov_inv=slr_cache.proc_cov_inv,
            meas_cov_inv=slr_cache.meas_cov_inv,
        )

        self.assertAlmostEqual(pre_comp(ss_ms), on_the_fly(ss_ms))
        self.assertAlmostEqual(pre_comp(ss_ms), varying_means(ss_ms))