nyx_space/od/process/solution/
mod.rs1use crate::linalg::allocator::Allocator;
20use crate::linalg::{DefaultAllocator, DimName};
21use crate::md::trajectory::{Interpolatable, Traj};
22pub use crate::od::estimate::*;
23pub use crate::od::*;
24use indexmap::IndexSet;
25use msr::sensitivity::TrackerSensitivity;
26use nalgebra::OMatrix;
27use std::collections::BTreeMap;
28use std::iter::Zip;
29use std::ops::Add;
30use std::slice::Iter;
31
32use self::msr::MeasurementType;
33
34mod display;
35mod export;
36mod filter_data;
37mod import;
38mod smooth;
39mod stats;
40
41#[derive(Clone, Debug)]
60#[allow(clippy::upper_case_acronyms)]
61pub struct ODSolution<StateType, EstType, MsrSize, Trk>
62where
63 StateType: Interpolatable + Add<OVector<f64, <StateType as State>::Size>, Output = StateType>,
64 EstType: Estimate<StateType>,
65 MsrSize: DimName,
66 Trk: TrackerSensitivity<StateType, StateType>,
67 <DefaultAllocator as Allocator<<StateType as State>::VecLength>>::Buffer<f64>: Send,
68 DefaultAllocator: Allocator<<StateType as State>::Size>
69 + Allocator<<StateType as State>::VecLength>
70 + Allocator<MsrSize>
71 + Allocator<MsrSize, <StateType as State>::Size>
72 + Allocator<MsrSize, MsrSize>
73 + Allocator<<StateType as State>::Size, <StateType as State>::Size>
74 + Allocator<<StateType as State>::Size, MsrSize>,
75{
76 pub estimates: Vec<EstType>,
78 pub residuals: Vec<Option<Residual<MsrSize>>>,
80 pub gains: Vec<Option<OMatrix<f64, <StateType as State>::Size, MsrSize>>>,
82 pub filter_smoother_ratios: Vec<Option<OVector<f64, <StateType as State>::Size>>>,
84 pub devices: BTreeMap<String, Trk>,
86 pub measurement_types: IndexSet<MeasurementType>,
87}
88
89impl<StateType, EstType, MsrSize, Trk> ODSolution<StateType, EstType, MsrSize, Trk>
90where
91 StateType: Interpolatable + Add<OVector<f64, <StateType as State>::Size>, Output = StateType>,
92 EstType: Estimate<StateType>,
93 MsrSize: DimName,
94 Trk: TrackerSensitivity<StateType, StateType>,
95 <DefaultAllocator as Allocator<<StateType as State>::VecLength>>::Buffer<f64>: Send,
96 DefaultAllocator: Allocator<<StateType as State>::Size>
97 + Allocator<<StateType as State>::VecLength>
98 + Allocator<MsrSize>
99 + Allocator<MsrSize, <StateType as State>::Size>
100 + Allocator<MsrSize, MsrSize>
101 + Allocator<<StateType as State>::Size, <StateType as State>::Size>
102 + Allocator<<StateType as State>::Size, MsrSize>,
103{
104 pub fn new(
105 devices: BTreeMap<String, Trk>,
106 measurement_types: IndexSet<MeasurementType>,
107 ) -> Self {
108 Self {
109 estimates: Vec::new(),
110 residuals: Vec::new(),
111 gains: Vec::new(),
112 filter_smoother_ratios: Vec::new(),
113 devices,
114 measurement_types,
115 }
116 }
117
118 pub(crate) fn push_measurement_update(
120 &mut self,
121 estimate: EstType,
122 residual: Residual<MsrSize>,
123 gain: Option<OMatrix<f64, <StateType as State>::Size, MsrSize>>,
124 ) {
125 self.estimates.push(estimate);
126 self.residuals.push(Some(residual));
127 self.gains.push(gain);
128 self.filter_smoother_ratios.push(None);
129 }
130
131 pub(crate) fn push_time_update(&mut self, estimate: EstType) {
133 self.estimates.push(estimate);
134 self.residuals.push(None);
135 self.gains.push(None);
136 self.filter_smoother_ratios.push(None);
137 }
138
139 pub fn results(&self) -> Zip<Iter<'_, EstType>, Iter<'_, Option<Residual<MsrSize>>>> {
141 self.estimates.iter().zip(self.residuals.iter())
142 }
143
144 pub fn is_filter_run(&self) -> bool {
146 self.gains.iter().flatten().count() > 0
147 }
148
149 pub fn is_smoother_run(&self) -> bool {
151 self.filter_smoother_ratios.iter().flatten().count() > 0
152 }
153
154 pub fn to_traj(&self) -> Result<Traj<StateType>, NyxError>
156 where
157 DefaultAllocator: Allocator<StateType::VecLength>,
158 {
159 if self.estimates.is_empty() {
160 Err(NyxError::NoStateData {
161 msg: "No navigation trajectory to generate: run the OD process first".to_string(),
162 })
163 } else {
164 let mut traj = Traj {
166 states: self.estimates.iter().map(|est| est.state()).collect(),
167 name: None,
168 };
169 traj.finalize();
170 Ok(traj)
171 }
172 }
173
174 pub fn accepted_residuals(&self) -> Vec<Residual<MsrSize>> {
176 self.residuals
177 .iter()
178 .flatten()
179 .filter(|resid| !resid.rejected)
180 .cloned()
181 .collect::<Vec<Residual<MsrSize>>>()
182 }
183
184 pub fn rejected_residuals(&self) -> Vec<Residual<MsrSize>> {
186 self.residuals
187 .iter()
188 .flatten()
189 .filter(|resid| resid.rejected)
190 .cloned()
191 .collect::<Vec<Residual<MsrSize>>>()
192 }
193}
194
195impl<StateType, EstType, MsrSize, Trk> PartialEq for ODSolution<StateType, EstType, MsrSize, Trk>
196where
197 StateType: Interpolatable + Add<OVector<f64, <StateType as State>::Size>, Output = StateType>,
198 EstType: Estimate<StateType>,
199 MsrSize: DimName,
200 Trk: TrackerSensitivity<StateType, StateType> + PartialEq,
201 <DefaultAllocator as Allocator<<StateType as State>::VecLength>>::Buffer<f64>: Send,
202 DefaultAllocator: Allocator<<StateType as State>::Size>
203 + Allocator<<StateType as State>::VecLength>
204 + Allocator<MsrSize>
205 + Allocator<MsrSize, <StateType as State>::Size>
206 + Allocator<MsrSize, MsrSize>
207 + Allocator<<StateType as State>::Size, <StateType as State>::Size>
208 + Allocator<<StateType as State>::Size, MsrSize>,
209{
210 fn eq(&self, other: &Self) -> bool {
212 self.estimates.len() == other.estimates.len()
213 && self.residuals.len() == other.residuals.len()
214 && self.gains.len() == other.gains.len()
215 && self.filter_smoother_ratios.len() == other.filter_smoother_ratios.len()
216 && self.devices == other.devices
217 && self.measurement_types.iter().all(|msr_type| other.measurement_types.contains(msr_type))
218 && self.estimates.iter().zip(other.estimates.iter()).all(|(mine, theirs)| {
219 (mine.state().to_state_vector() - theirs.state().to_state_vector()).norm() < 1e-6 &&
220 (mine.covar() - theirs.covar()).norm() < 1e-8
221 })
222 && self.residuals.iter().zip(other.residuals.iter()).all(|(mine, theirs)| {
223 if let Some(mine) = mine {
224 if let Some(theirs) = theirs {
225 (mine.ratio - theirs.ratio).abs() < 1e-4
226 } else {
227 false
228 }
229 } else {
230 theirs.is_none()
231 }
232 })
233 && self.gains.iter().zip(other.gains.iter()).all(|(my_k, other_k)| {
235 if let Some(my_k) = my_k {
236 if let Some(other_k) = other_k {
237 (my_k - other_k).norm() < 1e-8
238 } else {
239 false
240 }
241 } else {
242 other_k.is_none()
243 }
244 })
245 && self.filter_smoother_ratios.iter().zip(other.filter_smoother_ratios.iter()).all(|(my_fs, other_fs)| {
247 if let Some(my_fs) = my_fs {
248 if let Some(other_fs) = other_fs {
249 (my_fs - other_fs).norm() < 1e-8
250 } else {
251 false
252 }
253 } else {
254 other_fs.is_none()
255 }
256 })
257 }
258}