1use crate::dynamics::DynamicsError;
40pub use crate::io::space_weather::Msise00DailyWeather;
41use hifitime::Epoch;
42use serde::{Deserialize, Serialize};
43use serde_dhall::StaticType;
44
45#[cfg(feature = "python")]
46use pyo3::prelude::*;
47
48pub mod coefficients;
49mod model;
50
51#[derive(Debug, Clone)]
55pub struct Nrlmsise00Output {
56 pub temp_exo_k: f64,
58 pub temp_alt_k: f64,
60 pub density_he_per_cm3: f64,
62 pub density_o_per_cm3: f64,
64 pub density_n2_per_cm3: f64,
66 pub density_o2_per_cm3: f64,
68 pub density_ar_per_cm3: f64,
70 pub density_h_per_cm3: f64,
72 pub density_n_per_cm3: f64,
74 pub density_anomalous_o_per_cm3: f64,
76 pub total_mass_density_kg_m3: f64,
78}
79
80#[derive(Debug, Clone)]
82pub struct Nrlmsise00Input {
83 pub day_of_year: u32,
85 pub ut_seconds: f64,
87 pub altitude_km: f64,
89 pub latitude_deg: f64,
91 pub longitude_deg: f64,
93 pub local_solar_time_hours: f64,
95 pub f107_daily: f64,
97 pub f107_avg: f64,
99 pub ap_daily: f64,
101 pub ap_array: [f64; 7],
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, StaticType)]
106#[cfg_attr(feature = "python", pyclass(from_py_object, eq, eq_int))]
107pub enum GeomagneticMode {
108 Off,
109 StandardDailyAp,
110 ExtendedHistory57h, }
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, StaticType)]
114#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
115pub struct Nrlmsise00Flags {
116 pub geomagnetic: GeomagneticMode,
117 pub f107_solar_flux: bool,
118 pub time_independent: bool,
119 pub annual_harmonics: bool,
120 pub semiannual_harmonics: bool,
121 pub diurnal_tides: bool,
122 pub semidiurnal_tides: bool,
123 pub terdiurnal_tides: bool,
124 pub ut_and_longitude: bool,
125 pub exospheric_temp_variations: bool,
126 pub lower_boundary_temp_variations: bool,
127 pub gradient_variations: bool,
128 pub departures_from_diffusive_equilibrium: bool,
129 pub lower_thermosphere_temp_variations: bool,
130 pub upper_stratosphere_temp_variations: bool,
131 pub boundary_density_variations: bool,
132 pub lower_mesosphere_temp_variations: bool,
133 pub turbopause_scale_height_variations: bool,
134}
135
136impl Default for Nrlmsise00Flags {
137 fn default() -> Self {
138 Self {
139 geomagnetic: GeomagneticMode::StandardDailyAp,
140 f107_solar_flux: true,
141 time_independent: true,
142 annual_harmonics: true,
143 semiannual_harmonics: true,
144 diurnal_tides: true,
145 semidiurnal_tides: true,
146 terdiurnal_tides: true,
147 ut_and_longitude: true,
148 exospheric_temp_variations: true,
149 lower_boundary_temp_variations: true,
150 gradient_variations: true,
151 departures_from_diffusive_equilibrium: true,
152 lower_thermosphere_temp_variations: true,
153 upper_stratosphere_temp_variations: true,
154 boundary_density_variations: true,
155 lower_mesosphere_temp_variations: true,
156 turbopause_scale_height_variations: true,
157 }
158 }
159}
160
161impl Nrlmsise00Flags {
162 pub(crate) fn to_switches(self) -> [f64; 24] {
164 let mut sw = [1.0f64; 24];
165
166 sw[9] = match self.geomagnetic {
170 GeomagneticMode::Off => 0.0,
171 GeomagneticMode::StandardDailyAp => 1.0,
172 GeomagneticMode::ExtendedHistory57h => -1.0,
173 };
174
175 if !self.f107_solar_flux {
177 sw[1] = 0.0;
178 }
179 if !self.time_independent {
180 sw[2] = 0.0;
181 }
182 if !self.annual_harmonics {
183 sw[3] = 0.0;
184 sw[5] = 0.0;
185 }
186 if !self.semiannual_harmonics {
187 sw[4] = 0.0;
188 sw[6] = 0.0;
189 }
190 if !self.diurnal_tides {
191 sw[7] = 0.0;
192 }
193 if !self.semidiurnal_tides {
194 sw[8] = 0.0;
195 }
196 if !self.terdiurnal_tides {
197 sw[14] = 0.0;
198 }
199 if !self.ut_and_longitude {
200 sw[10] = 0.0;
201 sw[11] = 0.0;
202 sw[12] = 0.0;
203 sw[13] = 0.0;
204 }
205 if !self.exospheric_temp_variations {
206 sw[16] = 0.0;
207 }
208 if !self.lower_boundary_temp_variations {
209 sw[17] = 0.0;
210 }
211 if !self.gradient_variations {
212 sw[19] = 0.0;
213 }
214 if !self.departures_from_diffusive_equilibrium {
215 sw[15] = 0.0;
216 }
217 if !self.lower_thermosphere_temp_variations {
218 sw[18] = 0.0;
219 }
220 if !self.upper_stratosphere_temp_variations {
221 sw[20] = 0.0;
222 }
223 if !self.boundary_density_variations {
224 sw[21] = 0.0;
225 }
226 if !self.lower_mesosphere_temp_variations {
227 sw[22] = 0.0;
228 }
229 if !self.turbopause_scale_height_variations {
230 sw[23] = 0.0;
231 }
232
233 sw
234 }
235}
236
237#[cfg(feature = "python")]
238#[pymethods]
239impl Nrlmsise00Flags {
240 #[new]
241 #[pyo3(signature = (
242 geomagnetic = None,
243 f107_solar_flux = true,
244 time_independent = true,
245 annual_harmonics = true,
246 semiannual_harmonics = true,
247 diurnal_tides = true,
248 semidiurnal_tides = true,
249 terdiurnal_tides = true,
250 ut_and_longitude = true,
251 exospheric_temp_variations = true,
252 lower_boundary_temp_variations = true,
253 gradient_variations = true,
254 departures_from_diffusive_equilibrium = true,
255 lower_thermosphere_temp_variations = true,
256 upper_stratosphere_temp_variations = true,
257 boundary_density_variations = true,
258 lower_mesosphere_temp_variations = true,
259 turbopause_scale_height_variations = true,
260 ))]
261 #[allow(clippy::too_many_arguments)]
262 fn py_new(
263 geomagnetic: Option<GeomagneticMode>,
264 f107_solar_flux: bool,
265 time_independent: bool,
266 annual_harmonics: bool,
267 semiannual_harmonics: bool,
268 diurnal_tides: bool,
269 semidiurnal_tides: bool,
270 terdiurnal_tides: bool,
271 ut_and_longitude: bool,
272 exospheric_temp_variations: bool,
273 lower_boundary_temp_variations: bool,
274 gradient_variations: bool,
275 departures_from_diffusive_equilibrium: bool,
276 lower_thermosphere_temp_variations: bool,
277 upper_stratosphere_temp_variations: bool,
278 boundary_density_variations: bool,
279 lower_mesosphere_temp_variations: bool,
280 turbopause_scale_height_variations: bool,
281 ) -> Self {
282 Self {
283 geomagnetic: geomagnetic.unwrap_or(GeomagneticMode::StandardDailyAp),
284 f107_solar_flux,
285 time_independent,
286 annual_harmonics,
287 semiannual_harmonics,
288 diurnal_tides,
289 semidiurnal_tides,
290 terdiurnal_tides,
291 ut_and_longitude,
292 exospheric_temp_variations,
293 lower_boundary_temp_variations,
294 gradient_variations,
295 departures_from_diffusive_equilibrium,
296 lower_thermosphere_temp_variations,
297 upper_stratosphere_temp_variations,
298 boundary_density_variations,
299 lower_mesosphere_temp_variations,
300 turbopause_scale_height_variations,
301 }
302 }
303
304 fn __repr__(&self) -> String {
305 format!("{:?}", self)
306 }
307
308 fn __str__(&self) -> String {
309 format!("{:?} @ {self:p}", self)
310 }
311}
312
313fn calculate(input: &Nrlmsise00Input, flags: Nrlmsise00Flags) -> Nrlmsise00Output {
317 let sw = flags.to_switches();
318 let (d, temp_exo, temp_alt) = model::compute(input, &sw);
319 Nrlmsise00Output {
322 temp_exo_k: temp_exo,
323 temp_alt_k: temp_alt,
324 density_he_per_cm3: d[0],
325 density_o_per_cm3: d[1],
326 density_n2_per_cm3: d[2],
327 density_o2_per_cm3: d[3],
328 density_ar_per_cm3: d[4],
329 density_h_per_cm3: d[6],
330 density_n_per_cm3: d[7],
331 density_anomalous_o_per_cm3: d[8],
332 total_mass_density_kg_m3: d[5] * 1e3,
334 }
335}
336
337pub fn msise00_density(
348 sw: Msise00DailyWeather,
349 lst_h: f64,
350 latitude_deg: f64,
351 longitude_deg: f64,
352 altitude_km: f64,
353 epoch: Epoch,
354 flags: Nrlmsise00Flags,
355) -> Result<Nrlmsise00Output, DynamicsError> {
356 let at_midnight = epoch.with_hms(0, 0, 0);
357 let ut_seconds = (epoch - at_midnight).to_seconds();
358
359 let input = Nrlmsise00Input {
360 day_of_year: at_midnight.day_of_year() as u32,
361 ut_seconds,
362 altitude_km,
363 latitude_deg,
364 longitude_deg,
365 local_solar_time_hours: lst_h,
366 f107_daily: sw.f107_daily_sfu,
367 f107_avg: sw.f107_avg_sfu,
368 ap_daily: sw.ap_daily,
369 ap_array: sw.ap_3hour_history,
370 };
371
372 Ok(calculate(&input, flags))
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn test_nrlmsise00_flags() {
381 let default_flags = Nrlmsise00Flags::default();
382 let default_switches = default_flags.to_switches();
383
384 assert_eq!(default_switches[0], 1.0);
386 assert_eq!(default_switches[9], 1.0);
387 assert_eq!(default_switches[1], 1.0);
388 assert_eq!(default_switches[2], 1.0);
389
390 let mut custom_flags = Nrlmsise00Flags {
392 geomagnetic: GeomagneticMode::StandardDailyAp,
393 f107_solar_flux: false,
394 time_independent: false,
395 annual_harmonics: false,
396 semiannual_harmonics: false,
397 diurnal_tides: false,
398 semidiurnal_tides: false,
399 terdiurnal_tides: false,
400 ut_and_longitude: false,
401 exospheric_temp_variations: false,
402 lower_boundary_temp_variations: false,
403 gradient_variations: false,
404 departures_from_diffusive_equilibrium: false,
405 lower_thermosphere_temp_variations: false,
406 upper_stratosphere_temp_variations: false,
407 boundary_density_variations: false,
408 lower_mesosphere_temp_variations: false,
409 turbopause_scale_height_variations: false,
410 };
411
412 let custom_switches = custom_flags.to_switches();
413 assert_eq!(custom_switches[0], 1.0); assert_eq!(custom_switches[9], 1.0); assert_eq!(custom_switches[1], 0.0);
416 assert_eq!(custom_switches[2], 0.0);
417 assert_eq!(custom_switches[3], 0.0);
418 assert_eq!(custom_switches[5], 0.0);
419 assert_eq!(custom_switches[4], 0.0);
420 assert_eq!(custom_switches[6], 0.0);
421 assert_eq!(custom_switches[7], 0.0);
422 assert_eq!(custom_switches[8], 0.0);
423 assert_eq!(custom_switches[14], 0.0);
424 assert_eq!(custom_switches[10], 0.0);
425 assert_eq!(custom_switches[11], 0.0);
426 assert_eq!(custom_switches[12], 0.0);
427 assert_eq!(custom_switches[13], 0.0);
428 assert_eq!(custom_switches[16], 0.0);
429 assert_eq!(custom_switches[17], 0.0);
430 assert_eq!(custom_switches[19], 0.0);
431 assert_eq!(custom_switches[15], 0.0);
432 assert_eq!(custom_switches[18], 0.0);
433 assert_eq!(custom_switches[20], 0.0);
434 assert_eq!(custom_switches[21], 0.0);
435 assert_eq!(custom_switches[22], 0.0);
436 assert_eq!(custom_switches[23], 0.0);
437
438 custom_flags.geomagnetic = GeomagneticMode::Off;
440 let switches_off = custom_flags.to_switches();
441 assert_eq!(switches_off[9], 0.0);
442 }
443}