use crate::error::EstimationError;
use crate::linear_algebra::{Matrix, Vector};
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum CovarianceUpdate {
#[default]
Joseph,
Naive,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct KalmanModel<const STATE_DIMENSION: usize, const MEASUREMENT_DIMENSION: usize, T = f64> {
pub state_transition: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
pub measurement_model: Matrix<MEASUREMENT_DIMENSION, STATE_DIMENSION, T>,
pub process_noise: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
pub measurement_noise: Matrix<MEASUREMENT_DIMENSION, MEASUREMENT_DIMENSION, T>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct KalmanFilter<const STATE_DIMENSION: usize, const MEASUREMENT_DIMENSION: usize, T = f64> {
state: Vector<STATE_DIMENSION, T>,
covariance: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
state_transition: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
measurement_model: Matrix<MEASUREMENT_DIMENSION, STATE_DIMENSION, T>,
process_noise: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
measurement_noise: Matrix<MEASUREMENT_DIMENSION, MEASUREMENT_DIMENSION, T>,
innovation: Vector<MEASUREMENT_DIMENSION, T>,
innovation_covariance: Matrix<MEASUREMENT_DIMENSION, MEASUREMENT_DIMENSION, T>,
covariance_update: CovarianceUpdate,
}
impl<const STATE_DIMENSION: usize, const MEASUREMENT_DIMENSION: usize, T: Numeric>
KalmanFilter<STATE_DIMENSION, MEASUREMENT_DIMENSION, T>
{
pub fn new(
initial_state: Vector<STATE_DIMENSION, T>,
initial_covariance: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
model: KalmanModel<STATE_DIMENSION, MEASUREMENT_DIMENSION, T>,
) -> Self {
const {
assert!(
STATE_DIMENSION > 0,
"KalmanFilter: STATE_DIMENSION must be non-zero"
)
};
const {
assert!(
MEASUREMENT_DIMENSION > 0,
"KalmanFilter: MEASUREMENT_DIMENSION must be non-zero"
)
};
KalmanFilter {
state: initial_state,
covariance: initial_covariance,
state_transition: model.state_transition,
measurement_model: model.measurement_model,
process_noise: model.process_noise,
measurement_noise: model.measurement_noise,
innovation: Vector::zeros(),
innovation_covariance: Matrix::zeros(),
covariance_update: CovarianceUpdate::Joseph,
}
}
#[must_use]
pub const fn with_covariance_update(mut self, covariance_update: CovarianceUpdate) -> Self {
self.covariance_update = covariance_update;
self
}
pub fn set_state(&mut self, state: Vector<STATE_DIMENSION, T>) {
self.state = state;
}
pub fn set_state_transition(
&mut self,
state_transition: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
) {
self.state_transition = state_transition;
}
pub fn set_process_noise(
&mut self,
process_noise: Matrix<STATE_DIMENSION, STATE_DIMENSION, T>,
) {
self.process_noise = process_noise;
}
pub fn set_measurement_model(
&mut self,
measurement_model: Matrix<MEASUREMENT_DIMENSION, STATE_DIMENSION, T>,
) {
self.measurement_model = measurement_model;
}
pub fn set_measurement_noise(
&mut self,
measurement_noise: Matrix<MEASUREMENT_DIMENSION, MEASUREMENT_DIMENSION, T>,
) {
self.measurement_noise = measurement_noise;
}
pub fn predict(&mut self) {
self.state = self.state_transition * self.state;
self.predict_covariance();
}
pub fn predict_with_control<const CONTROL_DIMENSION: usize>(
&mut self,
control_model: Matrix<STATE_DIMENSION, CONTROL_DIMENSION, T>,
control_input: Vector<CONTROL_DIMENSION, T>,
) {
self.state = self.state_transition * self.state + control_model * control_input;
self.predict_covariance();
}
fn predict_covariance(&mut self) {
self.covariance =
self.state_transition * self.covariance * self.state_transition.transpose()
+ self.process_noise;
}
pub fn update(
&mut self,
measurement: Vector<MEASUREMENT_DIMENSION, T>,
) -> Result<(), EstimationError> {
if !measurement.is_finite() {
return Err(EstimationError::NonFinite);
}
self.innovation = measurement - self.measurement_model * self.state;
self.innovation_covariance =
self.measurement_model * self.covariance * self.measurement_model.transpose()
+ self.measurement_noise;
if !self.innovation_covariance.is_finite() {
return Err(EstimationError::NonFinite);
}
let projected = self.measurement_model * self.covariance.transpose();
let kalman_gain = self
.innovation_covariance
.cholesky()
.map_err(|_| EstimationError::NotPositiveDefinite)?
.solve_matrix::<STATE_DIMENSION>(projected)
.transpose();
self.state += kalman_gain * self.innovation;
let residual = Matrix::<STATE_DIMENSION, STATE_DIMENSION, T>::identity()
- kalman_gain * self.measurement_model;
self.covariance = match self.covariance_update {
CovarianceUpdate::Joseph => {
residual * self.covariance * residual.transpose()
+ kalman_gain * self.measurement_noise * kalman_gain.transpose()
}
CovarianceUpdate::Naive => residual * self.covariance,
};
Ok(())
}
pub fn state(&self) -> Vector<STATE_DIMENSION, T> {
self.state
}
pub fn covariance(&self) -> Matrix<STATE_DIMENSION, STATE_DIMENSION, T> {
self.covariance
}
pub fn innovation(&self) -> Vector<MEASUREMENT_DIMENSION, T> {
self.innovation
}
pub fn innovation_covariance(&self) -> Matrix<MEASUREMENT_DIMENSION, MEASUREMENT_DIMENSION, T> {
self.innovation_covariance
}
pub fn normalized_innovation_squared(&self) -> Result<T, EstimationError> {
let weighted = self
.innovation_covariance
.cholesky()
.map_err(|_| EstimationError::NotPositiveDefinite)?
.solve(self.innovation);
Ok(self.innovation.dot(weighted))
}
}