Skip to main content

nyx_space/dynamics/sequence/
config.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18use anise::frames::FrameUid;
19use anise::prelude::Almanac;
20use serde::{Deserialize, Serialize};
21use serde_dhall::{SimpleType, StaticType};
22use std::collections::HashMap;
23use std::sync::Arc;
24
25use crate::dynamics::{GravityField, OrbitalDynamics};
26use crate::dynamics::{SolidTides, SpacecraftDynamics};
27use crate::io::gravity::GravityFieldData;
28use crate::propagators::Propagator;
29use crate::{
30    dynamics::{
31        Drag, PointMasses, SolarPressure,
32        guidance::{Maneuver, ObjectiveEfficiency, ObjectiveWeight},
33    },
34    io::gravity::GravityFieldConfig,
35    propagators::{IntegratorMethod, IntegratorOptions},
36};
37
38use crate::dynamics::sequence::discrete_event::DiscreteEvent;
39
40#[cfg(feature = "python")]
41use pyo3::prelude::*;
42
43#[derive(Clone, Debug, Serialize, Deserialize)]
44pub enum Phase {
45    Terminate,
46    Activity {
47        name: String,
48        propagator: String,
49        guidance: Option<Box<GuidanceConfig>>,
50        /// The discrete event will be applied ONCE before the equation of motions are integrated.
51        on_entry: Option<Box<DiscreteEvent>>,
52        /// Allows disabling a phase without removing it
53        disabled: bool,
54    },
55}
56
57impl StaticType for Phase {
58    fn static_type() -> SimpleType {
59        let mut variants = HashMap::new();
60
61        // Handle the Vector(Vector3<f64>) variant
62        // Most math crates serialize Vector3 as a list of 3 doubles
63        variants.insert("Terminate".to_string(), None);
64
65        //  Activity variant (Record variant)
66        let mut activity_fields = HashMap::new();
67
68        activity_fields.insert("name".to_string(), String::static_type());
69        activity_fields.insert("propagator".to_string(), String::static_type());
70
71        // Use the StaticType impl of the boxed inner types
72        activity_fields.insert(
73            "guidance".to_string(),
74            <Option<GuidanceConfig> as StaticType>::static_type(),
75        );
76
77        activity_fields.insert(
78            "on_entry".to_string(),
79            <Option<DiscreteEvent> as StaticType>::static_type(),
80        );
81
82        activity_fields.insert("disabled".to_string(), bool::static_type());
83
84        variants.insert(
85            "Activity".to_string(),
86            Some(SimpleType::Record(activity_fields)),
87        );
88
89        SimpleType::Union(variants)
90    }
91}
92
93/// Dynamics defines the dynamical environment with a set of acceleration and force models
94///
95/// :type accel_models: AccelModels
96/// :type force_models: ForceModels
97#[derive(Clone, Debug, Serialize, Deserialize, StaticType)]
98#[cfg_attr(feature = "python", pyclass(from_py_object))]
99pub struct Dynamics {
100    pub accel_models: AccelModels,
101    pub force_models: ForceModels,
102}
103
104impl Dynamics {
105    pub fn build(&self, almanac: Arc<Almanac>) -> Result<SpacecraftDynamics, String> {
106        // Build the orbital dynamics
107        let mut orbital_dyn = OrbitalDynamics::two_body();
108        if let Some(point_masses) = &self.accel_models.point_masses {
109            orbital_dyn
110                .accel_models
111                .push(Arc::new(point_masses.clone()));
112        }
113        if let Some(gravity_cfg) = &self.accel_models.gravity_field {
114            let grav_data = GravityFieldData::from_config(gravity_cfg.clone(), &almanac)
115                .map_err(|e| e.to_string())?;
116            let gravity_field = GravityField::new(grav_data);
117            orbital_dyn.accel_models.push(gravity_field);
118        }
119        if let Some(solid_tides) = &self.accel_models.solid_tides {
120            orbital_dyn.accel_models.push(Arc::new(solid_tides.clone()));
121        }
122        // Build the spacecraft dynamics
123        let mut sc_dyn = SpacecraftDynamics::new(orbital_dyn);
124
125        if let Some(srp) = &self.force_models.solar_pressure {
126            sc_dyn.force_models.push(Arc::new(srp.clone()));
127        }
128
129        if let Some(drag) = &self.force_models.drag {
130            sc_dyn.force_models.push(Arc::new(drag.clone()));
131        }
132
133        // And set it all up!
134        Ok(sc_dyn)
135    }
136}
137
138/// Propagator config includes the method, options, and all dynamics
139///
140/// :type dynamics: Dynamics
141/// :type method: IntegratorMethod
142/// :type options: IntegratorOptions
143#[derive(Clone, Debug, Serialize, Deserialize, StaticType)]
144#[cfg_attr(feature = "python", pyclass(from_py_object))]
145pub struct PropagatorConfig {
146    pub dynamics: Dynamics,
147    pub method: IntegratorMethod,
148    pub options: IntegratorOptions,
149}
150
151impl PropagatorConfig {
152    pub fn build(&self, almanac: Arc<Almanac>) -> Result<Propagator<SpacecraftDynamics>, String> {
153        Ok(Propagator::new(
154            self.dynamics.build(almanac)?,
155            self.method,
156            self.options,
157        ))
158    }
159}
160
161/// Acceleration models alter the orbital dynamics
162///
163/// :type point_masses: PointMasses | None
164/// :type gravity_field: GravityFieldConfig | None
165/// :type solid_tides: SolidTides | None
166#[derive(Clone, Default, Serialize, Deserialize, Debug)]
167#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
168pub struct AccelModels {
169    pub point_masses: Option<PointMasses>,
170    pub gravity_field: Option<GravityFieldConfig>,
171    pub solid_tides: Option<SolidTides>,
172}
173
174/// Force models alter the spacecraft dynamics (they need a mass).
175///
176/// :type solar_pressure: SolarPressure | None
177/// :type drag: Drag | None
178#[derive(Clone, Default, Serialize, Deserialize, Debug)]
179#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
180pub struct ForceModels {
181    pub solar_pressure: Option<SolarPressure>,
182    pub drag: Option<Drag>,
183}
184
185#[derive(Clone, Debug, Serialize, Deserialize, StaticType)]
186pub struct GuidanceConfig {
187    pub thruster_model: String,
188    pub disable_prop_mass: bool,
189    pub law: SteeringLaw,
190}
191
192// NOTE: Steering laws are not yet available in Python =(
193#[derive(Clone, Debug, Serialize, Deserialize, StaticType)]
194pub enum SteeringLaw {
195    FiniteBurn(Maneuver),
196    Kluever {
197        /// Stores the objectives, and their associated weights (set to zero to disable).
198        objectives: Vec<ObjectiveWeight>,
199        /// If defined, coast until vehicle is out of the provided eclipse state.
200        max_eclipse_prct: Option<f64>,
201    },
202    Ruggiero {
203        /// Stores the objectives, and their associated efficiency threshold (set to zero if not minimum efficiency).
204        objectives: Vec<ObjectiveEfficiency>,
205        /// If defined, coast until vehicle is out of the provided eclipse state.
206        max_eclipse_prct: Option<f64>,
207    },
208}
209
210impl StaticType for AccelModels {
211    fn static_type() -> serde_dhall::SimpleType {
212        let mut fields = HashMap::new();
213
214        fields.insert(
215            "point_masses".to_string(),
216            SimpleType::Optional(Box::new(PointMasses::static_type())),
217        );
218
219        #[allow(dead_code)]
220        #[derive(StaticType)]
221        struct GravityFieldDhall(GravityFieldConfig, FrameUid);
222
223        fields.insert(
224            "gravity_field".to_string(),
225            SimpleType::Optional(Box::new(GravityFieldDhall::static_type())),
226        );
227
228        fields.insert(
229            "solid_tides".to_string(),
230            SimpleType::Optional(Box::new(SolidTides::static_type())),
231        );
232
233        SimpleType::Record(fields)
234    }
235}
236
237impl StaticType for ForceModels {
238    fn static_type() -> serde_dhall::SimpleType {
239        let mut fields = HashMap::new();
240
241        fields.insert(
242            "solar_pressure".to_string(),
243            SimpleType::Optional(Box::new(SolarPressure::static_type())),
244        );
245
246        fields.insert(
247            "drag".to_string(),
248            SimpleType::Optional(Box::new(Drag::static_type())),
249        );
250
251        SimpleType::Record(fields)
252    }
253}