1use anise::almanac::Almanac;
20use anise::constants::frames::IAU_EARTH_FRAME;
21use snafu::ResultExt;
22
23use super::{
24 DynamicsAlmanacSnafu, DynamicsAstroSnafu, DynamicsError, DynamicsPlanetarySnafu, ForceModel,
25};
26use crate::cosmic::{AstroError, AstroPhysicsSnafu, Frame, Spacecraft};
27use crate::linalg::{Matrix4x3, Vector3};
28use serde::{Deserialize, Serialize};
29use serde_dhall::StaticType;
30use std::fmt;
31use std::sync::Arc;
32
33#[cfg(feature = "python")]
34use pyo3::prelude::*;
35#[cfg(feature = "python")]
36use pyo3::types::PyType;
37
38#[derive(Clone, Copy, Debug, Serialize, Deserialize, StaticType)]
40#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
41pub enum AtmDensity {
42 Constant(f64),
43 Exponential { rho0: f64, r0: f64, ref_alt_m: f64 },
44 StdAtm { max_alt_m: f64 },
45}
46
47#[cfg(feature = "python")]
48#[cfg_attr(feature = "python", pymethods)]
49impl AtmDensity {
50 #[classmethod]
51 fn earth_exponential(_cls: &Bound<'_, PyType>) -> Self {
52 AtmDensity::Exponential {
53 rho0: 3.614e-13,
54 r0: 700_000.0,
55 ref_alt_m: 88_667.0,
56 }
57 }
58}
59
60#[derive(Clone)]
66pub struct ConstantDrag {
67 pub rho: f64,
69 pub frame: Frame,
71 pub estimate: bool,
73}
74
75impl fmt::Display for ConstantDrag {
76 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77 write!(
78 f,
79 "\tConstant Drag rho = {} kg/m^3 in frame {}",
80 self.rho, self.frame
81 )
82 }
83}
84
85impl ForceModel for ConstantDrag {
86 fn estimation_index(&self) -> Option<usize> {
87 if self.estimate { Some(7) } else { None }
88 }
89
90 fn eom(&self, ctx: &Spacecraft, almanac: &Almanac) -> Result<Vector3<f64>, DynamicsError> {
91 let osc =
92 almanac
93 .transform_to(ctx.orbit, self.frame, None)
94 .context(DynamicsAlmanacSnafu {
95 action: "transforming into drag frame",
96 })?;
97
98 let velocity = osc.velocity_km_s;
99 Ok(-0.5
101 * 1e3
102 * self.rho
103 * ctx.drag.coeff_drag
104 * ctx.drag.area_m2
105 * velocity.norm()
106 * velocity)
107 }
108
109 fn gradient(
110 &self,
111 _osc_ctx: &Spacecraft,
112 _almanac: &Almanac,
113 ) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
114 Err(DynamicsError::DynamicsAstro {
115 source: AstroError::PartialsUndefined,
116 })
117 }
118}
119
120#[derive(Copy, Clone, Debug, Serialize, Deserialize, StaticType)]
122#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
123pub struct Drag {
124 pub density: AtmDensity,
126 pub frame: Frame,
128 pub estimate: bool,
130}
131
132impl Drag {
133 pub fn earth_exp(almanac: &Almanac) -> Result<Arc<Self>, DynamicsError> {
135 Ok(Arc::new(Self {
136 density: AtmDensity::Exponential {
137 rho0: 3.614e-13,
138 r0: 700_000.0,
139 ref_alt_m: 88_667.0,
140 },
141 frame: almanac.frame_info(IAU_EARTH_FRAME).context({
142 DynamicsPlanetarySnafu {
143 action: "planetary data from third body not loaded",
144 }
145 })?,
146 estimate: false,
147 }))
148 }
149
150 pub fn std_atm1976(almanac: &Almanac) -> Result<Arc<Self>, DynamicsError> {
152 Ok(Arc::new(Self {
153 density: AtmDensity::StdAtm {
154 max_alt_m: 1_000_000.0,
155 },
156 frame: almanac.frame_info(IAU_EARTH_FRAME).context({
157 DynamicsPlanetarySnafu {
158 action: "planetary data from third body not loaded",
159 }
160 })?,
161 estimate: false,
162 }))
163 }
164}
165
166impl fmt::Display for Drag {
167 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168 write!(
169 f,
170 "\tDrag density {:?} in frame {}",
171 self.density, self.frame
172 )
173 }
174}
175
176impl ForceModel for Drag {
177 fn estimation_index(&self) -> Option<usize> {
178 if self.estimate { Some(7) } else { None }
179 }
180
181 fn eom(&self, ctx: &Spacecraft, almanac: &Almanac) -> Result<Vector3<f64>, DynamicsError> {
182 let integration_frame = ctx.orbit.frame;
183
184 let osc_drag_frame =
185 almanac
186 .transform_to(ctx.orbit, self.frame, None)
187 .context(DynamicsAlmanacSnafu {
188 action: "transforming into drag frame",
189 })?;
190
191 match self.density {
192 AtmDensity::Constant(rho) => {
193 let velocity = osc_drag_frame.velocity_km_s;
194 Ok(-0.5
196 * 1e3
197 * rho
198 * ctx.drag.coeff_drag
199 * ctx.drag.area_m2
200 * velocity.norm()
201 * velocity)
202 }
203
204 AtmDensity::Exponential {
205 rho0,
206 r0,
207 ref_alt_m,
208 } => {
209 let rho = rho0
211 * (-(osc_drag_frame.rmag_km()
212 - (r0
213 + self
214 .frame
215 .mean_equatorial_radius_km()
216 .context(AstroPhysicsSnafu)
217 .context(DynamicsAstroSnafu)?))
218 / ref_alt_m)
219 .exp();
220
221 let velocity_integr_frame = almanac
224 .transform_to(osc_drag_frame, integration_frame, None)
225 .context(DynamicsAlmanacSnafu {
226 action: "rotating into the integration frame",
227 })?
228 .velocity_km_s;
229
230 let velocity = velocity_integr_frame - osc_drag_frame.velocity_km_s;
231 Ok(-0.5
233 * 1e3
234 * rho
235 * ctx.drag.coeff_drag
236 * ctx.drag.area_m2
237 * velocity.norm()
238 * velocity)
239 }
240
241 AtmDensity::StdAtm { max_alt_m } => {
242 let altitude_km = osc_drag_frame.rmag_km()
243 - self
244 .frame
245 .mean_equatorial_radius_km()
246 .context(AstroPhysicsSnafu)
247 .context(DynamicsAstroSnafu)?;
248 let rho = if altitude_km > max_alt_m / 1_000.0 {
249 10.0_f64.powf((-7e-5) * altitude_km - 14.464)
251 } else {
252 let scale = (altitude_km - 526.8000) / 292.8563;
255 let logdensity =
256 0.34047 * scale.powi(6) - 0.5889 * scale.powi(5) - 0.5269 * scale.powi(4)
257 + 1.0036 * scale.powi(3)
258 + 0.60713 * scale.powi(2)
259 - 2.3024 * scale
260 - 12.575;
261
262 10.0_f64.powf(logdensity)
264 };
265
266 let velocity_integr_frame = almanac
267 .transform_to(osc_drag_frame, integration_frame, None)
268 .context(DynamicsAlmanacSnafu {
269 action: "rotating into the integration frame",
270 })?
271 .velocity_km_s;
272
273 let velocity = velocity_integr_frame - osc_drag_frame.velocity_km_s;
274 Ok(-0.5
276 * 1e3
277 * rho
278 * ctx.drag.coeff_drag
279 * ctx.drag.area_m2
280 * velocity.norm()
281 * velocity)
282 }
283 }
284 }
285
286 fn gradient(
287 &self,
288 _osc_ctx: &Spacecraft,
289 _almanac: &Almanac,
290 ) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
291 Err(DynamicsError::DynamicsAstro {
292 source: AstroError::PartialsUndefined,
293 })
294 }
295}
296
297#[cfg(feature = "python")]
298#[cfg_attr(feature = "python", pymethods)]
299impl Drag {
300 #[pyo3(signature = (density, frame, estimate=true))]
301 #[new]
302 fn py_new(density: AtmDensity, frame: Frame, estimate: bool) -> Self {
303 Self {
304 density,
305 frame,
306 estimate,
307 }
308 }
309
310 fn __str__(&self) -> String {
311 format!("{self}")
312 }
313
314 fn __repr__(&self) -> String {
315 format!("{self} @ {self:p}")
316 }
317}