#![allow(non_snake_case)]
use crate::error::{IrisError, Result};
pub struct KalmanFilter {
state_dim: usize,
measure_dim: usize,
F: Vec<Vec<f64>>,
H: Vec<Vec<f64>>,
Q: Vec<Vec<f64>>,
R: Vec<Vec<f64>>,
x: Vec<f64>,
P: Vec<Vec<f64>>,
}
impl KalmanFilter {
pub fn new(state_dim: usize, measure_dim: usize) -> Self {
let F = Self::eye(state_dim);
let H = Self::default_h(state_dim, measure_dim);
let Q = Self::scaled_eye(state_dim, 1e-2);
let R = Self::scaled_eye(measure_dim, 1e-1);
let x = vec![0.0; state_dim];
let P = Self::eye(state_dim);
Self {
state_dim,
measure_dim,
F,
H,
Q,
R,
x,
P,
}
}
pub fn set_transition(&mut self, f: Vec<Vec<f64>>) -> Result<()> {
if f.len() != self.state_dim || f.iter().any(|row| row.len() != self.state_dim) {
return Err(IrisError::InvalidParameter(format!(
"F must be [{0} x {0}]",
self.state_dim
)));
}
self.F = f;
Ok(())
}
pub fn set_measurement(&mut self, h: Vec<Vec<f64>>) -> Result<()> {
if h.len() != self.measure_dim || h.iter().any(|row| row.len() != self.state_dim) {
return Err(IrisError::InvalidParameter(format!(
"H must be [{0} x {1}]",
self.measure_dim, self.state_dim
)));
}
self.H = h;
Ok(())
}
pub fn set_process_noise(&mut self, q: Vec<Vec<f64>>) -> Result<()> {
if q.len() != self.state_dim || q.iter().any(|row| row.len() != self.state_dim) {
return Err(IrisError::InvalidParameter(format!(
"Q must be [{0} x {0}]",
self.state_dim
)));
}
self.Q = q;
Ok(())
}
pub fn set_measurement_noise(&mut self, r: Vec<Vec<f64>>) -> Result<()> {
if r.len() != self.measure_dim || r.iter().any(|row| row.len() != self.measure_dim) {
return Err(IrisError::InvalidParameter(format!(
"R must be [{0} x {0}]",
self.measure_dim
)));
}
self.R = r;
Ok(())
}
pub fn set_initial_state(&mut self, x: Vec<f64>) -> Result<()> {
if x.len() != self.state_dim {
return Err(IrisError::InvalidParameter(format!(
"State vector must have length {0}",
self.state_dim
)));
}
self.x = x;
Ok(())
}
pub fn predict(&mut self) {
self.x = mat_vec_mul(&self.F, &self.x);
let fp = mat_mul(&self.F, &self.P);
let f_t = transpose(&self.F);
let fpft = mat_mul(&fp, &f_t);
self.P = mat_add(&fpft, &self.Q);
}
pub fn update(&mut self, z: &[f64]) -> Result<()> {
if z.len() != self.measure_dim {
return Err(IrisError::InvalidParameter(format!(
"Measurement vector must have length {0}",
self.measure_dim
)));
}
let hx = mat_vec_mul(&self.H, &self.x);
let y: Vec<f64> = z.iter().zip(hx.iter()).map(|(zi, hxi)| zi - hxi).collect();
let hp = mat_mul(&self.H, &self.P);
let ht = transpose(&self.H);
let hpht = mat_mul(&hp, &ht);
let s = mat_add(&hpht, &self.R);
let s_inv = inverse(&s)?;
let pht = mat_mul(&self.P, &ht);
let k = mat_mul(&pht, &s_inv);
let ky = mat_vec_mul(&k, &y);
self.x = vec_add(&self.x, &ky);
let kh = mat_mul(&k, &self.H);
let i_kh = mat_sub(&Self::eye(self.state_dim), &kh);
self.P = mat_mul(&i_kh, &self.P);
Ok(())
}
pub fn state(&self) -> &[f64] {
&self.x
}
pub fn covariance_flat(&self) -> Vec<f64> {
self.P.iter().flat_map(|row| row.iter().copied()).collect()
}
pub fn covariance(&self) -> &[Vec<f64>] {
&self.P
}
fn eye(n: usize) -> Vec<Vec<f64>> {
(0..n)
.map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
.collect()
}
fn scaled_eye(n: usize, scale: f64) -> Vec<Vec<f64>> {
(0..n)
.map(|i| (0..n).map(|j| if i == j { scale } else { 0.0 }).collect())
.collect()
}
fn default_h(state_dim: usize, measure_dim: usize) -> Vec<Vec<f64>> {
let mut h = vec![vec![0.0; state_dim]; measure_dim];
let m = measure_dim.min(state_dim);
for i in 0..m {
h[i][i] = 1.0;
}
h
}
}
fn mat_mul(a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
let rows = a.len();
let cols = b[0].len();
let k = b.len();
let mut result = vec![vec![0.0; cols]; rows];
for i in 0..rows {
for j in 0..cols {
let mut sum = 0.0;
for p in 0..k {
sum += a[i][p] * b[p][j];
}
result[i][j] = sum;
}
}
result
}
fn mat_vec_mul(m: &[Vec<f64>], v: &[f64]) -> Vec<f64> {
m.iter()
.map(|row| row.iter().zip(v.iter()).map(|(a, b)| a * b).sum())
.collect()
}
fn mat_add(a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
a.iter()
.zip(b.iter())
.map(|(ra, rb)| ra.iter().zip(rb.iter()).map(|(ai, bi)| ai + bi).collect())
.collect()
}
fn mat_sub(a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
a.iter()
.zip(b.iter())
.map(|(ra, rb)| ra.iter().zip(rb.iter()).map(|(ai, bi)| ai - bi).collect())
.collect()
}
fn transpose(m: &[Vec<f64>]) -> Vec<Vec<f64>> {
if m.is_empty() {
return vec![];
}
let rows = m.len();
let cols = m[0].len();
(0..cols)
.map(|j| (0..rows).map(|i| m[i][j]).collect())
.collect()
}
fn vec_add(a: &[f64], b: &[f64]) -> Vec<f64> {
a.iter().zip(b.iter()).map(|(ai, bi)| ai + bi).collect()
}
fn inverse(m: &[Vec<f64>]) -> Result<Vec<Vec<f64>>> {
let n = m.len();
if n == 0 || m.iter().any(|row| row.len() != n) {
return Err(IrisError::InvalidParameter(
"Matrix must be square and non-empty".into(),
));
}
let mut aug: Vec<Vec<f64>> = (0..n)
.map(|i| {
let mut row = m[i].clone();
row.extend((0..n).map(|j| if i == j { 1.0 } else { 0.0 }));
row
})
.collect();
for col in 0..n {
let mut max_val = aug[col][col].abs();
let mut max_row = col;
for row in (col + 1)..n {
if aug[row][col].abs() > max_val {
max_val = aug[row][col].abs();
max_row = row;
}
}
if max_val < 1e-12 {
return Err(IrisError::Tensor(
"Matrix is singular and cannot be inverted".into(),
));
}
aug.swap(col, max_row);
let pivot = aug[col][col];
for j in 0..(2 * n) {
aug[col][j] /= pivot;
}
for row in 0..n {
if row == col {
continue;
}
let factor = aug[row][col];
for j in 0..(2 * n) {
aug[row][j] -= factor * aug[col][j];
}
}
}
let inv: Vec<Vec<f64>> = (0..n).map(|i| aug[i][n..(2 * n)].to_vec()).collect();
Ok(inv)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kalman_identity_tracking() {
let mut kf = KalmanFilter::new(2, 1);
kf.set_transition(vec![vec![1.0, 1.0], vec![0.0, 1.0]])
.unwrap();
kf.set_measurement(vec![vec![1.0, 0.0]]).unwrap();
kf.set_process_noise(vec![vec![1e-4, 0.0], vec![0.0, 1e-4]])
.unwrap();
kf.set_measurement_noise(vec![vec![0.1]]).unwrap();
kf.set_initial_state(vec![0.0, 0.0]).unwrap();
let mut last_pos = 0.0f64;
for step in 1..=10 {
kf.predict();
let true_pos = step as f64;
let measurement = [true_pos + 0.05]; kf.update(&measurement).unwrap();
let state = kf.state();
assert!(
(state[0] - true_pos).abs() < 2.0,
"Step {step}: expected ~{true_pos}, got {}",
state[0]
);
last_pos = state[0];
}
assert!((last_pos - 10.0).abs() < 2.0);
}
#[test]
fn test_kalman_predict_only() {
let mut kf = KalmanFilter::new(2, 1);
kf.set_transition(vec![vec![1.0, 1.0], vec![0.0, 1.0]])
.unwrap();
kf.set_initial_state(vec![0.0, 1.0]).unwrap();
kf.predict();
let s = kf.state();
assert!((s[0] - 1.0).abs() < 1e-10);
assert!((s[1] - 1.0).abs() < 1e-10);
kf.predict();
let s = kf.state();
assert!((s[0] - 2.0).abs() < 1e-10);
assert!((s[1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_kalman_covariance_dims() {
let kf = KalmanFilter::new(3, 2);
let cov = kf.covariance_flat();
assert_eq!(cov.len(), 9); }
#[test]
fn test_inverse_2x2() {
let m = vec![vec![4.0, 7.0], vec![2.0, 6.0]];
let inv = inverse(&m).unwrap();
let product = mat_mul(&m, &inv);
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(product[i][j] - expected).abs() < 1e-10,
"({i},{j}): got {}",
product[i][j]
);
}
}
}
#[test]
fn test_inverse_singular() {
let m = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
assert!(inverse(&m).is_err());
}
#[test]
fn test_measurement_mismatch() {
let mut kf = KalmanFilter::new(2, 1);
assert!(kf.update(&[1.0, 2.0]).is_err());
}
}