#![allow(non_snake_case)]
use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OMatrix, OVector, RealField};
use crate::linalg::rcond;
use crate::models::{
Estimator, ExtendedLinearObserver, ExtendedLinearPredictor, InformationState, KalmanEstimator,
KalmanState,
};
use crate::noise::{CorrelatedNoise, CoupledNoise};
impl<N: RealField, D: Dim> Estimator<N, D> for InformationState<N, D>
where
DefaultAllocator: Allocator<D, D> + Allocator<D>,
{
fn state<'e>(&self) -> Result<OVector<N, D>, &'e str> {
KalmanEstimator::kalman_state(self).map(|r| r.x)
}
}
impl<N: RealField, D: Dim> TryFrom<KalmanState<N, D>> for InformationState<N, D>
where
DefaultAllocator: Allocator<D, D> + Allocator<D>,
{
type Error = &'static str;
fn try_from(state: KalmanState<N, D>) -> Result<Self, Self::Error> {
let I = state.X.clone().cholesky().ok_or("X not PD")?.inverse();
let i = &I * &state.x;
Ok(InformationState { i, I })
}
}
impl<N: RealField, D: Dim> KalmanEstimator<N, D> for InformationState<N, D>
where
DefaultAllocator: Allocator<D, D> + Allocator<D>,
{
fn kalman_state<'e>(&self) -> Result<KalmanState<N, D>, &'e str> {
let X = self.I.clone().cholesky().ok_or("Y not PD")?.inverse();
let x = &X * &self.i;
Ok(KalmanState { x, X })
}
}
impl<N: RealField, D: Dim> ExtendedLinearPredictor<N, D> for InformationState<N, D>
where
DefaultAllocator: Allocator<D, D> + Allocator<D>,
{
fn predict<'e>(
&mut self,
x_pred: &OVector<N, D>,
fx: &OMatrix<N, D, D>,
noise: &CorrelatedNoise<N, D>,
) -> Result<(), &'e str> {
let mut X = self.I.clone().cholesky().ok_or("I not PD in predict")?.inverse();
X.quadform_tr(N::one(), &fx, &X.clone(), N::zero());
X += &noise.Q;
self.I = X.cholesky().ok_or("X not PD")?.inverse();
self.i = &self.I * x_pred;
Ok(())
}
}
impl<N: Copy + RealField, D: Dim, ZD: Dim> ExtendedLinearObserver<N, D, ZD>
for InformationState<N, D>
where
DefaultAllocator: Allocator<D, D>
+ Allocator<D, ZD>
+ Allocator<ZD, D>
+ Allocator<ZD, ZD>
+ Allocator<D>
+ Allocator<ZD>,
{
fn observe_innovation<'e>(
&mut self,
s: &OVector<N, ZD>,
hx: &OMatrix<N, ZD, D>,
noise: &CorrelatedNoise<N, ZD>,
) -> Result<(), &'e str> {
let x = self.state()?;
let noise_inv = noise
.Q
.clone()
.cholesky()
.ok_or("Q not PD in observe")?
.inverse();
let info = self.observe_info(hx, &noise_inv, &(s + hx * x));
self.add_information(&info);
Ok(())
}
}
impl<N: Copy + RealField, D: Dim> InformationState<N, D>
where
DefaultAllocator: Allocator<D, D> + Allocator<D>,
{
pub fn predict_linear<'e, QD: Dim>(
&mut self,
pred_inv: OMatrix<N, D, D>, noise: &CoupledNoise<N, D, QD>,
) -> Result<N, &'e str>
where
DefaultAllocator:
Allocator<QD, QD> + Allocator<D, QD> + Allocator<QD, D> + Allocator<QD>,
{
let I_shape = self.I.shape_generic();
let A = (&self.I * &pred_inv).tr_mul(&pred_inv);
let mut B = (&A * &noise.G).tr_mul(&noise.G);
for i in 0..noise.q.nrows() {
B[(i, i)] += N::one() / noise.q[i];
}
let brcond = rcond::rcond_symmetric(&B);
rcond::check_positive(brcond, "(G'invFx'.I.inv(Fx).G + inv(Q)) not PD")?;
B = B.cholesky().ok_or("B not PD")?.inverse();
self.I.quadform_tr(N::one(), &noise.G, &B, N::zero());
let ig = OMatrix::identity_generic(I_shape.0, I_shape.1) - &A * &self.I;
self.I = &ig * &A;
let y = pred_inv.tr_mul(&self.i);
self.i = &ig * &y;
Ok(brcond)
}
pub fn add_information(&mut self, information: &InformationState<N, D>) {
self.i += &information.i;
self.I += &information.I;
}
pub fn observe_info<ZD: Dim>(
&self,
hx: &OMatrix<N, ZD, D>,
noise_inv: &OMatrix<N, ZD, ZD>, z: &OVector<N, ZD>,
) -> InformationState<N, D>
where
DefaultAllocator:
Allocator<ZD, ZD> + Allocator<ZD, D> + Allocator<D, ZD> + Allocator<ZD>,
{
let HxTZI = hx.tr_mul(noise_inv);
let ii = &HxTZI * z;
let II = &HxTZI * hx;
InformationState { i: ii, I: II }
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::{Matrix2, Vector2};
#[test]
fn test_predict_linear_with_partial_zero_noise() {
let i = Vector2::new(1.0, 2.0);
let I = Matrix2::new(2.0, 0.0, 0.0, 2.0);
let mut info_state = InformationState { i, I };
let pred_inv = Matrix2::identity();
let q = Vector2::new(0.1, 0.0);
let G = Matrix2::identity();
let noise = CoupledNoise { q, G };
let result = info_state.predict_linear(pred_inv, &noise);
assert!(!result.is_ok(), "predict_linear should fail with partial zero noise");
}
#[test]
fn test_predict_linear_with_zero_coupling() {
let i = Vector2::new(1.0, 2.0);
let I = Matrix2::new(2.0, 0.0, 0.0, 2.0);
let mut info_state = InformationState { i, I };
let pred_inv = Matrix2::identity();
let q = Vector2::new(0.1, 0.1);
let G = Matrix2::zeros();
let noise = CoupledNoise { q, G };
let result = info_state.predict_linear(pred_inv, &noise);
}
}