Skip to main content

sidereon_core/astro/propagator/
api.rs

1use crate::astro::error::PropagationError;
2use crate::astro::frames::orientation::EarthOrientationProvider;
3use crate::constants::SECONDS_PER_HOUR;
4use std::sync::Arc;
5
6/// Per-evaluation context shared with force models.
7///
8/// The default context is intentionally empty. A caller that wants a future
9/// body-fixed force to use the precise Earth-fixed frame can attach an
10/// [`EarthOrientationProvider`], while existing force models and default
11/// propagation remain bit-identical.
12#[derive(Clone, Default)]
13pub struct PropagationContext {
14    body_fixed_frame_provider: Option<Arc<dyn EarthOrientationProvider>>,
15}
16
17impl core::fmt::Debug for PropagationContext {
18    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
19        f.debug_struct("PropagationContext")
20            .field(
21                "body_fixed_frame_provider",
22                &self.body_fixed_frame_provider.is_some(),
23            )
24            .finish()
25    }
26}
27
28impl PropagationContext {
29    /// Build an empty propagation context.
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Attach a body-fixed frame provider.
35    pub fn with_body_fixed_frame_provider(
36        mut self,
37        provider: Arc<dyn EarthOrientationProvider>,
38    ) -> Self {
39        self.body_fixed_frame_provider = Some(provider);
40        self
41    }
42
43    /// Return the body-fixed frame provider, if one was attached.
44    pub fn body_fixed_frame_provider(&self) -> Option<&dyn EarthOrientationProvider> {
45        self.body_fixed_frame_provider
46            .as_deref()
47            .map(|provider| provider as &dyn EarthOrientationProvider)
48    }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct IntegratorOptions {
53    pub abs_tol: f64,
54    pub rel_tol: f64,
55    pub min_step: f64,
56    pub max_step: f64,
57    pub initial_step: f64,
58    pub max_steps: u32,
59    pub dense_output: bool,
60}
61
62impl Default for IntegratorOptions {
63    fn default() -> Self {
64        Self {
65            abs_tol: 1e-9,
66            rel_tol: 1e-12,
67            min_step: 1e-6,
68            max_step: SECONDS_PER_HOUR,
69            initial_step: 60.0,
70            max_steps: 1_000_000,
71            dense_output: false,
72        }
73    }
74}
75
76pub(crate) fn validate_integrator_options(
77    opts: &IntegratorOptions,
78) -> Result<(), PropagationError> {
79    validate_step_options(opts)
80}
81
82pub(crate) fn validate_adaptive_integrator_options(
83    opts: &IntegratorOptions,
84) -> Result<(), PropagationError> {
85    validate_step_options(opts)?;
86    crate::validate::finite_positive(opts.abs_tol, "abs_tol").map_err(map_field_error)?;
87    crate::validate::finite_positive(opts.rel_tol, "rel_tol").map_err(map_field_error)?;
88    Ok(())
89}
90
91pub(crate) fn validate_integrator_epoch(
92    value: f64,
93    field: &'static str,
94) -> Result<(), PropagationError> {
95    crate::validate::finite(value, field)
96        .map(|_| ())
97        .map_err(map_field_error)
98}
99
100fn validate_step_options(opts: &IntegratorOptions) -> Result<(), PropagationError> {
101    crate::validate::positive_step(opts.initial_step, "initial_step").map_err(map_field_error)?;
102    crate::validate::positive_step(opts.min_step, "min_step").map_err(map_field_error)?;
103    crate::validate::positive_step(opts.max_step, "max_step").map_err(map_field_error)?;
104    Ok(())
105}
106
107fn map_field_error(error: crate::validate::FieldError) -> PropagationError {
108    PropagationError::InvalidInput(format!("{} {}", error.field(), error.reason()))
109}