mod batch;
#[cfg(feature = "alloc")]
mod ckf;
mod ekf;
#[cfg(feature = "alloc")]
mod rts;
#[cfg(feature = "alloc")]
mod srukf;
#[cfg(feature = "alloc")]
mod ukf;
#[cfg(test)]
mod tests;
pub use batch::BatchLsq;
#[cfg(feature = "alloc")]
pub use ckf::Ckf;
pub use ekf::Ekf;
#[cfg(feature = "alloc")]
pub use rts::{rts_smooth, EkfStep};
#[cfg(feature = "alloc")]
pub use srukf::SrUkf;
#[cfg(feature = "alloc")]
pub use ukf::Ukf;
use crate::linalg::CholeskyDecomposition;
use crate::matrix::vector::Vector;
use crate::traits::FloatScalar;
use crate::Matrix;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EstimateError {
CovarianceNotPD,
SingularInnovation,
}
impl core::fmt::Display for EstimateError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EstimateError::CovarianceNotPD => {
write!(f, "covariance matrix is not positive definite")
}
EstimateError::SingularInnovation => {
write!(f, "innovation covariance is singular")
}
}
}
}
pub(crate) fn cholesky_with_jitter<T: FloatScalar, const N: usize>(
p: &Matrix<T, N, N>,
) -> Result<CholeskyDecomposition<T, N>, EstimateError> {
if let Ok(c) = CholeskyDecomposition::new(p) {
return Ok(c);
}
for eps in [1e-9_f64, 1e-7, 1e-5] {
let eps = T::from(eps).unwrap();
let mut pj = *p;
for i in 0..N {
pj[(i, i)] = pj[(i, i)] + eps;
}
if let Ok(c) = CholeskyDecomposition::new(&pj) {
return Ok(c);
}
}
Err(EstimateError::CovarianceNotPD)
}
pub(crate) fn apply_var_floor<T: FloatScalar, const N: usize>(p: &mut Matrix<T, N, N>, min_var: T) {
if min_var > T::zero() {
for i in 0..N {
if p[(i, i)] < min_var {
p[(i, i)] = min_var;
}
}
}
}
#[cfg(feature = "alloc")]
pub(crate) fn symmetrize_and_floor<T: FloatScalar, const N: usize>(
p: &mut Matrix<T, N, N>,
min_variance: T,
) {
let half = T::from(0.5).unwrap();
*p = (*p + p.transpose()) * half;
apply_var_floor(p, min_variance);
}
#[cfg(feature = "alloc")]
pub(crate) fn store_predicted<T: FloatScalar, const N: usize>(
x: &mut Vector<T, N>,
p: &mut Matrix<T, N, N>,
x_mean: Vector<T, N>,
p_sigma: Matrix<T, N, N>,
gamma: T,
q: Option<&Matrix<T, N, N>>,
min_variance: T,
) {
let scaled = p_sigma * gamma;
*x = x_mean;
*p = match q {
Some(q) => scaled + *q,
None => scaled,
};
symmetrize_and_floor(p, min_variance);
}
#[cfg(feature = "alloc")]
pub(crate) fn sigma_point_update<T: FloatScalar, const N: usize, const M: usize>(
x: &mut Vector<T, N>,
p: &mut Matrix<T, N, N>,
s_mat: &Matrix<T, M, M>,
s_inv: &Matrix<T, M, M>,
pxz: &Matrix<T, N, M>,
innovation: &Vector<T, M>,
min_variance: T,
) {
let k = *pxz * *s_inv;
*x += k * *innovation;
*p -= k * *s_mat * k.transpose();
symmetrize_and_floor(p, min_variance);
}
pub(crate) fn fd_jacobian<T: FloatScalar, const N: usize, const M: usize>(
f: &impl Fn(&Vector<T, N>) -> Vector<T, M>,
x: &Vector<T, N>,
) -> Matrix<T, M, N> {
let f0 = f(x);
crate::fdiff::forward_diff_jacobian(x, &f0, |xp| f(xp))
}