use crate::common::{FilterError, FloatOps};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UnscentedKalmanFilter2D {
pub x: [f64; 2],
pub p: [[f64; 2]; 2],
pub q: [[f64; 2]; 2],
pub r: f64,
alpha: f64,
beta: f64,
kappa: f64,
}
pub type SigmaPoints2D = ([f64; 5], [f64; 5], [[f64; 2]; 5]);
impl UnscentedKalmanFilter2D {
pub fn new(
initial_x: [f64; 2],
initial_p: [[f64; 2]; 2],
q: [[f64; 2]; 2],
r: f64,
) -> Result<Self, FilterError> {
if !initial_x[0].is_finite()
|| !initial_x[1].is_finite()
|| initial_p[0][0] <= 0.0
|| initial_p[1][1] <= 0.0
|| r <= 0.0
{
return Err(FilterError::InvalidCovariance);
}
Ok(Self {
x: initial_x,
p: initial_p,
q,
r,
alpha: 1.0,
beta: 2.0, kappa: 1.0,
})
}
fn generate_sigma_points(&self) -> Result<SigmaPoints2D, FilterError> {
let n = 2.0;
let lambda = self.alpha * self.alpha * (n + self.kappa) - n;
let c = n + lambda;
if c <= 0.0 {
return Err(FilterError::NumericalInstability);
}
let a = c * self.p[0][0];
let b = c * self.p[0][1];
let d = c * self.p[1][1];
if a <= 1e-15 {
return Err(FilterError::NumericalInstability);
}
let l00 = a.sqrt_val();
let l10 = b / l00;
let l11_sq = d - (l10 * l10);
if l11_sq <= 1e-15 {
return Err(FilterError::NumericalInstability);
}
let l11 = l11_sq.sqrt_val();
let mut sigma_points = [[0.0; 2]; 5];
sigma_points[0] = self.x;
sigma_points[1] = [self.x[0] + l00, self.x[1] + l10];
sigma_points[2] = [self.x[0], self.x[1] + l11];
sigma_points[3] = [self.x[0] - l00, self.x[1] - l10];
sigma_points[4] = [self.x[0], self.x[1] - l11];
let mut wm = [0.0; 5];
let mut wc = [0.0; 5];
wm[0] = lambda / (n + lambda);
wc[0] = wm[0] + (1.0 - self.alpha * self.alpha + self.beta);
let weight_i = 1.0 / (2.0 * (n + lambda));
for i in 1..5 {
wm[i] = weight_i;
wc[i] = weight_i;
}
Ok((wm, wc, sigma_points))
}
pub fn predict<F>(&mut self, dt: f64, mut transition_fn: F) -> Result<(), FilterError>
where
F: FnMut([f64; 2], f64) -> [f64; 2],
{
let (wm, wc, sigma_points) = self.generate_sigma_points()?;
let mut propagated_points = [[0.0; 2]; 5];
for i in 0..5 {
propagated_points[i] = transition_fn(sigma_points[i], dt);
if !propagated_points[i][0].is_finite() || !propagated_points[i][1].is_finite() {
return Err(FilterError::NonFiniteInput);
}
}
let mut x_pred = [0.0; 2];
for i in 0..5 {
x_pred[0] += wm[i] * propagated_points[i][0];
x_pred[1] += wm[i] * propagated_points[i][1];
}
let mut p_pred = [[0.0; 2]; 2];
for i in 0..5 {
let dx0 = propagated_points[i][0] - x_pred[0];
let dx1 = propagated_points[i][1] - x_pred[1];
p_pred[0][0] += wc[i] * dx0 * dx0;
p_pred[0][1] += wc[i] * dx0 * dx1;
p_pred[1][0] += wc[i] * dx1 * dx0;
p_pred[1][1] += wc[i] * dx1 * dx1;
}
p_pred[0][0] += self.q[0][0];
p_pred[0][1] += self.q[0][1];
p_pred[1][0] += self.q[1][0];
p_pred[1][1] += self.q[1][1];
let p_sym = (p_pred[0][1] + p_pred[1][0]) * 0.5;
p_pred[0][1] = p_sym;
p_pred[1][0] = p_sym;
self.x = x_pred;
self.p = p_pred;
Ok(())
}
pub fn update<H>(
&mut self,
measurement: f64,
mut observe_fn: H,
gate_sigma: Option<f64>,
) -> Result<[f64; 2], FilterError>
where
H: FnMut([f64; 2]) -> f64,
{
if !measurement.is_finite() {
return Err(FilterError::NonFiniteInput);
}
let (wm, wc, sigma_points) = self.generate_sigma_points()?;
let mut gamma = [0.0; 5];
for i in 0..5 {
gamma[i] = observe_fn(sigma_points[i]);
if !gamma[i].is_finite() {
return Err(FilterError::NonFiniteInput);
}
}
let mut z_pred = 0.0;
for i in 0..5 {
z_pred += wm[i] * gamma[i];
}
let mut s = self.r;
for i in 0..5 {
let dz = gamma[i] - z_pred;
s += wc[i] * dz * dz;
}
if s <= 1e-15 || !s.is_finite() {
return Err(FilterError::SingularInnovation);
}
let y = measurement - z_pred;
if let Some(gate) = gate_sigma {
if gate > 0.0 {
let normalized_err = y.abs_val() / s.sqrt_val();
if normalized_err > gate {
return Err(FilterError::MeasurementGated);
}
}
}
let mut p_xz = [0.0; 2];
for i in 0..5 {
let dx0 = sigma_points[i][0] - self.x[0];
let dx1 = sigma_points[i][1] - self.x[1];
let dz = gamma[i] - z_pred;
p_xz[0] += wc[i] * dx0 * dz;
p_xz[1] += wc[i] * dx1 * dz;
}
let k0 = p_xz[0] / s;
let k1 = p_xz[1] / s;
self.x[0] += k0 * y;
self.x[1] += k1 * y;
self.p[0][0] -= k0 * k0 * s;
self.p[0][1] -= k0 * k1 * s;
self.p[1][0] -= k1 * k0 * s;
self.p[1][1] -= k1 * k1 * s;
let p_sym = (self.p[0][1] + self.p[1][0]) * 0.5;
self.p[0][1] = p_sym;
self.p[1][0] = p_sym;
if self.p[0][0] < 1e-15 {
self.p[0][0] = 1e-15;
}
if self.p[1][1] < 1e-15 {
self.p[1][1] = 1e-15;
}
Ok(self.x)
}
}