pub use crate::errors::NyxError;
use crate::linalg::allocator::Allocator;
use crate::linalg::{DefaultAllocator, DimName, OMatrix, OVector};
pub use crate::od::estimate::{Estimate, KfEstimate, Residual};
use crate::od::prelude::KalmanVariant;
use crate::od::process::SigmaRejection;
pub use crate::od::snc::ProcessNoise;
use crate::od::{ODDynamicsSnafu, ODError, State};
pub use crate::time::{Epoch, Unit};
use log::info;
use snafu::prelude::*;
use super::KalmanFilter;
impl<T, A> KalmanFilter<T, A>
where
A: DimName,
T: State,
DefaultAllocator: Allocator<<T as State>::Size>
+ Allocator<<T as State>::VecLength>
+ Allocator<A>
+ Allocator<<T as State>::Size, <T as State>::Size>
+ Allocator<A, A>
+ Allocator<<T as State>::Size, A>
+ Allocator<A, <T as State>::Size>,
<DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
<DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
{
pub fn previous_estimate(&self) -> &KfEstimate<T> {
&self.prev_estimate
}
pub fn set_previous_estimate(&mut self, est: &KfEstimate<T>) {
self.prev_estimate = *est;
}
pub fn time_update(&mut self, nominal_state: T) -> Result<KfEstimate<T>, ODError> {
let stm = nominal_state.stm().context(ODDynamicsSnafu)?;
let mut covar_bar = stm * self.prev_estimate.covar * stm.transpose();
for (i, snc) in self.process_noise.iter().enumerate().rev() {
if let Some(snc_contrib) = snc.propagate::<<T as State>::Size>(
nominal_state.orbit(),
nominal_state.epoch() - self.prev_estimate.epoch(),
)? {
if self.prev_used_snc != i {
info!("Switched to {i}-th {snc}");
self.prev_used_snc = i;
}
covar_bar += snc_contrib;
break;
}
}
let state_bar = if matches!(self.variant, KalmanVariant::DeviationTracking) {
stm * self.prev_estimate.state_deviation
} else {
OVector::<f64, <T as State>::Size>::zeros()
};
let estimate = KfEstimate {
nominal_state,
state_deviation: state_bar,
covar: covar_bar,
covar_bar,
stm,
predicted: true,
};
self.prev_estimate = estimate;
for snc in &mut self.process_noise {
snc.prev_epoch = Some(self.prev_estimate.epoch());
}
Ok(estimate)
}
pub fn measurement_update<M: DimName>(
&mut self,
nominal_state: T,
real_obs: OVector<f64, M>,
computed_obs: OVector<f64, M>,
r_k: OMatrix<f64, M, M>,
h_tilde: OMatrix<f64, M, <T as State>::Size>,
resid_rejection: Option<SigmaRejection>,
) -> Result<
(
KfEstimate<T>,
Residual<M>,
Option<OMatrix<f64, <T as State>::Size, M>>,
),
ODError,
>
where
DefaultAllocator: Allocator<M>
+ Allocator<M, M>
+ Allocator<M, <T as State>::Size>
+ Allocator<<T as State>::Size, M>
+ Allocator<nalgebra::Const<1>, M>,
{
let epoch = nominal_state.epoch();
let stm = nominal_state.stm().context(ODDynamicsSnafu)?;
let mut covar_bar = stm * self.prev_estimate.covar * stm.transpose();
for (i, snc) in self.process_noise.iter().enumerate().rev() {
if let Some(snc_contrib) = snc.propagate::<<T as State>::Size>(
nominal_state.orbit(),
nominal_state.epoch() - self.prev_estimate.epoch(),
)? {
if self.prev_used_snc != i {
info!("Switched to {i}-th {snc}");
self.prev_used_snc = i;
}
covar_bar += snc_contrib;
break;
}
}
let p_ht = covar_bar * h_tilde.transpose();
let h_p_ht = &h_tilde * &p_ht;
let s_k = &h_p_ht + &r_k;
let prefit = real_obs.clone() - computed_obs.clone();
let s_k_chol = match s_k.clone().cholesky() {
Some(r_k_clone) => r_k_clone,
None => {
r_k.clone().cholesky().ok_or(ODError::SingularNoiseRk)?
}
};
let l_matrix = s_k_chol.l();
let whitened_resid = l_matrix.solve_lower_triangular(&prefit).unwrap();
let ratio = (whitened_resid.norm_squared() / (M::DIM as f64)).sqrt();
let innovation_trend = s_k.diagonal().map(|x| x.sqrt());
if let Some(resid_reject) = resid_rejection
&& ratio > resid_reject.num_sigmas
{
let pred_est = self.time_update(nominal_state)?;
let resid = Residual::rejected(
epoch,
prefit,
whitened_resid,
ratio,
innovation_trend,
real_obs,
computed_obs,
);
return Ok((pred_est, resid, None));
}
let rhs = p_ht.transpose();
let gain = match s_k.clone().cholesky() {
Some(chol) => {
let k_t = chol.solve(&rhs);
k_t.transpose()
}
None => {
match s_k.try_inverse() {
Some(s_k_inv) => covar_bar * &h_tilde.transpose() * &s_k_inv,
None => {
eprintln!(
"SINGULAR GAIN\nr = {r_k}\nh = {h_tilde:.3e}\ncovar = {covar_bar:.3e}"
);
return Err(ODError::SingularKalmanGain);
}
}
}
};
let (state_hat, res) = match self.variant {
KalmanVariant::ReferenceUpdate => {
let state_hat = &gain * &prefit;
let postfit = &prefit - (&h_tilde * state_hat);
let resid = Residual::accepted(
epoch,
prefit,
whitened_resid,
postfit,
ratio,
innovation_trend,
real_obs,
computed_obs,
);
(state_hat, resid)
}
KalmanVariant::DeviationTracking => {
let state_bar = stm * self.prev_estimate.state_deviation;
let postfit = &prefit - (&h_tilde * state_bar);
(
state_bar + &gain * &postfit,
Residual::accepted(
epoch,
prefit,
whitened_resid,
postfit,
ratio,
innovation_trend,
real_obs,
computed_obs,
),
)
}
};
let first_term =
OMatrix::<f64, <T as State>::Size, <T as State>::Size>::identity() - &gain * &h_tilde;
let covar =
first_term * covar_bar * first_term.transpose() + &gain * &r_k * &gain.transpose();
let covar = 0.5 * (covar + covar.transpose());
let estimate = KfEstimate {
nominal_state,
state_deviation: state_hat,
covar,
covar_bar,
stm,
predicted: false,
};
self.prev_estimate = estimate;
for snc in &mut self.process_noise {
snc.prev_epoch = Some(self.prev_estimate.epoch());
}
Ok((estimate, res, Some(gain)))
}
pub fn replace_state(&self) -> bool {
matches!(self.variant, KalmanVariant::ReferenceUpdate)
}
pub fn set_process_noise(&mut self, snc: ProcessNoise<A>) {
self.process_noise = vec![snc];
}
}