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::Spacecraft;
25use anise::analysis::prelude::OrbitalElement;
26use anise::astro::orbit_gradient::OrbitGrad;
27use nalgebra::Const;
28use nalgebra::SMatrix;
29use rand::rngs::SysRng;
30use rand::SeedableRng;
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 disperse_from_diag(
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 diag_data = [
127 (3.0 * delta_orbit.radius_km.x.abs()).powi(2),
128 (3.0 * delta_orbit.radius_km.y.abs()).powi(2),
129 (3.0 * delta_orbit.radius_km.z.abs()).powi(2),
130 (3.0 * delta_orbit.velocity_km_s.x.abs()).powi(2),
131 (3.0 * delta_orbit.velocity_km_s.y.abs()).powi(2),
132 (3.0 * delta_orbit.velocity_km_s.z.abs()).powi(2),
133 (3.0 * (nominal_state.srp.coeff_reflectivity
134 - dispersed_state.state.srp.coeff_reflectivity)
135 .abs())
136 .powi(2),
137 (3.0 * (nominal_state.drag.coeff_drag - dispersed_state.state.drag.coeff_drag).abs())
138 .powi(2),
139 (3.0 * (nominal_state.mass.prop_mass_kg - dispersed_state.state.mass.prop_mass_kg)
140 .abs())
141 .powi(2),
142 ];
143
144 let diag = OVector::<f64, Const<9>>::from_iterator(diag_data);
145
146 let covar = Matrix::from_diagonal(&diag);
148
149 Ok(Self {
150 nominal_state: dispersed_state.state,
151 state_deviation: OVector::<f64, Const<9>>::zeros(),
152 covar,
153 covar_bar: covar,
154 predicted: true,
155 stm: OMatrix::<f64, Const<9>, Const<9>>::identity(),
156 })
157 }
158
159 pub fn to_random_variable(&self) -> Result<MvnSpacecraft, Box<dyn Error>> {
161 MvnSpacecraft::from_spacecraft_cov(self.nominal_state, self.covar, self.state_deviation)
162 }
163
164 pub fn sigma_for(&self, param: OrbitalElement) -> Result<f64, AstroError> {
169 let mut rotmat = SMatrix::<f64, 1, 6>::zeros();
171 let orbit_dual = OrbitGrad::from(self.nominal_state.orbit);
172
173 let xf_partial = orbit_dual.partial_for(param).context(AstroPhysicsSnafu)?;
174 for (cno, val) in [
175 xf_partial.wrt_x(),
176 xf_partial.wrt_y(),
177 xf_partial.wrt_z(),
178 xf_partial.wrt_vx(),
179 xf_partial.wrt_vy(),
180 xf_partial.wrt_vz(),
181 ]
182 .iter()
183 .copied()
184 .enumerate()
185 {
186 rotmat[(0, cno)] = val;
187 }
188
189 Ok((rotmat * self.covar.fixed_view::<6, 6>(0, 0) * rotmat.transpose())[(0, 0)].sqrt())
190 }
191
192 pub fn keplerian_covar(&self) -> SMatrix<f64, 6, 6> {
194 let mut rotmat = SMatrix::<f64, 6, 6>::zeros();
196 let orbit_dual = OrbitGrad::from(self.nominal_state.orbit);
197 for (pno, param) in [
198 OrbitalElement::SemiMajorAxis,
199 OrbitalElement::Eccentricity,
200 OrbitalElement::Inclination,
201 OrbitalElement::RAAN,
202 OrbitalElement::AoP,
203 OrbitalElement::TrueAnomaly,
204 ]
205 .iter()
206 .copied()
207 .enumerate()
208 {
209 let xf_partial = orbit_dual.partial_for(param).unwrap();
210 for (cno, val) in [
211 xf_partial.wrt_x(),
212 xf_partial.wrt_y(),
213 xf_partial.wrt_z(),
214 xf_partial.wrt_vx(),
215 xf_partial.wrt_vy(),
216 xf_partial.wrt_vz(),
217 ]
218 .iter()
219 .copied()
220 .enumerate()
221 {
222 rotmat[(pno, cno)] = val;
223 }
224 }
225
226 rotmat * self.covar.fixed_view::<6, 6>(0, 0) * rotmat.transpose()
227 }
228}
229
230impl<T: State> Estimate<T> for KfEstimate<T>
231where
232 DefaultAllocator: Allocator<<T as State>::Size>
233 + Allocator<<T as State>::Size, <T as State>::Size>
234 + Allocator<<T as State>::Size>
235 + Allocator<<T as State>::VecLength>,
236 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
237 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
238{
239 fn zeros(nominal_state: T) -> Self {
240 Self {
241 nominal_state,
242 state_deviation: OVector::<f64, <T as State>::Size>::zeros(),
243 covar: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::zeros(),
244 covar_bar: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::zeros(),
245 predicted: true,
246 stm: OMatrix::<f64, <T as State>::Size, <T as State>::Size>::identity(),
247 }
248 }
249
250 fn nominal_state(&self) -> T {
251 self.nominal_state
252 }
253
254 fn state_deviation(&self) -> OVector<f64, <T as State>::Size> {
255 self.state_deviation
256 }
257
258 fn covar(&self) -> OMatrix<f64, <T as State>::Size, <T as State>::Size> {
259 self.covar
260 }
261
262 fn predicted_covar(&self) -> OMatrix<f64, <T as State>::Size, <T as State>::Size> {
263 self.covar_bar
264 }
265
266 fn predicted(&self) -> bool {
267 self.predicted
268 }
269 fn stm(&self) -> &OMatrix<f64, <T as State>::Size, <T as State>::Size> {
270 &self.stm
271 }
272 fn set_state_deviation(&mut self, new_state: OVector<f64, <T as State>::Size>) {
273 self.state_deviation = new_state;
274 }
275 fn set_covar(&mut self, new_covar: OMatrix<f64, <T as State>::Size, <T as State>::Size>) {
276 self.covar = new_covar;
277 }
278}
279
280impl<T: State> fmt::Display for KfEstimate<T>
281where
282 DefaultAllocator: Allocator<<T as State>::Size>
283 + Allocator<<T as State>::Size, <T as State>::Size>
284 + Allocator<<T as State>::Size>
285 + Allocator<<T as State>::VecLength>,
286 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
287 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
288{
289 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
290 let dim = <T as State>::Size::dim();
291 let word = if self.predicted {
292 "Prediction"
293 } else {
294 "Estimate"
295 };
296 let mut fmt_cov = Vec::with_capacity(dim);
297 for i in 0..dim {
298 let unit = if i < 3 {
299 "km"
300 } else if i < 6 {
301 "km/s"
302 } else {
303 ""
304 };
305 fmt_cov.push(format!("{:.6} {unit}", &self.covar[(i, i)]));
306 }
307 write!(
308 f,
309 "=== {} @ {} -- within 3 sigma: {} ===\nstate {}\nsigmas [{}]\n",
310 word,
311 &self.epoch(),
312 self.within_3sigma(),
313 &self.state(),
314 fmt_cov.join(", ")
315 )
316 }
317}
318
319impl<T: State> fmt::LowerExp for KfEstimate<T>
320where
321 DefaultAllocator: Allocator<<T as State>::Size>
322 + Allocator<<T as State>::Size, <T as State>::Size>
323 + Allocator<<T as State>::Size>
324 + Allocator<<T as State>::VecLength>,
325 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
326 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
327{
328 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
329 let dim = <T as State>::Size::dim();
330 let word = if self.predicted {
331 "Prediction"
332 } else {
333 "Estimate"
334 };
335 let mut fmt_cov = Vec::with_capacity(dim);
336 for i in 0..dim {
337 let unit = if i < 3 {
338 "km"
339 } else if i < 6 {
340 "km/s"
341 } else {
342 ""
343 };
344 fmt_cov.push(format!("{:e} {unit}", &self.covar[(i, i)]));
345 }
346 write!(
347 f,
348 "=== {} @ {} -- within 3 sigma: {} ===\nstate {}\nsigmas [{}]\n",
349 word,
350 &self.epoch(),
351 self.within_3sigma(),
352 &self.state(),
353 fmt_cov.join(", ")
354 )
355 }
356}
357
358impl<T: State> Mul<f64> for KfEstimate<T>
359where
360 DefaultAllocator: Allocator<<T as State>::Size>
361 + Allocator<<T as State>::Size, <T as State>::Size>
362 + Allocator<<T as State>::Size>
363 + Allocator<<T as State>::VecLength>,
364 <DefaultAllocator as Allocator<<T as State>::Size>>::Buffer<f64>: Copy,
365 <DefaultAllocator as Allocator<<T as State>::Size, <T as State>::Size>>::Buffer<f64>: Copy,
366{
367 type Output = Self;
368
369 fn mul(mut self, rhs: f64) -> Self::Output {
370 self.covar *= rhs.powi(2);
371 self
372 }
373}
374
375#[cfg(test)]
376mod ut_kfest {
377 use crate::{
378 mc::StateDispersion, md::StateParameter, od::estimate::KfEstimate, Spacecraft,
379 GMAT_EARTH_GM,
380 };
381 use anise::analysis::prelude::OrbitalElement;
382 use anise::{constants::frames::EARTH_J2000, prelude::Orbit};
383 use hifitime::Epoch;
384
385 #[test]
386 fn test_estimate_from_disp() {
387 let eme2k = EARTH_J2000.with_mu_km3_s2(GMAT_EARTH_GM);
388 let dt = Epoch::from_gregorian_tai_at_midnight(2020, 1, 1);
389 let initial_state = Spacecraft::builder()
390 .orbit(Orbit::keplerian(
391 22000.0, 0.01, 30.0, 80.0, 40.0, 0.0, dt, eme2k,
392 ))
393 .build();
394
395 let initial_estimate = KfEstimate::disperse_from_diag(
396 initial_state,
397 vec![
398 StateDispersion::builder()
399 .param(StateParameter::Element(OrbitalElement::SemiMajorAxis))
400 .std_dev(1.1)
401 .build(),
402 StateDispersion::zero_mean(
403 StateParameter::Element(OrbitalElement::Inclination),
404 0.2,
405 ),
406 StateDispersion::zero_mean(StateParameter::Element(OrbitalElement::RAAN), 0.2),
407 StateDispersion::zero_mean(StateParameter::Element(OrbitalElement::AoP), 0.2),
408 ],
409 Some(0),
410 )
411 .unwrap();
412
413 let initial_state_dev = initial_estimate.nominal_state;
414
415 let (init_rss_pos_km, init_rss_vel_km_s, _) =
416 initial_state.rss(&initial_state_dev).unwrap();
417
418 let delta = (initial_state.orbit - initial_state_dev.orbit).unwrap();
419
420 println!("Truth initial state:\n{initial_state}\n{initial_state:x}");
421 println!("Filter initial state:\n{initial_state_dev}\n{initial_state_dev:x}");
422 println!(
423 "Initial state dev:\t{init_rss_pos_km:.6} km\t{init_rss_vel_km_s:.6} km/s\n{delta}",
424 );
425 println!("covariance: {:.6}", initial_estimate.covar);
426
427 assert!(delta.radius_km.x < initial_estimate.covar[(0, 0)].sqrt());
429 assert!(delta.radius_km.y < initial_estimate.covar[(1, 1)].sqrt());
430 assert!(delta.radius_km.z < initial_estimate.covar[(2, 2)].sqrt());
431 assert!(delta.velocity_km_s.x < initial_estimate.covar[(3, 3)].sqrt());
432 assert!(delta.velocity_km_s.y < initial_estimate.covar[(4, 4)].sqrt());
433 assert!(delta.velocity_km_s.z < initial_estimate.covar[(5, 5)].sqrt());
434 }
435}