diff --git a/src/od/process/conf.rs b/src/od/process/conf.rs index 60291c5b..1575b419 100644 --- a/src/od/process/conf.rs +++ b/src/od/process/conf.rs @@ -22,8 +22,12 @@ use std::convert::TryFrom; use std::default::Default; use std::fmt; +#[cfg(feature = "python")] +use pyo3::prelude::*; + /// Defines the stopping condition for the smoother #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "python", pyclass)] pub enum SmoothingArc { /// Stop smoothing when the gap between estimate is the provided floating point number in seconds TimeGap(Duration), @@ -35,6 +39,43 @@ pub enum SmoothingArc { All, } +#[cfg(feature = "python")] +#[pymethods] +impl SmoothingArc { + #[new] + fn py_new( + strategy: Option, + duration: Option, + epoch: Option, + ) -> Result { + if let Some(strategy) = strategy { + match strategy.to_lowercase().trim() { + "all" => Ok(Self::All), + "prediction" => Ok(Self::Prediction), + _ => Err(NyxError::CustomError(format!( + "strategy should be `all` or `prediction`" + ))), + } + } else if Some(duration) = duration { + Ok(Self::TimeGap(duration)) + } else if Some(epoch) = epoch { + Ok(Self::Epoch(epoch)) + } else { + Err(NyxError::CustomError( + "Smoothing arc strategy not specified", + )) + } + } + + fn __repr__(&self) -> String { + format!("{self}") + } + + fn __str__(&self) -> String { + format!("Smoothing {self}") + } +} + impl fmt::Display for SmoothingArc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -49,6 +90,7 @@ impl fmt::Display for SmoothingArc { /// 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)] +#[cfg_attr(feature = "python", pyclass)] pub struct IterationConf { /// The number of measurements to account for in the iteration pub smoother: SmoothingArc, @@ -74,6 +116,52 @@ impl IterationConf { } } +#[cfg(feature = "python")] +#[pymethods] +impl IterationConf { + #[new] + fn py_new( + smoother: SmoothingArc, + absolute_tol: Option, + relative_tol: Option, + max_iterations: Option, + max_divergences: Option, + force_failure: Option, + ) -> Self { + let mut me = Self::default(); + me.smoother = smoother; + if let Some(abs_tol) = absolute_tol { + me.absolute_tol = abs_tol; + } + + if let Some(rel_tol) = relative_tol { + me.relative_tol = rel_toll + } + + if let Some(max_it) = max_iterations { + me.max_iterations = max_it; + } + + if let Some(max_div) = max_divergences { + me.max_divergences = max_div; + } + + if let Some(force_failure) = force_failure { + me.force_failure = force_failure; + } + + me + } + + fn __repr__(&self) -> String { + format!("{self}") + } + + fn __str__(&self) -> String { + format!("Smoothing {self}") + } +} + impl Default for IterationConf { /// The default absolute tolerance is 1e-2 (calibrated on an EKF with error). fn default() -> Self { diff --git a/src/python/mission_design/orbit_trajectory.rs b/src/python/mission_design/orbit_trajectory.rs index a6255384..9965ca6e 100644 --- a/src/python/mission_design/orbit_trajectory.rs +++ b/src/python/mission_design/orbit_trajectory.rs @@ -186,6 +186,23 @@ impl OrbitTraj { Ok(Self { inner: conv_traj }) } + /// Compute the RIC difference between this trajectory and another, writing the output to a parquet file. + fn ric_diff_to_parquet( + &self, + other: &Self, + path: String, + cfg: Option, + ) -> Result { + match self.inner.ric_diff_to_parquet( + &other.inner, + path, + cfg.unwrap_or_else(|| ExportCfg::default()), + ) { + Ok(path) => Ok(format!("{}", path.to_str().unwrap())), + Err(e) => Err(NyxError::CustomError(e.to_string())), + } + } + fn __add__(&self, rhs: &Self) -> Result { let inner = (self.inner.clone() + rhs.inner.clone())?; diff --git a/src/python/orbit_determination/mod.rs b/src/python/orbit_determination/mod.rs index d6d6e8ae..ad049cfd 100644 --- a/src/python/orbit_determination/mod.rs +++ b/src/python/orbit_determination/mod.rs @@ -19,7 +19,7 @@ use crate::io::tracking_data::DynamicTrackingArc; use crate::io::ExportCfg; use crate::od::noise::GaussMarkov; -use crate::od::process::FltResid; +use crate::od::process::{FltResid, IterationConf, SmoothingArc}; pub use crate::od::simulator::TrkConfig; pub use crate::{io::ConfigError, od::prelude::GroundStation}; use pyo3::{prelude::*, py_run}; @@ -42,6 +42,8 @@ pub(crate) fn register_od(py: Python<'_>, parent_module: &PyModule) -> PyResult< sm.add_class::()?; sm.add_class::()?; sm.add_class::()?; + sm.add_class::()?; + sm.add_class::()?; sm.add_class::()?; sm.add_function(wrap_pyfunction!(process_tracking_arc, sm)?)?; sm.add_function(wrap_pyfunction!(predictor, sm)?)?; diff --git a/src/python/orbit_determination/process.rs b/src/python/orbit_determination/process.rs index e31a7cad..edea0941 100644 --- a/src/python/orbit_determination/process.rs +++ b/src/python/orbit_determination/process.rs @@ -26,7 +26,7 @@ use crate::{ md::prelude::{Cosm, Propagator, SpacecraftDynamics}, od::{ filter::kalman::KF, - process::{EkfTrigger, FltResid, ODProcess}, + process::{EkfTrigger, FltResid, IterationConf, ODProcess}, }, NyxError, Spacecraft, }; @@ -54,8 +54,8 @@ pub(crate) fn process_tracking_arc( predict_for: Option, predict_step: Option, fixed_step: Option, + iter_conf: Option, ) -> Result { - // TODO: Return a navigation trajectory or use a class that mimics the better ODProcess -- https://github.com/nyx-space/nyx/issues/134 let msr_noise = Matrix2::from_iterator(measurement_noise); let init_sc = spacecraft.with_orbit(initial_estimate.0.nominal_state.with_stm()); @@ -83,18 +83,20 @@ pub(crate) fn process_tracking_arc( let concrete_arc = arc.to_tracking_arc()?; - odp.process_arc::(&concrete_arc).unwrap(); + odp.process_arc::(&concrete_arc)?; + + if let Some(iter_conf) = iter_conf { + odp.iterate_arc::(&concrete_arc, iter_conf)?; + } if let Some(epoch) = predict_until { let max_step = predict_step.ok_or_else(|| NyxError::CustomError("predict_step unset".to_string()))?; - odp.predict_until(max_step, fixed_step.unwrap_or_else(|| false), epoch) - .unwrap(); + odp.predict_until(max_step, fixed_step.unwrap_or_else(|| false), epoch)?; } else if let Some(duration) = predict_for { let max_step = predict_step.ok_or_else(|| NyxError::CustomError("predict_step unset".to_string()))?; - odp.predict_for(max_step, fixed_step.unwrap_or_else(|| false), duration) - .unwrap(); + odp.predict_for(max_step, fixed_step.unwrap_or_else(|| false), duration)?; } let maybe = odp.to_parquet(