nyx_space/dynamics/
orbital.rs1use super::{
20 AccelModel, DynamicsAlmanacSnafu, DynamicsAstroSnafu, DynamicsError, DynamicsPlanetarySnafu,
21};
22use crate::cosmic::{AstroPhysicsSnafu, Frame, Orbit};
23use crate::linalg::{Const, Matrix3, Matrix6, OVector, Vector3, Vector6};
24
25use anise::almanac::Almanac;
26use anise::astro::Aberration;
27use hyperdual::linalg::norm;
28use hyperdual::{extract_jacobian_and_result, hyperspace_from_vector, Float, OHyperdual};
29use snafu::ResultExt;
30use std::f64;
31use std::fmt;
32use std::sync::Arc;
33
34pub use super::sph_harmonics::Harmonics;
35
36#[derive(Clone)]
38pub struct OrbitalDynamics {
39 pub accel_models: Vec<Arc<dyn AccelModel + Sync>>,
40}
41
42impl OrbitalDynamics {
43 pub fn point_masses(celestial_objects: Vec<i32>) -> Self {
45 Self::new(vec![PointMasses::new(celestial_objects)])
47 }
48
49 pub fn two_body() -> Self {
51 Self::new(vec![])
52 }
53
54 pub fn new(accel_models: Vec<Arc<dyn AccelModel + Sync>>) -> Self {
56 Self { accel_models }
57 }
58
59 pub fn from_model(accel_model: Arc<dyn AccelModel + Sync>) -> Self {
62 Self::new(vec![accel_model])
63 }
64}
65
66impl fmt::Display for OrbitalDynamics {
67 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68 let models: Vec<String> = self.accel_models.iter().map(|x| format!("{x}")).collect();
69 write!(f, "Orbital dynamics: {}", models.join("; "))
70 }
71}
72
73impl OrbitalDynamics {
74 pub(crate) fn eom(
75 &self,
76 osc: &Orbit,
77 almanac: Arc<Almanac>,
78 ) -> Result<OVector<f64, Const<42>>, DynamicsError> {
79 let body_acceleration = (-osc
81 .frame
82 .mu_km3_s2()
83 .context(AstroPhysicsSnafu)
84 .context(DynamicsAstroSnafu)?
85 / osc.rmag_km().powi(3))
86 * osc.radius_km;
87
88 let mut d_x = Vector6::from_iterator(
89 osc.velocity_km_s
90 .iter()
91 .chain(body_acceleration.iter())
92 .cloned(),
93 );
94
95 for model in &self.accel_models {
97 let model_acc = model.eom(osc, almanac.clone())?;
98 for i in 0..3 {
99 d_x[i + 3] += model_acc[i];
100 }
101 }
102
103 Ok(OVector::<f64, Const<42>>::from_iterator(
104 d_x.iter()
105 .chain(OVector::<f64, Const<36>>::zeros().iter())
106 .cloned(),
107 ))
108 }
109
110 pub fn dual_eom(
111 &self,
112 _delta_t_s: f64,
113 osc: &Orbit,
114 almanac: Arc<Almanac>,
115 ) -> Result<(Vector6<f64>, Matrix6<f64>), DynamicsError> {
116 let state: Vector6<OHyperdual<f64, Const<7>>> =
119 hyperspace_from_vector(&osc.to_cartesian_pos_vel());
120
121 let radius = state.fixed_rows::<3>(0).into_owned();
122 let velocity = state.fixed_rows::<3>(3).into_owned();
123
124 let rmag = norm(&radius);
126 let body_acceleration = radius
127 * (OHyperdual::<f64, Const<7>>::from_real(
128 -osc.frame
129 .mu_km3_s2()
130 .context(AstroPhysicsSnafu)
131 .context(DynamicsAstroSnafu)?,
132 ) / rmag.powi(3));
133
134 let mut dx = Vector6::zeros();
136 let mut grad = Matrix6::zeros();
137 for i in 0..6 {
138 dx[i] = if i < 3 {
139 velocity[i].real()
140 } else {
141 body_acceleration[i - 3].real()
142 };
143 for j in 1..7 {
144 grad[(i, j - 1)] = if i < 3 {
145 velocity[i][j]
146 } else {
147 body_acceleration[i - 3][j]
148 };
149 }
150 }
151
152 for model in &self.accel_models {
154 let (model_acc, model_grad) = model.dual_eom(osc, almanac.clone())?;
155 for i in 0..3 {
156 dx[i + 3] += model_acc[i];
157 for j in 1..4 {
158 grad[(i + 3, j - 1)] += model_grad[(i, j - 1)];
159 }
160 }
161 }
162
163 Ok((dx, grad))
166 }
167}
168
169pub struct PointMasses {
171 pub celestial_objects: Vec<i32>,
172 pub correction: Option<Aberration>,
174}
175
176impl PointMasses {
177 pub fn new(celestial_objects: Vec<i32>) -> Arc<Self> {
179 Arc::new(Self {
180 celestial_objects,
181 correction: None,
182 })
183 }
184
185 pub fn with_correction(celestial_objects: Vec<i32>, correction: Aberration) -> Self {
187 Self {
188 celestial_objects,
189 correction: Some(correction),
190 }
191 }
192}
193
194impl fmt::Display for PointMasses {
195 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196 let masses: Vec<String> = self
197 .celestial_objects
198 .iter()
199 .map(|third_body| format!("{}", Frame::from_ephem_j2000(*third_body)))
200 .collect();
201 write!(f, "Point masses of {}", masses.join(", "))
202 }
203}
204
205impl AccelModel for PointMasses {
206 fn eom(&self, osc: &Orbit, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
207 let mut d_x = Vector3::zeros();
208 for third_body in self.celestial_objects.iter().copied() {
210 if osc.frame.ephem_origin_id_match(third_body) {
211 continue;
213 }
214
215 let third_body_frame = almanac
216 .frame_from_uid(osc.frame.with_ephem(third_body))
217 .context(DynamicsPlanetarySnafu {
218 action: "planetary data from third body not loaded",
219 })?;
220
221 let st_ij = almanac
223 .transform(third_body_frame, osc.frame, osc.epoch, self.correction)
224 .context(DynamicsAlmanacSnafu {
225 action: "computing third body gravitational pull",
226 })?;
227
228 let r_ij = st_ij.radius_km;
229 let r_ij3 = st_ij.rmag_km().powi(3);
230 let r_j = osc.radius_km - r_ij; let r_j3 = r_j.norm().powi(3);
232 d_x += -third_body_frame
233 .mu_km3_s2()
234 .context(AstroPhysicsSnafu)
235 .context(DynamicsAstroSnafu)?
236 * (r_j / r_j3 + r_ij / r_ij3);
237 }
238 Ok(d_x)
239 }
240
241 fn dual_eom(
242 &self,
243 osc: &Orbit,
244 almanac: Arc<Almanac>,
245 ) -> Result<(Vector3<f64>, Matrix3<f64>), DynamicsError> {
246 let radius: Vector3<OHyperdual<f64, Const<7>>> = hyperspace_from_vector(&osc.radius_km);
248 let mut fx = Vector3::zeros();
250 let mut grad = Matrix3::zeros();
251
252 for third_body in &self.celestial_objects {
254 let third_body_frame = almanac
255 .frame_from_uid(Frame::from_ephem_j2000(*third_body))
256 .context(DynamicsPlanetarySnafu {
257 action: "planetary data from third body not loaded",
258 })?;
259
260 if osc.frame.ephem_origin_match(third_body_frame) {
261 continue;
263 }
264
265 let gm_d = OHyperdual::<f64, Const<7>>::from_real(
266 -third_body_frame
267 .mu_km3_s2()
268 .context(AstroPhysicsSnafu)
269 .context(DynamicsAstroSnafu)?,
270 );
271
272 let st_ij = almanac
274 .transform(third_body_frame, osc.frame, osc.epoch, self.correction)
275 .context(DynamicsAlmanacSnafu {
276 action: "computing third body gravitational pull",
277 })?;
278
279 let r_ij: Vector3<OHyperdual<f64, Const<7>>> = hyperspace_from_vector(&st_ij.radius_km);
280 let r_ij3 = norm(&r_ij).powi(3);
281
282 let mut r_j = radius - r_ij; r_j[0][1] = 1.0;
285 r_j[1][2] = 1.0;
286 r_j[2][3] = 1.0;
287
288 let r_j3 = norm(&r_j).powi(3);
289 let mut third_body_acc_d = r_j / r_j3 + r_ij / r_ij3;
290 third_body_acc_d[0] *= gm_d;
291 third_body_acc_d[1] *= gm_d;
292 third_body_acc_d[2] *= gm_d;
293
294 let (fxp, gradp) = extract_jacobian_and_result::<_, 3, 3, 7>(&third_body_acc_d);
295 fx += fxp;
296 grad += gradp;
297 }
298
299 Ok((fx, grad))
300 }
301}