diff --git a/src/io/trajectory_data.rs b/src/io/trajectory_data.rs index 35ba55c6..e9806a7b 100644 --- a/src/io/trajectory_data.rs +++ b/src/io/trajectory_data.rs @@ -243,6 +243,9 @@ impl TrajectoryLoader { } } + // Remove any duplicates that may exist in the imported trajectory. + traj.finalize(); + Ok(traj) } diff --git a/src/md/trajectory/traj.rs b/src/md/trajectory/traj.rs index 406edb12..0788b182 100644 --- a/src/md/trajectory/traj.rs +++ b/src/md/trajectory/traj.rs @@ -400,18 +400,11 @@ where None => S::export_params(), }; - // Check that we can retrieve this information - fields.retain(|param| match self.first().value(*param) { - Ok(_) => true, - Err(_) => { - warn!("Removed unavailable field `{param}` from RIC export",); - false - } + // Remove disallowed field and check that we can retrieve this information + fields.retain(|param| { + param != &StateParameter::GuidanceMode && self.first().value(*param).is_ok() }); - // Disallowed fields - fields.retain(|param| param != &StateParameter::GuidanceMode); - for field in &fields { hdrs.push(field.to_field(more_meta.clone())); } @@ -458,8 +451,8 @@ where // Build the list of 6x6 RIC DCM // Assuming identical rate just before and after the first DCM and for the last DCM let mut inertial2ric_dcms = Vec::with_capacity(self_states.len()); - for ii in 0..self_states.len() { - let dcm_cur = self_states[ii] + for (ii, self_state) in self_states.iter().enumerate() { + let dcm_cur = self_state .orbit() .dcm_from_traj_frame(Frame::RIC) .unwrap() @@ -468,21 +461,27 @@ where let dcm_pre = if ii == 0 { dcm_cur } else { - self_states_pre[ii - 1] - .orbit() - .dcm_from_traj_frame(Frame::RIC) - .unwrap() - .transpose() + match self_states_pre.get(ii - 1) { + Some(state) => state + .orbit() + .dcm_from_traj_frame(Frame::RIC) + .unwrap() + .transpose(), + None => dcm_cur, + } }; let dcm_post = if ii == self_states_post.len() { dcm_cur } else { - self_states_post[ii] - .orbit() - .dcm_from_traj_frame(Frame::RIC) - .unwrap() - .transpose() + match self_states_post.get(ii) { + Some(state) => state + .orbit() + .dcm_from_traj_frame(Frame::RIC) + .unwrap() + .transpose(), + None => dcm_cur, + } }; let dcm6x6 = dcm_finite_differencing(dcm_pre, dcm_cur, dcm_post); diff --git a/src/od/msr/arc.rs b/src/od/msr/arc.rs index 098b5680..697301fc 100644 --- a/src/od/msr/arc.rs +++ b/src/od/msr/arc.rs @@ -35,7 +35,7 @@ use crate::State; use arrow::array::{Array, Float64Builder, StringBuilder}; use arrow::datatypes::{DataType, Field, Schema}; use arrow::record_batch::RecordBatch; -use hifitime::prelude::{Duration, Epoch}; +use hifitime::prelude::{Duration, Epoch, Unit}; use parquet::arrow::ArrowWriter; /// Tracking arc contains the tracking data generated by the tracking devices defined in this structure. @@ -209,10 +209,12 @@ where } else { let mut windows = self.measurements.windows(2); let first_window = windows.next()?; - let mut min_interval = first_window[1].1.epoch() - first_window[0].1.epoch(); + // Ensure that the first interval isn't zero + let mut min_interval = + (first_window[1].1.epoch() - first_window[0].1.epoch()).max(2 * Unit::Second); for window in windows { let interval = window[1].1.epoch() - window[0].1.epoch(); - if interval != Duration::ZERO && interval < min_interval { + if interval > Duration::ZERO && interval < min_interval { min_interval = interval; } } diff --git a/src/od/process/conf.rs b/src/od/process/conf.rs index 2e886787..0ce28ab8 100644 --- a/src/od/process/conf.rs +++ b/src/od/process/conf.rs @@ -21,6 +21,7 @@ use crate::NyxError; use std::convert::TryFrom; use std::default::Default; use std::fmt; +use typed_builder::TypedBuilder; #[cfg(feature = "python")] use pyo3::prelude::*; @@ -49,22 +50,34 @@ impl fmt::Display for SmoothingArc { } } +impl Default for SmoothingArc { + fn default() -> Self { + Self::All + } +} + /// Defines a filter iteration configuration. Allows iterating on an OD solution until convergence criteria is met. /// The root mean squared of the prefit residuals ratios is used to assess convergence between iterations. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, TypedBuilder)] #[cfg_attr(feature = "python", pyclass)] pub struct IterationConf { /// The number of measurements to account for in the iteration + #[builder(default)] pub smoother: SmoothingArc, /// The absolute tolerance of the RMS prefit residual ratios + #[builder(default = 1e-1)] pub absolute_tol: f64, /// The relative tolerance between the latest RMS prefit residual ratios and the best RMS prefit residual ratios so far + #[builder(default = 1e-2)] pub relative_tol: f64, /// The maximum number of iterations to allow (will raise an error if the filter has not converged after this many iterations) + #[builder(default = 15)] pub max_iterations: usize, /// The maximum number of subsequent divergences in RMS. + #[builder(default = 3)] pub max_divergences: usize, /// Set to true to force an ODP failure when the convergence criteria is not met + #[builder(default = false)] pub force_failure: bool, } diff --git a/src/od/process/mod.rs b/src/od/process/mod.rs index eab5d522..ab25eaa6 100644 --- a/src/od/process/mod.rs +++ b/src/od/process/mod.rs @@ -465,15 +465,15 @@ where where Dev: TrackingDeviceSim, { - assert!( - measurements.len() >= 2, - "must have at least two measurements" - ); + if measurements.len() < 2 { + return Err(NyxError::CustomError( + "must have at least two measurements".to_string(), + )); + } - assert!( - max_step.abs() > (0.0 * Unit::Nanosecond), - "step size is zero" - ); + if max_step.is_negative() || max_step == Duration::ZERO { + return Err(NyxError::CustomError("step size is zero".to_string())); + } // Start by propagating the estimator (on the same thread). let num_msrs = measurements.len(); diff --git a/tests/python/.gitignore b/tests/python/.gitignore index f74c856f..52a8c3d9 100644 --- a/tests/python/.gitignore +++ b/tests/python/.gitignore @@ -1,2 +1,3 @@ __pycache__ -private \ No newline at end of file +private +mod.rs \ No newline at end of file