use crate::coordinate::{Frame, Origin};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum ForceModelTier {
Approximate = 0,
Basic = 1,
Standard = 2,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UncertaintyMethod {
FirstOrder,
SecondOrder,
SigmaPoint {
n_sigma: f64,
samples_per_plane: usize,
},
MonteCarlo {
n_samples: usize,
seed: Option<u64>,
},
Auto {
threshold_first: f64,
threshold_mixture: f64,
threshold_ip_skip: f64,
gmm_max_depth: usize,
gmm_components_per_split: usize,
},
}
impl UncertaintyMethod {
pub fn sigma_point() -> Self {
Self::SigmaPoint {
n_sigma: 1.0,
samples_per_plane: 8,
}
}
pub fn monte_carlo(n_samples: usize) -> Self {
Self::MonteCarlo {
n_samples,
seed: Some(42),
}
}
pub fn auto() -> Self {
Self::Auto {
threshold_first: 0.1,
threshold_mixture: 10.0,
threshold_ip_skip: 1e-12,
gmm_max_depth: 3,
gmm_components_per_split: 3,
}
}
pub(crate) fn to_ffi(&self) -> empyrean_sys::EmpyreanUncertaintyMethod {
let mut out = empyrean_sys::EmpyreanUncertaintyMethod {
tag: 0,
sp_n_sigma: 0.0,
sp_samples_per_plane: 0,
mc_n_samples: 0,
mc_seed_some: 0,
mc_seed: 0,
auto_threshold_first: 0.0,
auto_threshold_mixture: 0.0,
auto_threshold_ip_skip: 0.0,
auto_gmm_max_depth: 0,
auto_gmm_components_per_split: 0,
};
match *self {
Self::FirstOrder => out.tag = 0,
Self::SecondOrder => out.tag = 1,
Self::SigmaPoint {
n_sigma,
samples_per_plane,
} => {
out.tag = 2;
out.sp_n_sigma = n_sigma;
out.sp_samples_per_plane = samples_per_plane as u64;
}
Self::MonteCarlo { n_samples, seed } => {
out.tag = 3;
out.mc_n_samples = n_samples as u64;
out.mc_seed_some = u8::from(seed.is_some());
out.mc_seed = seed.unwrap_or(0);
}
Self::Auto {
threshold_first,
threshold_mixture,
threshold_ip_skip,
gmm_max_depth,
gmm_components_per_split,
} => {
out.tag = 4;
out.auto_threshold_first = threshold_first;
out.auto_threshold_mixture = threshold_mixture;
out.auto_threshold_ip_skip = threshold_ip_skip;
out.auto_gmm_max_depth = gmm_max_depth as u64;
out.auto_gmm_components_per_split = gmm_components_per_split as u64;
}
}
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IntegratorChoice {
#[default]
GR15,
DOP853,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OriginSwitchingConfig {
pub enabled: bool,
pub hysteresis: f64,
}
impl Default for OriginSwitchingConfig {
fn default() -> Self {
Self {
enabled: true,
hysteresis: 0.2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AdvancedIntegratorConfig {
pub integrator: IntegratorChoice,
pub epsilon: f64,
pub dt_initial: Option<f64>,
pub dt_min: Option<f64>,
pub encounter_timescale_divisor: f64,
pub max_steps: usize,
pub max_dense_steps: usize,
pub cache_integrator_steps: bool,
pub origin_switching: OriginSwitchingConfig,
}
impl Default for AdvancedIntegratorConfig {
fn default() -> Self {
Self {
integrator: IntegratorChoice::default(),
epsilon: 1e-9,
dt_initial: None,
dt_min: None,
encounter_timescale_divisor: 1000.0,
max_steps: 10_000_000,
max_dense_steps: 100_000,
cache_integrator_steps: false,
origin_switching: OriginSwitchingConfig::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct DiagnosticsConfig {
pub sensitivity: bool,
pub nonlinearity: bool,
pub lyapunov: bool,
pub keyholes: bool,
pub bifurcations: bool,
pub sample_stride: usize,
pub sensitivity_threshold: Option<f64>,
pub lyapunov_threshold: Option<f64>,
pub nonlinearity_threshold: Option<f64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventConfig {
pub close_approaches: bool,
pub impacts: bool,
pub atmospheric: bool,
pub possible_impacts: bool,
pub shadow_events: bool,
pub body_filter: Vec<Origin>,
pub dense_output: bool,
pub dense_output_cadence_days: f64,
}
impl Default for EventConfig {
fn default() -> Self {
Self {
close_approaches: true,
impacts: true,
atmospheric: true,
possible_impacts: true,
shadow_events: true,
body_filter: Vec::new(),
dense_output: false,
dense_output_cadence_days: 5.0 / 1440.0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropagationConfig {
pub force_model: ForceModelTier,
pub excluded_perturbers: Vec<Origin>,
pub uncertainty_method: UncertaintyMethod,
pub compute_stm: bool,
pub frame: Frame,
pub events: EventConfig,
pub diagnostics: DiagnosticsConfig,
pub num_threads: Option<std::num::NonZeroUsize>,
pub advanced: AdvancedIntegratorConfig,
}
impl Default for PropagationConfig {
fn default() -> Self {
Self {
force_model: ForceModelTier::Standard,
excluded_perturbers: Vec::new(),
uncertainty_method: UncertaintyMethod::FirstOrder,
compute_stm: false,
frame: Frame::EclipticJ2000,
events: EventConfig::default(),
diagnostics: DiagnosticsConfig::default(),
num_threads: None,
advanced: AdvancedIntegratorConfig::default(),
}
}
}
impl PropagationConfig {
pub(crate) fn to_ffi_with(&self) -> (empyrean_sys::EmpyreanPropagationConfig, PropConfigKeep) {
let perturbers: Vec<i32> = self
.excluded_perturbers
.iter()
.map(|o| o.naif_id())
.collect();
let body_filter: Vec<i32> = self
.events
.body_filter
.iter()
.map(|o| o.naif_id())
.collect();
let cfg = empyrean_sys::EmpyreanPropagationConfig {
force_model: self.force_model as i32,
num_excluded_perturbers: perturbers.len(),
excluded_perturbers_naif: if perturbers.is_empty() {
std::ptr::null()
} else {
perturbers.as_ptr()
},
uncertainty_method: self.uncertainty_method.to_ffi(),
compute_stm: u8::from(self.compute_stm),
frame: self.frame as i32,
events: empyrean_sys::EmpyreanEventConfig {
close_approaches: u8::from(self.events.close_approaches),
impacts: u8::from(self.events.impacts),
atmospheric: u8::from(self.events.atmospheric),
possible_impacts: u8::from(self.events.possible_impacts),
shadow_events: u8::from(self.events.shadow_events),
num_body_filter: body_filter.len(),
body_filter_naif: if body_filter.is_empty() {
std::ptr::null()
} else {
body_filter.as_ptr()
},
dense_output: u8::from(self.events.dense_output),
dense_output_cadence_days: self.events.dense_output_cadence_days,
},
diagnostics: empyrean_sys::EmpyreanDiagnosticsConfig {
sensitivity: u8::from(self.diagnostics.sensitivity),
nonlinearity: u8::from(self.diagnostics.nonlinearity),
lyapunov: u8::from(self.diagnostics.lyapunov),
keyholes: u8::from(self.diagnostics.keyholes),
bifurcations: u8::from(self.diagnostics.bifurcations),
sample_stride: self.diagnostics.sample_stride,
sensitivity_threshold: self.diagnostics.sensitivity_threshold.unwrap_or(f64::NAN),
lyapunov_threshold: self.diagnostics.lyapunov_threshold.unwrap_or(f64::NAN),
nonlinearity_threshold: self.diagnostics.nonlinearity_threshold.unwrap_or(f64::NAN),
},
num_threads: self.num_threads.map_or(0, |n| n.get()),
advanced: empyrean_sys::EmpyreanAdvancedIntegratorConfig {
integrator: match self.advanced.integrator {
IntegratorChoice::GR15 => empyrean_sys::EMPYREAN_INTEGRATOR_GR15 as i32,
IntegratorChoice::DOP853 => empyrean_sys::EMPYREAN_INTEGRATOR_DOP853 as i32,
},
epsilon: self.advanced.epsilon,
dt_initial: self.advanced.dt_initial.unwrap_or(f64::NAN),
dt_min: self.advanced.dt_min.unwrap_or(f64::NAN),
encounter_timescale_divisor: self.advanced.encounter_timescale_divisor,
max_steps: self.advanced.max_steps,
max_dense_steps: self.advanced.max_dense_steps,
cache_integrator_steps: u8::from(self.advanced.cache_integrator_steps),
origin_switching: empyrean_sys::EmpyreanOriginSwitchingConfig {
enabled: if self.advanced.origin_switching.enabled {
empyrean_sys::EMPYREAN_ORIGIN_SWITCHING_ON as u8
} else {
empyrean_sys::EMPYREAN_ORIGIN_SWITCHING_OFF as u8
},
hysteresis: self.advanced.origin_switching.hysteresis,
},
},
};
(
cfg,
PropConfigKeep {
_perturbers: perturbers,
_body_filter: body_filter,
},
)
}
}
pub(crate) struct PropConfigKeep {
_perturbers: Vec<i32>,
_body_filter: Vec<i32>,
}