use super::ins_error_state::InsErrorState;
use alloc::format;
use deep_causality_algebra::RealField;
use deep_causality_linear::dot_n;
use deep_causality_physics::PhysicsError;
pub const NAV_STATES: usize = 17;
fn mat_mul<R: RealField, const M: usize>(a: &[[R; M]; M], b: &[[R; M]; M]) -> [[R; M]; M] {
core::array::from_fn(|i| {
core::array::from_fn(|j| (0..M).fold(R::zero(), |s, k| s + a[i][k] * b[k][j]))
})
}
fn mat_transpose<R: RealField, const M: usize>(a: &[[R; M]; M]) -> [[R; M]; M] {
core::array::from_fn(|i| core::array::from_fn(|j| a[j][i]))
}
fn mat_add<R: RealField, const M: usize>(a: &[[R; M]; M], b: &[[R; M]; M]) -> [[R; M]; M] {
core::array::from_fn(|i| core::array::from_fn(|j| a[i][j] + b[i][j]))
}
fn mat_vec<R: RealField, const M: usize>(a: &[[R; M]; M], v: &[R; M]) -> [R; M] {
core::array::from_fn(|i| (0..M).fold(R::zero(), |s, k| s + a[i][k] * v[k]))
}
fn dot<R: RealField, const M: usize>(a: &[R; M], b: &[R; M]) -> R {
dot_n(a, b)
}
fn diag<R: RealField, const M: usize>(d: &[R; M]) -> [[R; M]; M] {
core::array::from_fn(|i| core::array::from_fn(|j| if i == j { d[i] } else { R::zero() }))
}
pub fn nav_transition_matrix<R: RealField>(dt: R, f: [R; 3]) -> [[R; NAV_STATES]; NAV_STATES] {
let mut m: [[R; NAV_STATES]; NAV_STATES] = core::array::from_fn(|i| {
core::array::from_fn(|j| if i == j { R::one() } else { R::zero() })
});
let neg = R::zero() - R::one();
m[0][3] = dt;
m[1][4] = dt;
m[2][5] = dt;
m[3][7] = f[2] * dt;
m[3][8] = neg * f[1] * dt;
m[4][6] = neg * f[2] * dt;
m[4][8] = f[0] * dt;
m[5][6] = f[1] * dt;
m[5][7] = neg * f[0] * dt;
m[3][9] = neg * dt;
m[4][10] = neg * dt;
m[5][11] = neg * dt;
m[6][12] = neg * dt;
m[7][13] = neg * dt;
m[8][14] = neg * dt;
m[15][16] = dt;
m
}
fn validate_covariance<R: RealField>(
cov: &[[R; NAV_STATES]; NAV_STATES],
) -> Result<(), PhysicsError> {
for (i, row) in cov.iter().enumerate() {
for (j, &c) in row.iter().enumerate() {
if !c.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"navigation covariance entry [{i}][{j}] is not finite"
)));
}
}
}
let tol = R::epsilon().sqrt();
for (i, row) in cov.iter().enumerate() {
for (j, &a) in row.iter().enumerate().skip(i + 1) {
let b = cov[j][i];
let scale = R::one() + max_abs(a, b);
if (a - b).abs() > tol * scale {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"navigation covariance is not symmetric: [{i}][{j}] and [{j}][{i}] differ beyond tolerance"
)));
}
}
}
for (i, row) in cov.iter().enumerate() {
if row[i] < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"navigation covariance diagonal [{i}][{i}] is a negative variance"
)));
}
}
Ok(())
}
fn max_abs<R: RealField>(a: R, b: R) -> R {
let (a, b) = (a.abs(), b.abs());
if a > b { a } else { b }
}
#[derive(Clone, Debug)]
pub struct NavFilter<R: RealField> {
state: InsErrorState<R>,
cov: [[R; NAV_STATES]; NAV_STATES],
}
impl<R: RealField> NavFilter<R> {
pub fn new(state: InsErrorState<R>, cov_diag: [R; NAV_STATES]) -> Result<Self, PhysicsError> {
let cov = diag(&cov_diag);
validate_covariance(&cov)?;
Ok(Self { state, cov })
}
pub fn predict(
&mut self,
dt: R,
specific_force: [R; 3],
process_noise_diag: [R; NAV_STATES],
) -> Result<(), PhysicsError> {
if dt <= R::zero() || !dt.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"navigation predict step dt is non-positive or non-finite (dt.is_finite() = {})",
dt.is_finite()
)));
}
for (i, &q) in process_noise_diag.iter().enumerate() {
if q < R::zero() || !q.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"process-noise spectral density [{i}] is negative or non-finite (is_finite() = {})",
q.is_finite()
)));
}
}
self.state = self.state.propagate(dt, specific_force);
let f = nav_transition_matrix(dt, specific_force);
let fp = mat_mul(&f, &self.cov);
let fpft = mat_mul(&fp, &mat_transpose(&f));
let q_d: [R; NAV_STATES] = core::array::from_fn(|i| process_noise_diag[i] * dt);
self.cov = mat_add(&fpft, &diag(&q_d));
Ok(())
}
pub fn update_scalar(&mut self, h: [R; NAV_STATES], z: R, r: R) -> Result<(), PhysicsError> {
if !z.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(
"measurement z is non-finite".into(),
));
}
if r < R::zero() || !r.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"measurement variance r is negative or non-finite (r.is_finite() = {})",
r.is_finite()
)));
}
let x = self.state.to_array();
let ph = mat_vec(&self.cov, &h); let s = dot(&h, &ph) + r; if s <= R::zero() || !s.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"innovation covariance s = h·P·hᵀ + r is non-positive or non-finite (s.is_finite() = {})",
s.is_finite()
)));
}
let innov = z - dot(&h, &x);
let k: [R; NAV_STATES] = core::array::from_fn(|i| ph[i] / s); let x_new: [R; NAV_STATES] = core::array::from_fn(|i| x[i] + k[i] * innov);
self.state = InsErrorState::from_array(x_new);
let a: [[R; NAV_STATES]; NAV_STATES] = core::array::from_fn(|i| {
core::array::from_fn(|j| {
let id = if i == j { R::one() } else { R::zero() };
id - k[i] * h[j]
})
});
let ap = mat_mul(&a, &self.cov);
let apat = mat_mul(&ap, &mat_transpose(&a));
let half = R::one() / (R::one() + R::one());
self.cov = core::array::from_fn(|i| {
core::array::from_fn(|j| {
let joseph = apat[i][j] + r * k[i] * k[j];
let joseph_t = apat[j][i] + r * k[j] * k[i];
(joseph + joseph_t) * half
})
});
Ok(())
}
pub fn covariance(&self) -> &[[R; NAV_STATES]; NAV_STATES] {
&self.cov
}
pub fn restore(
state: InsErrorState<R>,
cov: [[R; NAV_STATES]; NAV_STATES],
) -> Result<Self, PhysicsError> {
validate_covariance(&cov)?;
Ok(Self { state, cov })
}
pub fn state(&self) -> &InsErrorState<R> {
&self.state
}
pub fn reset_navigation_error(&mut self) {
self.state = self.state.reset_navigation();
}
pub fn position_variance(&self) -> R {
self.cov[0][0] + self.cov[1][1] + self.cov[2][2]
}
pub fn covariance_trace(&self) -> R {
(0..NAV_STATES).fold(R::zero(), |s, i| s + self.cov[i][i])
}
}