1use super::{Estimate, State};
20use crate::cosmic::{AstroError, AstroPhysicsSnafu};
21use crate::linalg::allocator::Allocator;
22use crate::linalg::{DefaultAllocator, DimName, Matrix, OMatrix, OVector};
23use crate::mc::{MvnSpacecraft, StateDispersion};
24use crate::{NyxError, Spacecraft};
25use anise::analysis::prelude::OrbitalElement;
26use anise::astro::orbit_gradient::OrbitGrad;
27use nalgebra::Const;
28use nalgebra::SMatrix;
29use rand::SeedableRng;
30use rand::rngs::SysRng;
31use rand_distr::Distribution;
32use rand_pcg::Pcg64Mcg;
33use snafu::ResultExt;
34use std::cmp::PartialEq;
35use std::error::Error;
36use std::fmt;
37use std::ops::Mul;
38
39#[derive(Debug, Copy, Clone, PartialEq)]
41pub struct KfEstimate<T: State>
42where
43 DefaultAllocator: Allocator<<T as State>::Size>
44 + Allocator<<T as State>::Size, <T as State>::Size>
45 + Allocator<<T as State>::Size>
46 + Allocator<<T as State>::VecLength>,
47 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
48 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
49{
50 pub nominal_state: T,
52 pub state_deviation: OVector<f64, <T as State>::Size>,
54 pub covar: OMatrix<f64, <T as State>::Size, <T as State>::Size>,
56 pub covar_bar: OMatrix<f64, <T as State>::Size, <T as State>::Size>,
58 pub predicted: bool,
60 pub stm: OMatrix<f64, <T as State>::Size, <T as State>::Size>,
62}
63
64impl<T: State> KfEstimate<T>
65where
66 DefaultAllocator: Allocator<<T as State>::Size>
67 + Allocator<<T as State>::Size, <T as State>::Size>
68 + Allocator<<T as State>::Size>
69 + Allocator<<T as State>::VecLength>,
70 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
71 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
72{
73 pub fn from_covar(
75 nominal_state: T,
76 covar: OMatrix<f64, <T as State>::Size, <T as State>::Size>,
77 ) -> Self {
78 Self {
79 nominal_state,
80 state_deviation: OVector::<f64, <T as State>::Size>::zeros(),
81 covar,
82 covar_bar: covar,
83 predicted: true,
84 stm: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::identity(),
85 }
86 }
87
88 pub fn from_diag(nominal_state: T, diag: OVector<f64, <T as State>::Size>) -> Self {
90 let covar = Matrix::from_diagonal(&diag);
91 Self {
92 nominal_state,
93 state_deviation: OVector::<f64, <T as State>::Size>::zeros(),
94 covar,
95 covar_bar: covar,
96 predicted: true,
97 stm: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::identity(),
98 }
99 }
100}
101
102impl KfEstimate<Spacecraft> {
103 pub fn from_dispersions(
109 nominal_state: Spacecraft,
110 dispersions: Vec<StateDispersion>,
111 seed: Option<u128>,
112 ) -> Result<Self, Box<dyn Error>> {
113 let generator = MvnSpacecraft::new(nominal_state, dispersions)?;
114
115 let mut rng = match seed {
116 Some(seed) => Pcg64Mcg::new(seed),
117 None => Pcg64Mcg::try_from_rng(&mut SysRng).unwrap(),
118 };
119 let dispersed_state = generator.sample(&mut rng);
120
121 let delta_orbit = (nominal_state.orbit - dispersed_state.state.orbit).unwrap();
123
124 let state_deviation = [
125 delta_orbit.radius_km.x,
126 delta_orbit.radius_km.y,
127 delta_orbit.radius_km.z,
128 delta_orbit.velocity_km_s.x,
129 delta_orbit.velocity_km_s.y,
130 delta_orbit.velocity_km_s.z,
131 (nominal_state.srp.coeff_reflectivity - dispersed_state.state.srp.coeff_reflectivity),
132 (nominal_state.drag.coeff_drag - dispersed_state.state.drag.coeff_drag),
133 (nominal_state.mass.prop_mass_kg - dispersed_state.state.mass.prop_mass_kg),
134 ];
135
136 let diag_data = state_deviation
138 .iter()
139 .map(|v| (3.0 * v.abs()).powi(2))
140 .collect::<Vec<f64>>();
141
142 let diag = OVector::<f64, Const<9>>::from_iterator(diag_data);
143
144 let covar = Matrix::from_diagonal(&diag);
146
147 Ok(Self {
148 nominal_state, state_deviation: OVector::<f64, Const<9>>::from_iterator(state_deviation),
150 covar,
151 covar_bar: covar,
152 predicted: true,
153 stm: OMatrix::<f64, Const<9>, Const<9>>::identity(),
154 })
155 }
156
157 pub fn to_random_variable(&self) -> Result<MvnSpacecraft, Box<NyxError>> {
159 MvnSpacecraft::from_spacecraft_cov(self.nominal_state, self.covar, self.state_deviation)
160 }
161
162 pub fn sigma_for(&self, param: OrbitalElement) -> Result<f64, AstroError> {
167 let mut rotmat = SMatrix::<f64, 1, 6>::zeros();
169 let orbit_dual = OrbitGrad::from(self.nominal_state.orbit);
170
171 let xf_partial = orbit_dual.partial_for(param).context(AstroPhysicsSnafu)?;
172 for (cno, val) in [
173 xf_partial.wrt_x(),
174 xf_partial.wrt_y(),
175 xf_partial.wrt_z(),
176 xf_partial.wrt_vx(),
177 xf_partial.wrt_vy(),
178 xf_partial.wrt_vz(),
179 ]
180 .iter()
181 .copied()
182 .enumerate()
183 {
184 rotmat[(0, cno)] = val;
185 }
186
187 Ok((rotmat * self.covar.fixed_view::<6, 6>(0, 0) * rotmat.transpose())[(0, 0)].sqrt())
188 }
189
190 pub fn keplerian_covar(&self) -> SMatrix<f64, 6, 6> {
192 let mut rotmat = SMatrix::<f64, 6, 6>::zeros();
194 let orbit_dual = OrbitGrad::from(self.nominal_state.orbit);
195 for (pno, param) in [
196 OrbitalElement::SemiMajorAxis,
197 OrbitalElement::Eccentricity,
198 OrbitalElement::Inclination,
199 OrbitalElement::RAAN,
200 OrbitalElement::AoP,
201 OrbitalElement::TrueAnomaly,
202 ]
203 .iter()
204 .copied()
205 .enumerate()
206 {
207 let xf_partial = orbit_dual.partial_for(param).unwrap();
208 for (cno, val) in [
209 xf_partial.wrt_x(),
210 xf_partial.wrt_y(),
211 xf_partial.wrt_z(),
212 xf_partial.wrt_vx(),
213 xf_partial.wrt_vy(),
214 xf_partial.wrt_vz(),
215 ]
216 .iter()
217 .copied()
218 .enumerate()
219 {
220 rotmat[(pno, cno)] = val;
221 }
222 }
223
224 rotmat * self.covar.fixed_view::<6, 6>(0, 0) * rotmat.transpose()
225 }
226}
227
228impl<T: State> Estimate<T> for KfEstimate<T>
229where
230 DefaultAllocator: Allocator<<T as State>::Size>
231 + Allocator<<T as State>::Size, <T as State>::Size>
232 + Allocator<<T as State>::Size>
233 + Allocator<<T as State>::VecLength>,
234 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
235 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
236{
237 fn zeros(nominal_state: T) -> Self {
238 Self {
239 nominal_state,
240 state_deviation: OVector::<f64, <T as State>::Size>::zeros(),
241 covar: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::zeros(),
242 covar_bar: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::zeros(),
243 predicted: true,
244 stm: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::identity(),
245 }
246 }
247
248 fn nominal_state(&self) -> T {
249 self.nominal_state
250 }
251
252 fn state_deviation(&self) -> OVector<f64, <T as State>::Size> {
253 self.state_deviation
254 }
255
256 fn covar(&self) -> OMatrix<f64, <T as State>::Size, <T as State>::Size> {
257 self.covar
258 }
259
260 fn predicted_covar(&self) -> OMatrix<f64, <T as State>::Size, <T as State>::Size> {
261 self.covar_bar
262 }
263
264 fn predicted(&self) -> bool {
265 self.predicted
266 }
267 fn stm(&self) -> &OMatrix<f64, <T as State>::Size, <T as State>::Size> {
268 &self.stm
269 }
270 fn set_state_deviation(&mut self, new_state: OVector<f64, <T as State>::Size>) {
271 self.state_deviation = new_state;
272 }
273 fn set_covar(&mut self, new_covar: OMatrix<f64, <T as State>::Size, <T as State>::Size>) {
274 self.covar = new_covar;
275 }
276}
277
278impl<T: State> fmt::Display for KfEstimate<T>
279where
280 DefaultAllocator: Allocator<<T as State>::Size>
281 + Allocator<<T as State>::Size, <T as State>::Size>
282 + Allocator<<T as State>::Size>
283 + Allocator<<T as State>::VecLength>,
284 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
285 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
286{
287 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 let dim = <T as State>::Size::dim();
289 let word = if self.predicted {
290 "Prediction"
291 } else {
292 "Estimate"
293 };
294 let mut fmt_cov = Vec::with_capacity(dim);
295 for i in 0..dim {
296 let unit = if i < 3 {
297 "m"
298 } else if i < 6 {
299 "m/s"
300 } else {
301 ""
302 };
303 let val = &self.covar[(i, i)] * 1e3;
305 if val.abs() < 1e-3 {
306 fmt_cov.push(format!("{val:.6e} {unit}"));
307 } else {
308 fmt_cov.push(format!("{val:.6} {unit}"));
309 }
310 }
311 write!(
312 f,
313 "=== {word} @ {} -- within 3 sigma: {} ===\nstate {}\nsigmas [{}]\n",
314 &self.epoch(),
315 self.within_3sigma(),
316 &self.state(),
317 fmt_cov.join(", ")
318 )
319 }
320}
321
322impl<T: State> fmt::LowerExp for KfEstimate<T>
323where
324 DefaultAllocator: Allocator<<T as State>::Size>
325 + Allocator<<T as State>::Size, <T as State>::Size>
326 + Allocator<<T as State>::Size>
327 + Allocator<<T as State>::VecLength>,
328 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
329 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
330{
331 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
332 let dim = <T as State>::Size::dim();
333 let word = if self.predicted {
334 "Prediction"
335 } else {
336 "Estimate"
337 };
338 let mut fmt_cov = Vec::with_capacity(dim);
339 for i in 0..dim {
340 let unit = if i < 3 {
341 "km"
342 } else if i < 6 {
343 "km/s"
344 } else {
345 ""
346 };
347 fmt_cov.push(format!("{:e} {unit}", &self.covar[(i, i)]));
348 }
349 write!(
350 f,
351 "=== {} @ {} -- within 3 sigma: {} ===\nstate {}\nsigmas [{}]\n",
352 word,
353 &self.epoch(),
354 self.within_3sigma(),
355 &self.state(),
356 fmt_cov.join(", ")
357 )
358 }
359}
360
361impl<T: State> Mul<f64> for KfEstimate<T>
362where
363 DefaultAllocator: Allocator<<T as State>::Size>
364 + Allocator<<T as State>::Size, <T as State>::Size>
365 + Allocator<<T as State>::Size>
366 + Allocator<<T as State>::VecLength>,
367 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
368 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
369{
370 type Output = Self;
371
372 fn mul(mut self, rhs: f64) -> Self::Output {
373 self.covar *= rhs.powi(2);
374 self
375 }
376}
377
378#[cfg(test)]
379mod ut_kfest {
380 use crate::{
381 GMAT_EARTH_GM, Spacecraft, mc::StateDispersion, md::StateParameter,
382 od::estimate::KfEstimate,
383 };
384 use anise::analysis::prelude::OrbitalElement;
385 use anise::{constants::frames::EARTH_J2000, prelude::Orbit};
386 use hifitime::Epoch;
387
388 #[test]
389 fn test_estimate_from_disp() {
390 let eme2k = EARTH_J2000.with_mu_km3_s2(GMAT_EARTH_GM);
391 let dt = Epoch::from_gregorian_tai_at_midnight(2020, 1, 1);
392 let initial_state = Spacecraft::builder()
393 .orbit(Orbit::keplerian(
394 22000.0, 0.01, 30.0, 80.0, 40.0, 0.0, dt, eme2k,
395 ))
396 .build();
397
398 let initial_estimate = KfEstimate::from_dispersions(
399 initial_state,
400 vec![
401 StateDispersion::builder()
402 .param(StateParameter::Element(OrbitalElement::SemiMajorAxis))
403 .std_dev(1.1)
404 .build(),
405 StateDispersion::zero_mean(
406 StateParameter::Element(OrbitalElement::Inclination),
407 0.2,
408 ),
409 StateDispersion::zero_mean(StateParameter::Element(OrbitalElement::RAAN), 0.2),
410 StateDispersion::zero_mean(StateParameter::Element(OrbitalElement::AoP), 0.2),
411 ],
412 Some(0),
413 )
414 .unwrap();
415
416 let initial_state_dev = initial_estimate.nominal_state;
417
418 let (init_rss_pos_km, init_rss_vel_km_s, _) =
419 initial_state.rss(&initial_state_dev).unwrap();
420
421 let delta = (initial_state.orbit - initial_state_dev.orbit).unwrap();
422
423 println!("Truth initial state:\n{initial_state}\n{initial_state:x}");
424 println!("Filter initial state:\n{initial_state_dev}\n{initial_state_dev:x}");
425 println!(
426 "Initial state dev:\t{init_rss_pos_km:.6} km\t{init_rss_vel_km_s:.6} km/s\n{delta}",
427 );
428 println!("covariance: {:.6}", initial_estimate.covar);
429
430 assert!(delta.radius_km.x < initial_estimate.covar[(0, 0)].sqrt());
432 assert!(delta.radius_km.y < initial_estimate.covar[(1, 1)].sqrt());
433 assert!(delta.radius_km.z < initial_estimate.covar[(2, 2)].sqrt());
434 assert!(delta.velocity_km_s.x < initial_estimate.covar[(3, 3)].sqrt());
435 assert!(delta.velocity_km_s.y < initial_estimate.covar[(4, 4)].sqrt());
436 assert!(delta.velocity_km_s.z < initial_estimate.covar[(5, 5)].sqrt());
437 }
438}