nyx_space/dynamics/
drag.rs1use 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 std::fmt;
29use std::sync::Arc;
30
31#[derive(Clone, Copy, Debug)]
33pub enum AtmDensity {
34 Constant(f64),
35 Exponential { rho0: f64, r0: f64, ref_alt_m: f64 },
36 StdAtm { max_alt_m: f64 },
37}
38
39#[derive(Clone)]
45pub struct ConstantDrag {
46 pub rho: f64,
48 pub drag_frame: Frame,
50 pub estimate: bool,
52}
53
54impl fmt::Display for ConstantDrag {
55 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56 write!(
57 f,
58 "\tConstant Drag rho = {} kg/m^3 in frame {}",
59 self.rho, self.drag_frame
60 )
61 }
62}
63
64impl ForceModel for ConstantDrag {
65 fn estimation_index(&self) -> Option<usize> {
66 if self.estimate {
67 Some(7)
68 } else {
69 None
70 }
71 }
72
73 fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
74 let osc = almanac
75 .transform_to(ctx.orbit, self.drag_frame, None)
76 .context(DynamicsAlmanacSnafu {
77 action: "transforming into drag frame",
78 })?;
79
80 let velocity = osc.velocity_km_s;
81 Ok(-0.5
83 * 1e3
84 * self.rho
85 * ctx.drag.coeff_drag
86 * ctx.drag.area_m2
87 * velocity.norm()
88 * velocity)
89 }
90
91 fn dual_eom(
92 &self,
93 _osc_ctx: &Spacecraft,
94 _almanac: Arc<Almanac>,
95 ) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
96 Err(DynamicsError::DynamicsAstro {
97 source: AstroError::PartialsUndefined,
98 })
99 }
100}
101
102#[derive(Clone)]
104pub struct Drag {
105 pub density: AtmDensity,
107 pub drag_frame: Frame,
109 pub estimate: bool,
111}
112
113impl Drag {
114 pub fn earth_exp(almanac: Arc<Almanac>) -> Result<Arc<Self>, DynamicsError> {
116 Ok(Arc::new(Self {
117 density: AtmDensity::Exponential {
118 rho0: 3.614e-13,
119 r0: 700_000.0,
120 ref_alt_m: 88_667.0,
121 },
122 drag_frame: almanac.frame_from_uid(IAU_EARTH_FRAME).context({
123 DynamicsPlanetarySnafu {
124 action: "planetary data from third body not loaded",
125 }
126 })?,
127 estimate: false,
128 }))
129 }
130
131 pub fn std_atm1976(almanac: Arc<Almanac>) -> Result<Arc<Self>, DynamicsError> {
133 Ok(Arc::new(Self {
134 density: AtmDensity::StdAtm {
135 max_alt_m: 1_000_000.0,
136 },
137 drag_frame: almanac.frame_from_uid(IAU_EARTH_FRAME).context({
138 DynamicsPlanetarySnafu {
139 action: "planetary data from third body not loaded",
140 }
141 })?,
142 estimate: false,
143 }))
144 }
145}
146
147impl fmt::Display for Drag {
148 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149 write!(
150 f,
151 "\tDrag density {:?} in frame {}",
152 self.density, self.drag_frame
153 )
154 }
155}
156
157impl ForceModel for Drag {
158 fn estimation_index(&self) -> Option<usize> {
159 if self.estimate {
160 Some(7)
161 } else {
162 None
163 }
164 }
165
166 fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
167 let integration_frame = ctx.orbit.frame;
168
169 let osc_drag_frame = almanac
170 .transform_to(ctx.orbit, self.drag_frame, None)
171 .context(DynamicsAlmanacSnafu {
172 action: "transforming into drag frame",
173 })?;
174
175 match self.density {
176 AtmDensity::Constant(rho) => {
177 let velocity = osc_drag_frame.velocity_km_s;
178 Ok(-0.5
180 * 1e3
181 * rho
182 * ctx.drag.coeff_drag
183 * ctx.drag.area_m2
184 * velocity.norm()
185 * velocity)
186 }
187
188 AtmDensity::Exponential {
189 rho0,
190 r0,
191 ref_alt_m,
192 } => {
193 let rho = rho0
195 * (-(osc_drag_frame.rmag_km()
196 - (r0
197 + self
198 .drag_frame
199 .mean_equatorial_radius_km()
200 .context(AstroPhysicsSnafu)
201 .context(DynamicsAstroSnafu)?))
202 / ref_alt_m)
203 .exp();
204
205 let velocity_integr_frame = almanac
208 .transform_to(osc_drag_frame, integration_frame, None)
209 .context(DynamicsAlmanacSnafu {
210 action: "rotating into the integration frame",
211 })?
212 .velocity_km_s;
213
214 let velocity = velocity_integr_frame - osc_drag_frame.velocity_km_s;
215 Ok(-0.5
217 * 1e3
218 * rho
219 * ctx.drag.coeff_drag
220 * ctx.drag.area_m2
221 * velocity.norm()
222 * velocity)
223 }
224
225 AtmDensity::StdAtm { max_alt_m } => {
226 let altitude_km = osc_drag_frame.rmag_km()
227 - self
228 .drag_frame
229 .mean_equatorial_radius_km()
230 .context(AstroPhysicsSnafu)
231 .context(DynamicsAstroSnafu)?;
232 let rho = if altitude_km > max_alt_m / 1_000.0 {
233 10.0_f64.powf((-7e-5) * altitude_km - 14.464)
235 } else {
236 let scale = (altitude_km - 526.8000) / 292.8563;
239 let logdensity =
240 0.34047 * scale.powi(6) - 0.5889 * scale.powi(5) - 0.5269 * scale.powi(4)
241 + 1.0036 * scale.powi(3)
242 + 0.60713 * scale.powi(2)
243 - 2.3024 * scale
244 - 12.575;
245
246 10.0_f64.powf(logdensity)
248 };
249
250 let velocity_integr_frame = almanac
251 .transform_to(osc_drag_frame, integration_frame, None)
252 .context(DynamicsAlmanacSnafu {
253 action: "rotating into the integration frame",
254 })?
255 .velocity_km_s;
256
257 let velocity = velocity_integr_frame - osc_drag_frame.velocity_km_s;
258 Ok(-0.5
260 * 1e3
261 * rho
262 * ctx.drag.coeff_drag
263 * ctx.drag.area_m2
264 * velocity.norm()
265 * velocity)
266 }
267 }
268 }
269
270 fn dual_eom(
271 &self,
272 _osc_ctx: &Spacecraft,
273 _almanac: Arc<Almanac>,
274 ) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
275 Err(DynamicsError::DynamicsAstro {
276 source: AstroError::PartialsUndefined,
277 })
278 }
279}