nyx_space/dynamics/
drag.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*/
18
19use 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/// Density in kg/m^3 and altitudes in meters, not kilometers!
32#[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/// `ConstantDrag` implements a constant drag model as defined in Vallado, 4th ed., page 551, with an important caveat.
40///
41/// **WARNING:** This basic model assumes that the velocity of the spacecraft is identical to the velocity of the upper atmosphere,
42/// This is a **bad** assumption and **should not** be used for high fidelity simulations.
43/// This will be resolved after https://gitlab.com/chrisrabotin/nyx/issues/93 is implemented.
44#[derive(Clone)]
45pub struct ConstantDrag {
46    /// atmospheric density in kg/m^3
47    pub rho: f64,
48    /// Geoid causing the drag
49    pub drag_frame: Frame,
50    /// Set to true to estimate the coefficient of drag
51    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        // Note the 1e3 factor to convert drag units from ((kg * km^2 * s^-2) / m^1) to (kg * km * s^-2)
82        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/// `Drag` implements all three drag models.
103#[derive(Clone)]
104pub struct Drag {
105    /// Density computation method
106    pub density: AtmDensity,
107    /// Frame to compute the drag in
108    pub drag_frame: Frame,
109    /// Set to true to estimate the coefficient of drag
110    pub estimate: bool,
111}
112
113impl Drag {
114    /// Common exponential drag model for the Earth
115    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    /// Drag model which uses the standard atmosphere 1976 model for atmospheric density
132    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                // Note the 1e3 factor to convert drag units from ((kg * km^2 * s^-2) / m^1) to (kg * km * s^-2)
179                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                // Compute rho in the drag frame.
194                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                // TODO: Drag modeling will be improved in https://github.com/nyx-space/nyx/issues/317
206                // The frame will be double checked in this PR as well.
207                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                // Note the 1e3 factor to convert drag units from ((kg * km^2 * s^-2) / m^1) to (kg * km * s^-2)
216                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                    // Use a constant density
234                    10.0_f64.powf((-7e-5) * altitude_km - 14.464)
235                } else {
236                    // Code from AVS/Schaub's Basilisk
237                    // Calculating the density based on a scaled 6th order polynomial fit to the log of density
238                    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                    /* Calculating density by raising 10 to the log of density */
247                    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                // Note the 1e3 factor to convert drag units from ((kg * km^2 * s^-2) / m^1) to (kg * km * s^-2)
259                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}