use crate::core::matrix::{matmul, matvec, Matrix};
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmootherError {
SingularMatrix,
BufferFull,
IndexOutOfRange,
InsufficientData,
}
impl core::fmt::Display for SmootherError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SmootherError::SingularMatrix => write!(f, "RtsSmoother: singular matrix"),
SmootherError::BufferFull => write!(f, "RtsSmoother: buffer full"),
SmootherError::IndexOutOfRange => write!(f, "RtsSmoother: index out of range"),
SmootherError::InsufficientData => {
write!(f, "RtsSmoother: insufficient data for smoothing")
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct FilteredState<S: ControlScalar, const N: usize> {
pub x: [S; N],
pub p: Matrix<S, N, N>,
pub x_pred: [S; N],
pub p_pred: Matrix<S, N, N>,
}
impl<S: ControlScalar, const N: usize> FilteredState<S, N> {
pub fn new(x: [S; N], p: Matrix<S, N, N>, x_pred: [S; N], p_pred: Matrix<S, N, N>) -> Self {
Self {
x,
p,
x_pred,
p_pred,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SmoothedState<S: ControlScalar, const N: usize> {
pub x: [S; N],
pub p: Matrix<S, N, N>,
}
#[derive(Debug, Clone, Copy)]
pub struct SmoothedData<S: ControlScalar, const N: usize, const T: usize> {
pub states: [SmoothedState<S, N>; T],
pub len: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct RtsSmoother<S: ControlScalar, const N: usize, const T: usize> {
buffer: [FilteredState<S, N>; T],
count: usize,
}
impl<S: ControlScalar, const N: usize, const T: usize> RtsSmoother<S, N, T> {
pub fn new() -> Self {
let zero_state = FilteredState {
x: [S::ZERO; N],
p: Matrix::zeros(),
x_pred: [S::ZERO; N],
p_pred: Matrix::zeros(),
};
Self {
buffer: [zero_state; T],
count: 0,
}
}
pub fn reset(&mut self) {
self.count = 0;
}
pub fn store_forward(&mut self, state: FilteredState<S, N>) -> Result<(), SmootherError> {
if self.count >= T {
return Err(SmootherError::BufferFull);
}
self.buffer[self.count] = state;
self.count += 1;
Ok(())
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn get_state(&self, k: usize) -> Option<&FilteredState<S, N>> {
if k < self.count {
Some(&self.buffer[k])
} else {
None
}
}
pub fn smooth(&self, a: &Matrix<S, N, N>) -> Result<SmoothedData<S, N, T>, SmootherError> {
let n = self.count;
if n == 0 {
return Err(SmootherError::InsufficientData);
}
let zero_smoothed = SmoothedState {
x: [S::ZERO; N],
p: Matrix::zeros(),
};
let mut out = SmoothedData {
states: [zero_smoothed; T],
len: n,
};
out.states[n - 1] = SmoothedState {
x: self.buffer[n - 1].x,
p: self.buffer[n - 1].p,
};
if n == 1 {
return Ok(out);
}
let at = a.transpose();
for k in (0..n - 1).rev() {
let fk = &self.buffer[k];
let p_pred_kp1 = &fk.p_pred;
let p_pred_kp1_inv = p_pred_kp1.inv().ok_or(SmootherError::SingularMatrix)?;
let p_at = matmul(&fk.p, &at);
let g_k = matmul(&p_at, &p_pred_kp1_inv);
let x_smooth_kp1 = out.states[k + 1].x;
let x_pred_kp1 = fk.x_pred;
let diff_x: [S; N] = core::array::from_fn(|i| x_smooth_kp1[i] - x_pred_kp1[i]);
let g_diff = matvec(&g_k, &diff_x);
let x_smooth_k: [S; N] = core::array::from_fn(|i| fk.x[i] + g_diff[i]);
let p_smooth_kp1 = out.states[k + 1].p;
let diff_p = p_smooth_kp1.sub_mat(p_pred_kp1);
let g_diff_p = matmul(&g_k, &diff_p);
let gkt = g_k.transpose();
let correction = matmul(&g_diff_p, &gkt);
let p_smooth_k = fk.p.add_mat(&correction);
out.states[k] = SmoothedState {
x: x_smooth_k,
p: p_smooth_k,
};
}
Ok(out)
}
}
impl<S: ControlScalar, const N: usize, const T: usize> Default for RtsSmoother<S, N, T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::matrix::matmul;
fn run_forward_1d(
steps: usize,
q: f64,
r: f64,
) -> (RtsSmoother<f64, 1, 64>, Matrix<f64, 1, 1>) {
let a = Matrix::<f64, 1, 1>::identity();
let h = Matrix::<f64, 1, 1>::identity();
let q_mat = Matrix::<f64, 1, 1> { data: [[q]] };
let r_mat = Matrix::<f64, 1, 1> { data: [[r]] };
let mut x = [0.0_f64; 1];
let mut p = Matrix::<f64, 1, 1> { data: [[10.0]] };
let mut smoother = RtsSmoother::<f64, 1, 64>::new();
let measurement = 5.0_f64;
for _ in 0..steps {
let x_pred = x; let ap = matmul(&a, &p);
let at = a.transpose();
let apat = matmul(&ap, &at);
let p_pred = apat.add_mat(&q_mat);
let hx = [x_pred[0]]; let innov = [measurement - hx[0]];
let hp = matmul(&h, &p_pred);
let ht = h.transpose();
let hpht = matmul(&hp, &ht);
let s_mat = hpht.add_mat(&r_mat);
let s_inv = s_mat.inv().expect("S invertible");
let pht = matmul(&p_pred, &ht);
let k = matmul(&pht, &s_inv);
let kv = crate::core::matrix::matvec(&k, &innov);
let x_post: [f64; 1] = core::array::from_fn(|i| x_pred[i] + kv[i]);
let kh = matmul(&k, &h);
let eye = Matrix::<f64, 1, 1>::identity();
let i_kh = eye.sub_mat(&kh);
let p_post = matmul(&i_kh, &p_pred);
smoother
.store_forward(FilteredState::new(x_post, p_post, x_pred, p_pred))
.expect("store");
x = x_post;
p = p_post;
}
(smoother, a)
}
#[test]
fn smoother_covariance_le_filter_covariance() {
let steps = 10_usize;
let (smoother, a) = run_forward_1d(steps, 1e-4, 1.0);
let smoothed = smoother.smooth(&a).expect("smooth");
for k in 0..steps {
let p_filter = smoother.buffer[k].p.trace();
let p_smooth = smoothed.states[k].p.trace();
assert!(
p_smooth <= p_filter + 1e-10,
"Smoothed variance must not exceed filtered variance at k={k}: \
p_smooth={p_smooth}, p_filter={p_filter}"
);
}
}
#[test]
fn smoother_with_single_step_equals_filter() {
let (smoother, a) = run_forward_1d(1, 1e-4, 1.0);
let smoothed = smoother.smooth(&a).expect("smooth");
assert_eq!(smoothed.len, 1);
let x_filter = smoother.buffer[0].x[0];
let x_smooth = smoothed.states[0].x[0];
assert!(
(x_filter - x_smooth).abs() < 1e-12,
"Single step: smoothed must equal filtered. filter={x_filter}, smooth={x_smooth}"
);
}
#[test]
fn smoother_len_matches_stored_count() {
let (smoother, a) = run_forward_1d(8, 1e-4, 0.5);
let smoothed = smoother.smooth(&a).expect("smooth");
assert_eq!(smoothed.len, 8);
}
#[test]
fn store_forward_returns_error_when_full() {
let mut smoother = RtsSmoother::<f64, 1, 2>::new();
let fs = FilteredState::new(
[0.0_f64; 1],
Matrix::identity(),
[0.0_f64; 1],
Matrix::identity(),
);
smoother.store_forward(fs).expect("first store ok");
smoother.store_forward(fs).expect("second store ok");
let res = smoother.store_forward(fs);
assert!(
matches!(res, Err(SmootherError::BufferFull)),
"Expected BufferFull"
);
}
#[test]
fn empty_smoother_returns_insufficient_data() {
let smoother = RtsSmoother::<f64, 2, 16>::new();
let a = Matrix::<f64, 2, 2>::identity();
let res = smoother.smooth(&a);
assert!(matches!(res, Err(SmootherError::InsufficientData)));
}
#[test]
fn reset_clears_stored_states() {
let mut smoother = RtsSmoother::<f64, 1, 8>::new();
let fs = FilteredState::new(
[1.0_f64; 1],
Matrix::identity(),
[1.0_f64; 1],
Matrix::identity(),
);
smoother.store_forward(fs).expect("store");
assert_eq!(smoother.len(), 1);
smoother.reset();
assert_eq!(smoother.len(), 0);
assert!(smoother.is_empty());
}
}