extern crate alloc;
use alloc::vec::Vec;
use crate::matrix::vector::Vector;
use crate::traits::FloatScalar;
use crate::Matrix;
use super::{apply_var_floor, cholesky_with_jitter, EstimateError};
pub struct Ukf<T: FloatScalar, const N: usize, const M: usize> {
pub x: Vector<T, N>,
pub p: Matrix<T, N, N>,
alpha: T,
beta: T,
kappa: T,
min_variance: T,
gamma: T,
}
impl<T: FloatScalar, const N: usize, const M: usize> Ukf<T, N, M> {
pub fn new(x0: Vector<T, N>, p0: Matrix<T, N, N>) -> Self {
Self::with_params(x0, p0, T::one(), T::from(2.0).unwrap(), T::zero())
}
pub fn with_params(x0: Vector<T, N>, p0: Matrix<T, N, N>, alpha: T, beta: T, kappa: T) -> Self {
Self {
x: x0,
p: p0,
alpha,
beta,
kappa,
min_variance: T::zero(),
gamma: T::one(),
}
}
pub fn with_min_variance(mut self, min_variance: T) -> Self {
self.min_variance = min_variance;
self
}
pub fn with_fading_memory(mut self, gamma: T) -> Self {
self.gamma = gamma;
self
}
#[inline]
pub fn state(&self) -> &Vector<T, N> {
&self.x
}
#[inline]
pub fn covariance(&self) -> &Matrix<T, N, N> {
&self.p
}
fn weights(&self) -> (T, T, T) {
let n = T::from(N).unwrap();
let lambda = self.alpha * self.alpha * (n + self.kappa) - n;
let denom = n + lambda;
let wm_0 = lambda / denom;
let wc_0 = wm_0 + (T::one() - self.alpha * self.alpha + self.beta);
let w_i = T::one() / (T::from(2.0).unwrap() * denom);
(wm_0, wc_0, w_i)
}
fn lambda(&self) -> T {
let n = T::from(N).unwrap();
self.alpha * self.alpha * (n + self.kappa) - n
}
fn sigma_cholesky(&self) -> Result<Matrix<T, N, N>, EstimateError> {
let n = T::from(N).unwrap();
let lambda = self.lambda();
let scale = (n + lambda).sqrt();
let chol = cholesky_with_jitter(&self.p)?;
Ok(chol.l_full() * scale)
}
fn sigma_points(&self, scaled_l: &Matrix<T, N, N>) -> Vec<Vector<T, N>> {
let mut sigmas: Vec<Vector<T, N>> = Vec::with_capacity(2 * N + 1);
sigmas.push(self.x);
for i in 0..N {
let mut col = Vector::<T, N>::zeros();
for r in 0..N {
col[r] = scaled_l[(r, i)];
}
sigmas.push(self.x + col);
sigmas.push(self.x - col);
}
sigmas
}
#[allow(clippy::type_complexity)]
fn measurement_transform(
&self,
z: &Vector<T, M>,
h: &impl Fn(&Vector<T, N>) -> Vector<T, M>,
r: &Matrix<T, M, M>,
) -> Result<
(
Matrix<T, M, M>,
Matrix<T, M, M>,
Matrix<T, N, M>,
Vector<T, M>,
T,
),
EstimateError,
> {
let scaled_l = self.sigma_cholesky()?;
let (wm_0, wc_0, w_i) = self.weights();
let sigmas_x = self.sigma_points(&scaled_l);
let mut sigmas_z: Vec<Vector<T, M>> = Vec::with_capacity(2 * N + 1);
for sx in &sigmas_x {
sigmas_z.push(h(sx));
}
let mut z_mean = Vector::<T, M>::zeros();
for r in 0..M {
z_mean[r] = wm_0 * sigmas_z[0][r];
}
for i in 0..N {
for r in 0..M {
z_mean[r] = z_mean[r] + w_i * (sigmas_z[2 * i + 1][r] + sigmas_z[2 * i + 2][r]);
}
}
let mut s = Matrix::<T, M, M>::zeros();
let dz0 = sigmas_z[0] - z_mean;
for r in 0..M {
for c in 0..M {
s[(r, c)] = s[(r, c)] + wc_0 * dz0[r] * dz0[c];
}
}
for i in 0..N {
let dzp = sigmas_z[2 * i + 1] - z_mean;
let dzm = sigmas_z[2 * i + 2] - z_mean;
for r in 0..M {
for c in 0..M {
s[(r, c)] = s[(r, c)] + w_i * (dzp[r] * dzp[c] + dzm[r] * dzm[c]);
}
}
}
let s_mat = s + *r;
let mut pxz = Matrix::<T, N, M>::zeros();
let dx0 = sigmas_x[0] - self.x;
for ri in 0..N {
for ci in 0..M {
pxz[(ri, ci)] = pxz[(ri, ci)] + wc_0 * dx0[ri] * dz0[ci];
}
}
for i in 0..N {
let dxp = sigmas_x[2 * i + 1] - self.x;
let dxm = sigmas_x[2 * i + 2] - self.x;
let dzp = sigmas_z[2 * i + 1] - z_mean;
let dzm = sigmas_z[2 * i + 2] - z_mean;
for ri in 0..N {
for ci in 0..M {
pxz[(ri, ci)] = pxz[(ri, ci)] + w_i * (dxp[ri] * dzp[ci] + dxm[ri] * dzm[ci]);
}
}
}
let s_inv = cholesky_with_jitter(&s_mat)
.map_err(|_| EstimateError::SingularInnovation)?
.inverse();
let innovation = *z - z_mean;
let nis = (innovation.transpose() * s_inv * innovation)[(0, 0)];
Ok((s_mat, s_inv, pxz, innovation, nis))
}
fn apply_update(
&mut self,
s_mat: &Matrix<T, M, M>,
s_inv: &Matrix<T, M, M>,
pxz: &Matrix<T, N, M>,
innovation: &Vector<T, M>,
) {
let k = *pxz * *s_inv;
self.x += k * *innovation;
self.p -= k * *s_mat * k.transpose();
let half = T::from(0.5).unwrap();
self.p = (self.p + self.p.transpose()) * half;
apply_var_floor(&mut self.p, self.min_variance);
}
pub fn predict(
&mut self,
f: impl Fn(&Vector<T, N>) -> Vector<T, N>,
q: Option<&Matrix<T, N, N>>,
) -> Result<(), EstimateError> {
let scaled_l = self.sigma_cholesky()?;
let (wm_0, wc_0, w_i) = self.weights();
let mut sigmas = self.sigma_points(&scaled_l);
for s in sigmas.iter_mut() {
*s = f(s);
}
let mut x_mean = Vector::<T, N>::zeros();
for r in 0..N {
x_mean[r] = wm_0 * sigmas[0][r];
}
for i in 0..N {
for r in 0..N {
x_mean[r] = x_mean[r] + w_i * (sigmas[2 * i + 1][r] + sigmas[2 * i + 2][r]);
}
}
let mut p_new = Matrix::<T, N, N>::zeros();
let d0 = sigmas[0] - x_mean;
for r in 0..N {
for c in 0..N {
p_new[(r, c)] = p_new[(r, c)] + wc_0 * d0[r] * d0[c];
}
}
for i in 0..N {
let dp = sigmas[2 * i + 1] - x_mean;
let dm = sigmas[2 * i + 2] - x_mean;
for r in 0..N {
for c in 0..N {
p_new[(r, c)] = p_new[(r, c)] + w_i * (dp[r] * dp[c] + dm[r] * dm[c]);
}
}
}
p_new *= self.gamma;
self.x = x_mean;
self.p = if let Some(q) = q { p_new + *q } else { p_new };
let half = T::from(0.5).unwrap();
self.p = (self.p + self.p.transpose()) * half;
apply_var_floor(&mut self.p, self.min_variance);
Ok(())
}
pub fn update(
&mut self,
z: &Vector<T, M>,
h: impl Fn(&Vector<T, N>) -> Vector<T, M>,
r: &Matrix<T, M, M>,
) -> Result<T, EstimateError> {
let (s_mat, s_inv, pxz, innovation, nis) = self.measurement_transform(z, &h, r)?;
self.apply_update(&s_mat, &s_inv, &pxz, &innovation);
Ok(nis)
}
pub fn update_gated(
&mut self,
z: &Vector<T, M>,
h: impl Fn(&Vector<T, N>) -> Vector<T, M>,
r: &Matrix<T, M, M>,
gate: T,
) -> Result<Option<T>, EstimateError> {
let (s_mat, s_inv, pxz, innovation, nis) = self.measurement_transform(z, &h, r)?;
if nis > gate {
return Ok(None);
}
self.apply_update(&s_mat, &s_inv, &pxz, &innovation);
Ok(Some(nis))
}
}