use crate::cosmic::AstroPhysicsSnafu;
use crate::dynamics::guidance::LocalFrame;
use crate::errors::StateAstroSnafu;
use crate::linalg::allocator::Allocator;
use crate::linalg::{DefaultAllocator, DimName, OMatrix, OVector, U3, U6};
use crate::md::StateParameter;
use crate::od::{ODError, ODStateSnafu};
use crate::time::{Duration, Epoch};
use anise::prelude::Orbit;
use log::debug;
use snafu::ResultExt;
use std::fmt;
#[allow(clippy::upper_case_acronyms)]
pub type ProcessNoise3D = ProcessNoise<U3>;
#[allow(clippy::upper_case_acronyms)]
pub type ProcessNoise6D = ProcessNoise<U6>;
#[derive(Clone, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub struct ProcessNoise<A: DimName>
where
DefaultAllocator: Allocator<A> + Allocator<A, A>,
{
pub start_time: Option<Epoch>,
pub local_frame: Option<LocalFrame>,
pub disable_time: Duration,
pub init_epoch: Option<Epoch>,
diag: OVector<f64, A>,
pub decay_diag: Option<Vec<f64>>,
pub prev_epoch: Option<Epoch>,
}
impl<A> fmt::Debug for ProcessNoise<A>
where
A: DimName,
DefaultAllocator: Allocator<A> + Allocator<A, A>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut fmt_cov = Vec::with_capacity(A::dim());
if let Some(decay) = &self.decay_diag {
for (i, dv) in decay.iter().enumerate() {
fmt_cov.push(format!(
"{:.1e} × exp(- {:.1e} × t)",
self.diag[i] * 1e6,
dv
));
}
} else {
for i in 0..A::dim() {
fmt_cov.push(format!("{:.1e}", self.diag[i] * 1e6));
}
}
write!(
f,
"Process noise: diag({}) mm/s^2 {} {}",
fmt_cov.join(", "),
if let Some(lf) = self.local_frame {
format!("in {lf:?}")
} else {
"".to_string()
},
if let Some(start) = self.start_time {
format!("starting at {start}")
} else {
"".to_string()
}
)
}
}
impl<A> fmt::Display for ProcessNoise<A>
where
A: DimName,
DefaultAllocator: Allocator<A> + Allocator<A, A>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl<A: DimName> ProcessNoise<A>
where
DefaultAllocator: Allocator<A> + Allocator<A, A>,
{
pub fn from_diagonal(
values: &[f64],
disable_time: Duration,
local_frame: Option<LocalFrame>,
) -> Self {
assert_eq!(
values.len(),
A::dim(),
"Not enough values for the size of the SNC matrix"
);
let mut diag = OVector::zeros();
for (i, v) in values.iter().enumerate() {
diag[i] = *v;
}
Self {
diag,
disable_time,
start_time: None,
local_frame,
decay_diag: None,
init_epoch: None,
prev_epoch: None,
}
}
pub fn with_start_time(disable_time: Duration, values: &[f64], start_time: Epoch) -> Self {
let mut me = Self::from_diagonal(values, disable_time, None);
me.start_time = Some(start_time);
me
}
pub fn with_decay(
values: &[f64],
disable_time: Duration,
decay_constants_s: &[f64],
local_frame: Option<LocalFrame>,
) -> Self {
assert_eq!(
decay_constants_s.len(),
A::dim(),
"Not enough decay constants for the size of the SNC matrix"
);
let mut me = Self::from_diagonal(values, disable_time, local_frame);
me.decay_diag = Some(decay_constants_s.to_vec());
me
}
pub fn to_matrix(&self, epoch: Epoch) -> Option<OMatrix<f64, A, A>> {
if let Some(start_time) = self.start_time
&& start_time > epoch
{
debug!("@{epoch} SNC starts at {start_time}");
return None;
}
if let Some(prev_epoch) = self.prev_epoch
&& epoch - prev_epoch > self.disable_time
{
debug!(
"@{} SNC disabled: prior use greater than {}",
epoch, self.disable_time
);
return None;
}
let mut snc = OMatrix::<f64, A, A>::zeros();
for i in 0..self.diag.nrows() {
snc[(i, i)] = self.diag[i];
}
if let Some(decay) = &self.decay_diag {
let total_delta_t = (epoch - self.init_epoch.unwrap()).to_seconds();
for i in 0..self.diag.nrows() {
snc[(i, i)] *= (-decay[i] * total_delta_t).exp();
}
}
debug!(
"@{} SNC diag {:?}",
epoch,
snc.diagonal().iter().copied().collect::<Vec<f64>>()
);
Some(snc)
}
pub fn propagate<S: DimName>(
&self,
nominal_orbit: Orbit,
delta_t: Duration,
) -> Result<Option<OMatrix<f64, S, S>>, ODError>
where
DefaultAllocator: Allocator<S> + Allocator<S, S> + Allocator<S, A> + Allocator<A, S>,
{
if let Some(mut snc_matrix) = self.to_matrix(nominal_orbit.epoch) {
if let Some(local_frame) = self.local_frame {
let dcm = nominal_orbit
.dcm_to_inertial(local_frame)
.context(AstroPhysicsSnafu)
.context(StateAstroSnafu {
param: StateParameter::Epoch(),
})
.context(ODStateSnafu {
action: "rotating SNC from definition frame into state frame",
})?;
match A::DIM {
3 => {
let new_snc = dcm.rot_mat
* snc_matrix.fixed_view::<3, 3>(0, 0)
* dcm.rot_mat.transpose();
for i in 0..A::DIM {
snc_matrix[(i, i)] = new_snc[(i, i)];
}
}
6 => {
let new_snc = dcm.state_dcm()
* snc_matrix.fixed_view::<6, 6>(0, 0)
* dcm.transpose().state_dcm();
for i in 0..A::DIM {
snc_matrix[(i, i)] = new_snc[(i, i)];
}
}
_ => {
return Err(ODError::ODLimitation {
action: "only process noises of size 3x3 or 6x6 are supported",
});
}
}
}
if delta_t > self.disable_time {
return Ok(None);
}
let mut gamma = OMatrix::<f64, S, A>::zeros();
let delta_t = delta_t.to_seconds();
for blk in 0..A::dim() / 3 {
for i in 0..3 {
let idx_i = i + A::dim() * blk;
let idx_j = i + 3 * blk;
let idx_k = i + 3 + A::dim() * blk;
gamma[(idx_i, idx_j)] = delta_t.powi(2) / 2.0;
gamma[(idx_k, idx_j)] = delta_t;
}
}
Ok(Some(&gamma * snc_matrix * &gamma.transpose()))
} else {
Ok(None)
}
}
}
impl ProcessNoise3D {
pub fn from_velocity_km_s(
velocity_noise: &[f64; 3],
noise_duration: Duration,
disable_time: Duration,
local_frame: Option<LocalFrame>,
) -> Self {
let mut diag = OVector::<f64, U3>::zeros();
for (i, v) in velocity_noise.iter().enumerate() {
diag[i] = *v / noise_duration.to_seconds();
}
Self {
diag,
disable_time,
start_time: None,
local_frame,
decay_diag: None,
init_epoch: None,
prev_epoch: None,
}
}
}
#[test]
fn test_snc_init() {
use crate::time::Unit;
let snc_expo = ProcessNoise3D::with_decay(
&[1e-6, 1e-6, 1e-6],
2 * Unit::Minute,
&[3600.0, 3600.0, 3600.0],
None,
);
println!("{snc_expo}");
let snc_std = ProcessNoise3D::with_start_time(
2 * Unit::Minute,
&[1e-6, 1e-6, 1e-6],
Epoch::from_et_seconds(3600.0),
);
println!("{snc_std}");
let snc_vel = ProcessNoise3D::from_velocity_km_s(
&[1e-2, 1e-2, 1e-2],
Unit::Hour * 2,
Unit::Minute * 2,
Some(LocalFrame::RIC),
);
let diag_val = 1e-2 / (Unit::Hour * 2).to_seconds();
assert_eq!(
snc_vel
.to_matrix(Epoch::from_et_seconds(3600.0))
.unwrap()
.diagonal(),
OVector::<f64, U3>::new(diag_val, diag_val, diag_val)
);
assert_eq!(snc_vel.local_frame, Some(LocalFrame::RIC));
}