1#![allow(clippy::needless_range_loop)]
32
33mod tables;
34
35pub type ApArray = [f64; 7];
41
42use crate::astro::constants::time::SECONDS_PER_HOUR;
43
44pub const MAX_ALTITUDE_KM: f64 = 1000.0;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
57pub enum AtmosphereError {
58 #[error("ap_array is required when switch 9 is -1 (Ap-history mode)")]
62 MissingApArray,
63 #[error("non-finite input: {0}")]
66 NonFiniteInput(&'static str),
67 #[error("input out of domain: {0}")]
71 OutOfDomain(&'static str),
72}
73
74fn validate(input: &NrlmsiseInput, flags: &Flags) -> Result<(), AtmosphereError> {
83 use AtmosphereError::*;
84
85 if flags.switches[9] == -1 && input.ap_array.is_none() {
86 return Err(MissingApArray);
87 }
88
89 for (value, name) in [
90 (input.alt, "alt"),
91 (input.g_lat, "g_lat"),
92 (input.g_long, "g_long"),
93 (input.sec, "sec"),
94 (input.lst, "lst"),
95 (input.f107, "f107"),
96 (input.f107a, "f107a"),
97 (input.ap, "ap"),
98 ] {
99 if !value.is_finite() {
100 return Err(NonFiniteInput(name));
101 }
102 }
103
104 if !(0.0..=MAX_ALTITUDE_KM).contains(&input.alt) {
105 return Err(OutOfDomain("alt"));
106 }
107 if input.f107 < 0.0 {
108 return Err(OutOfDomain("f107"));
109 }
110 if input.f107a < 0.0 {
111 return Err(OutOfDomain("f107a"));
112 }
113 if input.ap < 0.0 {
114 return Err(OutOfDomain("ap"));
115 }
116 if let Some(ap_array) = input.ap_array {
117 for v in ap_array {
118 if !v.is_finite() {
119 return Err(NonFiniteInput("ap_array"));
120 }
121 if v < 0.0 {
122 return Err(OutOfDomain("ap_array"));
123 }
124 }
125 }
126
127 Ok(())
128}
129
130#[derive(Debug, Clone, Copy)]
133pub struct NrlmsiseInput {
134 pub year: i32,
136 pub doy: i32,
138 pub sec: f64,
140 pub alt: f64,
142 pub g_lat: f64,
144 pub g_long: f64,
146 pub lst: f64,
149 pub f107a: f64,
151 pub f107: f64,
153 pub ap: f64,
155 pub ap_array: Option<ApArray>,
157}
158
159#[derive(Debug, Clone, Copy, Default, PartialEq)]
168pub struct NrlmsiseOutput {
169 pub d: [f64; 9],
171 pub t: [f64; 2],
173}
174
175impl NrlmsiseOutput {
176 pub fn density(&self) -> f64 {
178 self.d[5]
179 }
180 pub fn temperature_exo(&self) -> f64 {
182 self.t[0]
183 }
184 pub fn temperature_alt(&self) -> f64 {
186 self.t[1]
187 }
188}
189
190#[derive(Debug, Clone, Copy)]
197pub struct Flags {
198 pub switches: [i32; 24],
200 sw: [f64; 24],
201 swc: [f64; 24],
202}
203
204impl Flags {
205 pub fn new(switches: [i32; 24]) -> Self {
207 let mut f = Flags {
208 switches,
209 sw: [0.0; 24],
210 swc: [0.0; 24],
211 };
212 f.tselec();
213 f
214 }
215
216 pub fn standard() -> Self {
219 let mut s = [1i32; 24];
220 s[0] = 0;
221 Flags::new(s)
222 }
223
224 pub fn metric() -> Self {
226 Flags::new([1i32; 24])
227 }
228
229 fn tselec(&mut self) {
231 for i in 0..24 {
232 if i != 9 {
233 self.sw[i] = if self.switches[i] == 1 { 1.0 } else { 0.0 };
234 self.swc[i] = if self.switches[i] > 0 { 1.0 } else { 0.0 };
235 } else {
236 self.sw[i] = self.switches[i] as f64;
237 self.swc[i] = self.switches[i] as f64;
238 }
239 }
240 }
241}
242
243pub fn local_solar_time(sec: f64, g_long: f64) -> f64 {
245 let lst = sec / SECONDS_PER_HOUR + g_long / 15.0;
246 ((lst % 24.0) + 24.0) % 24.0
247}
248
249pub const DEFAULT_F107: f64 = 150.0;
255
256pub const DEFAULT_F107A: f64 = 150.0;
262
263pub const DEFAULT_AP: f64 = 4.0;
268
269const DGTR: f64 = 1.74533E-2;
270const DR: f64 = 1.72142E-2;
271const HR: f64 = 0.2618;
272const SR: f64 = 7.2722E-5;
273const RGAS: f64 = 831.4;
274
275fn ccor(alt: f64, r: f64, h1: f64, zh: f64) -> f64 {
277 let e = (alt - zh) / h1;
278 if e > 70.0 {
279 return libm::exp(0.0_f64);
280 }
281 if e < -70.0 {
282 return libm::exp(r);
283 }
284 let ex = libm::exp(e);
285 let e = r / (1.0 + ex);
286 libm::exp(e)
287}
288
289fn ccor2(alt: f64, r: f64, h1: f64, zh: f64, h2: f64) -> f64 {
292 let e1 = (alt - zh) / h1;
293 let e2 = (alt - zh) / h2;
294 if (e1 > 70.0) || (e2 > 70.0) {
295 return libm::exp(0.0_f64);
296 }
297 if (e1 < -70.0) && (e2 < -70.0) {
298 return libm::exp(r);
299 }
300 let ex1 = libm::exp(e1);
301 let ex2 = libm::exp(e2);
302 let ccor2v = r / (1.0 + 0.5 * (ex1 + ex2));
303 libm::exp(ccor2v)
304}
305
306fn dnet(dd: f64, dm: f64, zhm: f64, xmm: f64, xm: f64) -> f64 {
308 let a = zhm / (xmm - xm);
309 let (mut dd, dm) = (dd, dm);
310 if !((dm > 0.0) && (dd > 0.0)) {
311 if (dd == 0.0) && (dm == 0.0) {
312 dd = 1.0;
313 }
314 if dm == 0.0 {
315 return dd;
316 }
317 if dd == 0.0 {
318 return dm;
319 }
320 }
321 let ylog = a * libm::log(dm / dd);
322 if ylog < -10.0 {
323 return dd;
324 }
325 if ylog > 10.0 {
326 return dm;
327 }
328 dd * libm::pow(1.0 + libm::exp(ylog), 1.0 / a)
329}
330
331fn splini(xa: &[f64], ya: &[f64], y2a: &[f64], n: usize, x: f64) -> f64 {
333 let mut yi = 0.0;
334 let mut klo = 0usize;
335 let mut khi = 1usize;
336 while (x > xa[klo]) && (khi < n) {
337 let mut xx = x;
338 if khi < (n - 1) {
339 if x < xa[khi] {
340 xx = x;
341 } else {
342 xx = xa[khi];
343 }
344 }
345 let h = xa[khi] - xa[klo];
346 let a = (xa[khi] - xx) / h;
347 let b = (xx - xa[klo]) / h;
348 let a2 = a * a;
349 let b2 = b * b;
350 yi += ((1.0 - a2) * ya[klo] / 2.0
351 + b2 * ya[khi] / 2.0
352 + ((-(1.0 + a2 * a2) / 4.0 + a2 / 2.0) * y2a[klo]
353 + (b2 * b2 / 4.0 - b2 / 2.0) * y2a[khi])
354 * h
355 * h
356 / 6.0)
357 * h;
358 klo += 1;
359 khi += 1;
360 }
361 yi
362}
363
364fn splint(xa: &[f64], ya: &[f64], y2a: &[f64], n: usize, x: f64) -> f64 {
366 let mut klo = 0usize;
367 let mut khi = n - 1;
368 while (khi - klo) > 1 {
369 let k = (khi + klo) / 2;
370 if xa[k] > x {
371 khi = k;
372 } else {
373 klo = k;
374 }
375 }
376 let h = xa[khi] - xa[klo];
377 let a = (xa[khi] - x) / h;
378 let b = (x - xa[klo]) / h;
379 a * ya[klo]
380 + b * ya[khi]
381 + ((a * a * a - a) * y2a[klo] + (b * b * b - b) * y2a[khi]) * h * h / 6.0
382}
383
384fn spline(x: &[f64], y: &[f64], n: usize, yp1: f64, ypn: f64, y2: &mut [f64]) {
386 let mut u = [0.0_f64; 10];
387 if yp1 > 0.99E30 {
388 y2[0] = 0.0;
389 u[0] = 0.0;
390 } else {
391 y2[0] = -0.5;
392 u[0] = (3.0 / (x[1] - x[0])) * ((y[1] - y[0]) / (x[1] - x[0]) - yp1);
393 }
394 for i in 1..(n - 1) {
395 let sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
396 let p = sig * y2[i - 1] + 2.0;
397 y2[i] = (sig - 1.0) / p;
398 u[i] = (6.0
399 * ((y[i + 1] - y[i]) / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]))
400 / (x[i + 1] - x[i - 1])
401 - sig * u[i - 1])
402 / p;
403 }
404 let (qn, un) = if ypn > 0.99E30 {
405 (0.0, 0.0)
406 } else {
407 (
408 0.5,
409 (3.0 / (x[n - 1] - x[n - 2])) * (ypn - (y[n - 1] - y[n - 2]) / (x[n - 1] - x[n - 2])),
410 )
411 };
412 y2[n - 1] = (un - qn * u[n - 2]) / (qn * y2[n - 2] + 1.0);
413 for k in (0..=(n - 2)).rev() {
414 y2[k] = y2[k] * y2[k + 1] + u[k];
415 }
416}
417
418fn g0(a: f64, p: &[f64]) -> f64 {
420 a - 4.0
421 + (p[25] - 1.0)
422 * (a - 4.0
423 + (libm::exp(-(p[24] * p[24]).sqrt() * (a - 4.0)) - 1.0) / (p[24] * p[24]).sqrt())
424}
425
426fn sumex(ex: f64) -> f64 {
428 1.0 + (1.0 - libm::pow(ex, 19.0)) / (1.0 - ex) * libm::pow(ex, 0.5)
429}
430
431fn sg0(ex: f64, p: &[f64], ap: &[f64]) -> f64 {
433 (g0(ap[1], p)
434 + (g0(ap[2], p) * ex
435 + g0(ap[3], p) * ex * ex
436 + g0(ap[4], p) * libm::pow(ex, 3.0)
437 + (g0(ap[5], p) * libm::pow(ex, 4.0) + g0(ap[6], p) * libm::pow(ex, 12.0))
438 * (1.0 - libm::pow(ex, 8.0))
439 / (1.0 - ex)))
440 / sumex(ex)
441}
442
443#[derive(Clone)]
446struct State {
447 gsurf: f64,
448 re: f64,
449 dd: f64,
450 dm04: f64,
451 dm16: f64,
452 dm28: f64,
453 dm32: f64,
454 dm40: f64,
455 dm01: f64,
456 dm14: f64,
457 meso_tn1: [f64; 5],
458 meso_tn2: [f64; 4],
459 meso_tn3: [f64; 5],
460 meso_tgn1: [f64; 2],
461 meso_tgn2: [f64; 2],
462 meso_tgn3: [f64; 2],
463 dfa: f64,
464 plg: [[f64; 9]; 4],
465 ctloc: f64,
466 stloc: f64,
467 c2tloc: f64,
468 s2tloc: f64,
469 s3tloc: f64,
470 c3tloc: f64,
471 apdf: f64,
472 apt: [f64; 4],
473}
474
475impl State {
476 fn new() -> Self {
477 State {
478 gsurf: 0.0,
479 re: 0.0,
480 dd: 0.0,
481 dm04: 0.0,
482 dm16: 0.0,
483 dm28: 0.0,
484 dm32: 0.0,
485 dm40: 0.0,
486 dm01: 0.0,
487 dm14: 0.0,
488 meso_tn1: [0.0; 5],
489 meso_tn2: [0.0; 4],
490 meso_tn3: [0.0; 5],
491 meso_tgn1: [0.0; 2],
492 meso_tgn2: [0.0; 2],
493 meso_tgn3: [0.0; 2],
494 dfa: 0.0,
495 plg: [[0.0; 9]; 4],
496 ctloc: 0.0,
497 stloc: 0.0,
498 c2tloc: 0.0,
499 s2tloc: 0.0,
500 s3tloc: 0.0,
501 c3tloc: 0.0,
502 apdf: 0.0,
503 apt: [0.0; 4],
504 }
505 }
506
507 fn glatf(&mut self, lat: f64) {
510 let c2 = libm::cos(2.0 * DGTR * lat);
511 self.gsurf = 980.616 * (1.0 - 0.0026373 * c2);
512 self.re = 2.0 * self.gsurf / (3.085462E-6 + 2.27E-9 * c2) * 1.0E-5;
513 }
514
515 fn zeta(&self, zz: f64, zl: f64) -> f64 {
517 (zz - zl) * (self.re + zl) / (self.re + zz)
518 }
519
520 fn scalh(&self, alt: f64, xm: f64, temp: f64) -> f64 {
522 let g = self.gsurf / libm::pow(1.0 + alt / self.re, 2.0);
523 RGAS * temp / (g * xm)
524 }
525
526 #[allow(clippy::too_many_arguments)]
530 fn densm(
531 &self,
532 alt: f64,
533 d0: f64,
534 xm: f64,
535 tz: &mut f64,
536 mn3: usize,
537 zn3: &[f64],
538 tn3: &[f64],
539 tgn3: &[f64],
540 mn2: usize,
541 zn2: &[f64],
542 tn2: &[f64],
543 tgn2: &[f64],
544 ) -> f64 {
545 let mut xs = [0.0_f64; 10];
546 let mut ys = [0.0_f64; 10];
547 let mut y2out = [0.0_f64; 10];
548 let mut densm_tmp = d0;
549
550 if alt > zn2[0] {
551 if xm == 0.0 {
552 return *tz;
553 } else {
554 return d0;
555 }
556 }
557
558 let z = if alt > zn2[mn2 - 1] {
560 alt
561 } else {
562 zn2[mn2 - 1]
563 };
564 let mn = mn2;
565 let z1 = zn2[0];
566 let z2 = zn2[mn - 1];
567 let t1 = tn2[0];
568 let t2 = tn2[mn - 1];
569 let zg = self.zeta(z, z1);
570 let zgdif = self.zeta(z2, z1);
571
572 for k in 0..mn {
573 xs[k] = self.zeta(zn2[k], z1) / zgdif;
574 ys[k] = 1.0 / tn2[k];
575 }
576 let yd1 = -tgn2[0] / (t1 * t1) * zgdif;
577 let yd2 = -tgn2[1] / (t2 * t2) * zgdif * libm::pow((self.re + z2) / (self.re + z1), 2.0);
578
579 spline(&xs, &ys, mn, yd1, yd2, &mut y2out);
580 let x = zg / zgdif;
581 let y = splint(&xs, &ys, &y2out, mn, x);
582
583 *tz = 1.0 / y;
584 if xm != 0.0 {
585 let glb = self.gsurf / libm::pow(1.0 + z1 / self.re, 2.0);
586 let gamm = xm * glb * zgdif / RGAS;
587 let yi = splini(&xs, &ys, &y2out, mn, x);
588 let mut expl = gamm * yi;
589 if expl > 50.0 {
590 expl = 50.0;
591 }
592 densm_tmp = densm_tmp * (t1 / *tz) * libm::exp(-expl);
593 }
594
595 if alt > zn3[0] {
596 if xm == 0.0 {
597 return *tz;
598 } else {
599 return densm_tmp;
600 }
601 }
602
603 let z = alt;
605 let mn = mn3;
606 let z1 = zn3[0];
607 let z2 = zn3[mn - 1];
608 let t1 = tn3[0];
609 let t2 = tn3[mn - 1];
610 let zg = self.zeta(z, z1);
611 let zgdif = self.zeta(z2, z1);
612
613 for k in 0..mn {
614 xs[k] = self.zeta(zn3[k], z1) / zgdif;
615 ys[k] = 1.0 / tn3[k];
616 }
617 let yd1 = -tgn3[0] / (t1 * t1) * zgdif;
618 let yd2 = -tgn3[1] / (t2 * t2) * zgdif * libm::pow((self.re + z2) / (self.re + z1), 2.0);
619
620 spline(&xs, &ys, mn, yd1, yd2, &mut y2out);
621 let x = zg / zgdif;
622 let y = splint(&xs, &ys, &y2out, mn, x);
623
624 *tz = 1.0 / y;
625 if xm != 0.0 {
626 let glb = self.gsurf / libm::pow(1.0 + z1 / self.re, 2.0);
627 let gamm = xm * glb * zgdif / RGAS;
628 let yi = splini(&xs, &ys, &y2out, mn, x);
629 let mut expl = gamm * yi;
630 if expl > 50.0 {
631 expl = 50.0;
632 }
633 densm_tmp = densm_tmp * (t1 / *tz) * libm::exp(-expl);
634 }
635 if xm == 0.0 {
636 *tz
637 } else {
638 densm_tmp
639 }
640 }
641
642 #[allow(clippy::too_many_arguments)]
647 fn densu(
648 &self,
649 alt: f64,
650 dlb: f64,
651 tinf: f64,
652 tlb: f64,
653 xm: f64,
654 alpha: f64,
655 tz: &mut f64,
656 zlb: f64,
657 s2: f64,
658 mn1: usize,
659 zn1: &[f64],
660 tn1: &mut [f64],
661 tgn1: &mut [f64],
662 ) -> f64 {
663 let mut xs = [0.0_f64; 5];
664 let mut ys = [0.0_f64; 5];
665 let mut y2out = [0.0_f64; 5];
666 let mut densu_temp;
667
668 let za = zn1[0];
669 let z = if alt > za { alt } else { za };
670
671 let zg2 = self.zeta(z, zlb);
672
673 let tt = tinf - (tinf - tlb) * libm::exp(-s2 * zg2);
674 let ta = tt;
675 *tz = tt;
676 densu_temp = *tz;
677
678 let mut z1 = 0.0;
680 let mut zgdif = 0.0;
681 let mut mn = mn1;
682 let mut x = 0.0;
683 let mut t1 = 0.0;
684
685 if alt < za {
686 let dta = (tinf - ta) * s2 * libm::pow((self.re + zlb) / (self.re + za), 2.0);
687 tgn1[0] = dta;
688 tn1[0] = ta;
689 let z = if alt > zn1[mn1 - 1] {
690 alt
691 } else {
692 zn1[mn1 - 1]
693 };
694 mn = mn1;
695 z1 = zn1[0];
696 let z2 = zn1[mn - 1];
697 t1 = tn1[0];
698 let t2 = tn1[mn - 1];
699 let zg = self.zeta(z, z1);
700 zgdif = self.zeta(z2, z1);
701 for k in 0..mn {
702 xs[k] = self.zeta(zn1[k], z1) / zgdif;
703 ys[k] = 1.0 / tn1[k];
704 }
705 let yd1 = -tgn1[0] / (t1 * t1) * zgdif;
706 let yd2 =
707 -tgn1[1] / (t2 * t2) * zgdif * libm::pow((self.re + z2) / (self.re + z1), 2.0);
708 spline(&xs, &ys, mn, yd1, yd2, &mut y2out);
709 x = zg / zgdif;
710 let y = splint(&xs, &ys, &y2out, mn, x);
711 *tz = 1.0 / y;
712 densu_temp = *tz;
713 }
714 if xm == 0.0 {
715 return densu_temp;
716 }
717
718 let glb = self.gsurf / libm::pow(1.0 + zlb / self.re, 2.0);
719 let gamma = xm * glb / (s2 * RGAS * tinf);
720 let mut expl = libm::exp(-s2 * gamma * zg2);
721 if expl > 50.0 {
722 expl = 50.0;
723 }
724 if tt <= 0.0 {
725 expl = 50.0;
726 }
727
728 let densa = dlb * libm::pow(tlb / tt, 1.0 + alpha + gamma) * expl;
729 densu_temp = densa;
730 if alt >= za {
731 return densu_temp;
732 }
733
734 let glb = self.gsurf / libm::pow(1.0 + z1 / self.re, 2.0);
735 let gamm = xm * glb * zgdif / RGAS;
736
737 let yi = splini(&xs, &ys, &y2out, mn, x);
738 let mut expl = gamm * yi;
739 if expl > 50.0 {
740 expl = 50.0;
741 }
742 if *tz <= 0.0 {
743 expl = 50.0;
744 }
745
746 densu_temp * libm::pow(t1 / *tz, 1.0 + alpha) * libm::exp(-expl)
747 }
748
749 fn globe7(&mut self, p: &[f64], input: &NrlmsiseInput, flags: &Flags) -> f64 {
752 let mut t = [0.0_f64; 15];
753 let tloc = input.lst;
754
755 let c = libm::sin(input.g_lat * DGTR);
756 let s = libm::cos(input.g_lat * DGTR);
757 let c2 = c * c;
758 let c4 = c2 * c2;
759 let s2 = s * s;
760
761 self.plg[0][1] = c;
762 self.plg[0][2] = 0.5 * (3.0 * c2 - 1.0);
763 self.plg[0][3] = 0.5 * (5.0 * c * c2 - 3.0 * c);
764 self.plg[0][4] = (35.0 * c4 - 30.0 * c2 + 3.0) / 8.0;
765 self.plg[0][5] = (63.0 * c2 * c2 * c - 70.0 * c2 * c + 15.0 * c) / 8.0;
766 self.plg[0][6] = (11.0 * c * self.plg[0][5] - 5.0 * self.plg[0][4]) / 6.0;
767 self.plg[1][1] = s;
768 self.plg[1][2] = 3.0 * c * s;
769 self.plg[1][3] = 1.5 * (5.0 * c2 - 1.0) * s;
770 self.plg[1][4] = 2.5 * (7.0 * c2 * c - 3.0 * c) * s;
771 self.plg[1][5] = 1.875 * (21.0 * c4 - 14.0 * c2 + 1.0) * s;
772 self.plg[1][6] = (11.0 * c * self.plg[1][5] - 6.0 * self.plg[1][4]) / 5.0;
773 self.plg[2][2] = 3.0 * s2;
774 self.plg[2][3] = 15.0 * s2 * c;
775 self.plg[2][4] = 7.5 * (7.0 * c2 - 1.0) * s2;
776 self.plg[2][5] = 3.0 * c * self.plg[2][4] - 2.0 * self.plg[2][3];
777 self.plg[2][6] = (11.0 * c * self.plg[2][5] - 7.0 * self.plg[2][4]) / 4.0;
778 self.plg[2][7] = (13.0 * c * self.plg[2][6] - 8.0 * self.plg[2][5]) / 5.0;
779 self.plg[3][3] = 15.0 * s2 * s;
780 self.plg[3][4] = 105.0 * s2 * s * c;
781 self.plg[3][5] = (9.0 * c * self.plg[3][4] - 7.0 * self.plg[3][3]) / 2.0;
782 self.plg[3][6] = (11.0 * c * self.plg[3][5] - 8.0 * self.plg[3][4]) / 3.0;
783
784 if !(((flags.sw[7] == 0.0) && (flags.sw[8] == 0.0)) && (flags.sw[14] == 0.0)) {
785 self.stloc = libm::sin(HR * tloc);
786 self.ctloc = libm::cos(HR * tloc);
787 self.s2tloc = libm::sin(2.0 * HR * tloc);
788 self.c2tloc = libm::cos(2.0 * HR * tloc);
789 self.s3tloc = libm::sin(3.0 * HR * tloc);
790 self.c3tloc = libm::cos(3.0 * HR * tloc);
791 }
792
793 let doy = input.doy as f64;
794 let cd32 = libm::cos(DR * (doy - p[31]));
795 let cd18 = libm::cos(2.0 * DR * (doy - p[17]));
796 let cd14 = libm::cos(DR * (doy - p[13]));
797 let cd39 = libm::cos(2.0 * DR * (doy - p[38]));
798
799 let df = input.f107 - input.f107a;
801 self.dfa = input.f107a - 150.0;
802 let dfa = self.dfa;
803 t[0] = p[19] * df * (1.0 + p[59] * dfa)
804 + p[20] * df * df
805 + p[21] * dfa
806 + p[29] * libm::pow(dfa, 2.0);
807 let f1 = 1.0 + (p[47] * dfa + p[19] * df + p[20] * df * df) * flags.swc[1];
808 let f2 = 1.0 + (p[49] * dfa + p[19] * df + p[20] * df * df) * flags.swc[1];
809
810 t[1] = (p[1] * self.plg[0][2] + p[2] * self.plg[0][4] + p[22] * self.plg[0][6])
812 + (p[14] * self.plg[0][2]) * dfa * flags.swc[1]
813 + p[26] * self.plg[0][1];
814
815 t[2] = p[18] * cd32;
817
818 t[3] = (p[15] + p[16] * self.plg[0][2]) * cd18;
820
821 t[4] = f1 * (p[9] * self.plg[0][1] + p[10] * self.plg[0][3]) * cd14;
823
824 t[5] = p[37] * self.plg[0][1] * cd39;
826
827 if flags.sw[7] != 0.0 {
829 let t71 = (p[11] * self.plg[1][2]) * cd14 * flags.swc[5];
830 let t72 = (p[12] * self.plg[1][2]) * cd14 * flags.swc[5];
831 t[6] = f2
832 * ((p[3] * self.plg[1][1] + p[4] * self.plg[1][3] + p[27] * self.plg[1][5] + t71)
833 * self.ctloc
834 + (p[6] * self.plg[1][1]
835 + p[7] * self.plg[1][3]
836 + p[28] * self.plg[1][5]
837 + t72)
838 * self.stloc);
839 }
840
841 if flags.sw[8] != 0.0 {
843 let t81 = (p[23] * self.plg[2][3] + p[35] * self.plg[2][5]) * cd14 * flags.swc[5];
844 let t82 = (p[33] * self.plg[2][3] + p[36] * self.plg[2][5]) * cd14 * flags.swc[5];
845 t[7] = f2
846 * ((p[5] * self.plg[2][2] + p[41] * self.plg[2][4] + t81) * self.c2tloc
847 + (p[8] * self.plg[2][2] + p[42] * self.plg[2][4] + t82) * self.s2tloc);
848 }
849
850 if flags.sw[14] != 0.0 {
852 t[13] = f2
853 * ((p[39] * self.plg[3][3]
854 + (p[93] * self.plg[3][4] + p[46] * self.plg[3][6]) * cd14 * flags.swc[5])
855 * self.s3tloc
856 + (p[40] * self.plg[3][3]
857 + (p[94] * self.plg[3][4] + p[48] * self.plg[3][6]) * cd14 * flags.swc[5])
858 * self.c3tloc);
859 }
860
861 if flags.sw[9] == -1.0 {
863 let ap = input
867 .ap_array
868 .expect("ap_array must be present in Ap-history mode (validated at entry)");
869 if p[51] != 0.0 {
870 let mut pc = [0.0_f64; 150];
873 pc.copy_from_slice(p);
874 let mut exp1 = libm::exp(
875 -10800.0 * (pc[51] * pc[51]).sqrt()
876 / (1.0 + pc[138] * (45.0 - (input.g_lat * input.g_lat).sqrt())),
877 );
878 if exp1 > 0.99999 {
879 exp1 = 0.99999;
880 }
881 if pc[24] < 1.0E-4 {
882 pc[24] = 1.0E-4;
883 }
884 self.apt[0] = sg0(exp1, &pc, &ap);
885 if flags.sw[9] != 0.0 {
886 t[8] = self.apt[0]
887 * (p[50]
888 + p[96] * self.plg[0][2]
889 + p[54] * self.plg[0][4]
890 + (p[125] * self.plg[0][1]
891 + p[126] * self.plg[0][3]
892 + p[127] * self.plg[0][5])
893 * cd14
894 * flags.swc[5]
895 + (p[128] * self.plg[1][1]
896 + p[129] * self.plg[1][3]
897 + p[130] * self.plg[1][5])
898 * flags.swc[7]
899 * libm::cos(HR * (tloc - p[131])));
900 }
901 }
902 } else {
903 let apd = input.ap - 4.0;
904 let mut p44 = p[43];
905 let p45 = p[44];
906 if p44 < 0.0 {
907 p44 = 1.0E-5;
908 }
909 self.apdf = apd + (p45 - 1.0) * (apd + (libm::exp(-p44 * apd) - 1.0) / p44);
910 if flags.sw[9] != 0.0 {
911 t[8] = self.apdf
912 * (p[32]
913 + p[45] * self.plg[0][2]
914 + p[34] * self.plg[0][4]
915 + (p[100] * self.plg[0][1]
916 + p[101] * self.plg[0][3]
917 + p[102] * self.plg[0][5])
918 * cd14
919 * flags.swc[5]
920 + (p[121] * self.plg[1][1]
921 + p[122] * self.plg[1][3]
922 + p[123] * self.plg[1][5])
923 * flags.swc[7]
924 * libm::cos(HR * (tloc - p[124])));
925 }
926 }
927
928 if (flags.sw[10] != 0.0) && (input.g_long > -1000.0) {
929 if flags.sw[11] != 0.0 {
931 t[10] = (1.0 + p[80] * dfa * flags.swc[1])
932 * ((p[64] * self.plg[1][2]
933 + p[65] * self.plg[1][4]
934 + p[66] * self.plg[1][6]
935 + p[103] * self.plg[1][1]
936 + p[104] * self.plg[1][3]
937 + p[105] * self.plg[1][5]
938 + flags.swc[5]
939 * (p[109] * self.plg[1][1]
940 + p[110] * self.plg[1][3]
941 + p[111] * self.plg[1][5])
942 * cd14)
943 * libm::cos(DGTR * input.g_long)
944 + (p[90] * self.plg[1][2]
945 + p[91] * self.plg[1][4]
946 + p[92] * self.plg[1][6]
947 + p[106] * self.plg[1][1]
948 + p[107] * self.plg[1][3]
949 + p[108] * self.plg[1][5]
950 + flags.swc[5]
951 * (p[112] * self.plg[1][1]
952 + p[113] * self.plg[1][3]
953 + p[114] * self.plg[1][5])
954 * cd14)
955 * libm::sin(DGTR * input.g_long));
956 }
957
958 if flags.sw[12] != 0.0 {
960 t[11] = (1.0 + p[95] * self.plg[0][1])
961 * (1.0 + p[81] * dfa * flags.swc[1])
962 * (1.0 + p[119] * self.plg[0][1] * flags.swc[5] * cd14)
963 * ((p[68] * self.plg[0][1] + p[69] * self.plg[0][3] + p[70] * self.plg[0][5])
964 * libm::cos(SR * (input.sec - p[71])));
965 t[11] += flags.swc[11]
966 * (p[76] * self.plg[2][3] + p[77] * self.plg[2][5] + p[78] * self.plg[2][7])
967 * libm::cos(SR * (input.sec - p[79]) + 2.0 * DGTR * input.g_long)
968 * (1.0 + p[137] * dfa * flags.swc[1]);
969 }
970
971 if flags.sw[13] != 0.0 {
973 if flags.sw[9] == -1.0 {
974 if p[51] != 0.0 {
975 t[12] = self.apt[0]
976 * flags.swc[11]
977 * (1.0 + p[132] * self.plg[0][1])
978 * ((p[52] * self.plg[1][2]
979 + p[98] * self.plg[1][4]
980 + p[67] * self.plg[1][6])
981 * libm::cos(DGTR * (input.g_long - p[97])))
982 + self.apt[0]
983 * flags.swc[11]
984 * flags.swc[5]
985 * (p[133] * self.plg[1][1]
986 + p[134] * self.plg[1][3]
987 + p[135] * self.plg[1][5])
988 * cd14
989 * libm::cos(DGTR * (input.g_long - p[136]))
990 + self.apt[0]
991 * flags.swc[12]
992 * (p[55] * self.plg[0][1]
993 + p[56] * self.plg[0][3]
994 + p[57] * self.plg[0][5])
995 * libm::cos(SR * (input.sec - p[58]));
996 }
997 } else {
998 t[12] = self.apdf
999 * flags.swc[11]
1000 * (1.0 + p[120] * self.plg[0][1])
1001 * ((p[60] * self.plg[1][2]
1002 + p[61] * self.plg[1][4]
1003 + p[62] * self.plg[1][6])
1004 * libm::cos(DGTR * (input.g_long - p[63])))
1005 + self.apdf
1006 * flags.swc[11]
1007 * flags.swc[5]
1008 * (p[115] * self.plg[1][1]
1009 + p[116] * self.plg[1][3]
1010 + p[117] * self.plg[1][5])
1011 * cd14
1012 * libm::cos(DGTR * (input.g_long - p[118]))
1013 + self.apdf
1014 * flags.swc[12]
1015 * (p[83] * self.plg[0][1]
1016 + p[84] * self.plg[0][3]
1017 + p[85] * self.plg[0][5])
1018 * libm::cos(SR * (input.sec - p[75]));
1019 }
1020 }
1021 }
1022
1023 let mut tinf = p[30];
1024 for i in 0..14 {
1025 tinf += flags.sw[i + 1].abs() * t[i];
1026 }
1027 tinf
1028 }
1029
1030 fn glob7s(&self, p: &[f64], input: &NrlmsiseInput, flags: &Flags) -> f64 {
1033 let pset = 2.0;
1034 let mut t = [0.0_f64; 14];
1035 let p99 = if p[99] == 0.0 { pset } else { p[99] };
1036 if p99 != pset {
1037 return -1.0;
1038 }
1039 let doy = input.doy as f64;
1040 let cd32 = libm::cos(DR * (doy - p[31]));
1041 let cd18 = libm::cos(2.0 * DR * (doy - p[17]));
1042 let cd14 = libm::cos(DR * (doy - p[13]));
1043 let cd39 = libm::cos(2.0 * DR * (doy - p[38]));
1044
1045 t[0] = p[21] * self.dfa;
1047
1048 t[1] = p[1] * self.plg[0][2]
1050 + p[2] * self.plg[0][4]
1051 + p[22] * self.plg[0][6]
1052 + p[26] * self.plg[0][1]
1053 + p[14] * self.plg[0][3]
1054 + p[59] * self.plg[0][5];
1055
1056 t[2] = (p[18] + p[47] * self.plg[0][2] + p[29] * self.plg[0][4]) * cd32;
1058
1059 t[3] = (p[15] + p[16] * self.plg[0][2] + p[30] * self.plg[0][4]) * cd18;
1061
1062 t[4] = (p[9] * self.plg[0][1] + p[10] * self.plg[0][3] + p[20] * self.plg[0][5]) * cd14;
1064
1065 t[5] = (p[37] * self.plg[0][1]) * cd39;
1067
1068 if flags.sw[7] != 0.0 {
1070 let t71 = p[11] * self.plg[1][2] * cd14 * flags.swc[5];
1071 let t72 = p[12] * self.plg[1][2] * cd14 * flags.swc[5];
1072 t[6] = (p[3] * self.plg[1][1] + p[4] * self.plg[1][3] + t71) * self.ctloc
1073 + (p[6] * self.plg[1][1] + p[7] * self.plg[1][3] + t72) * self.stloc;
1074 }
1075
1076 if flags.sw[8] != 0.0 {
1078 let t81 = (p[23] * self.plg[2][3] + p[35] * self.plg[2][5]) * cd14 * flags.swc[5];
1079 let t82 = (p[33] * self.plg[2][3] + p[36] * self.plg[2][5]) * cd14 * flags.swc[5];
1080 t[7] = (p[5] * self.plg[2][2] + p[41] * self.plg[2][4] + t81) * self.c2tloc
1081 + (p[8] * self.plg[2][2] + p[42] * self.plg[2][4] + t82) * self.s2tloc;
1082 }
1083
1084 if flags.sw[14] != 0.0 {
1086 t[13] = p[39] * self.plg[3][3] * self.s3tloc + p[40] * self.plg[3][3] * self.c3tloc;
1087 }
1088
1089 if flags.sw[9] != 0.0 {
1091 if flags.sw[9] == 1.0 {
1092 t[8] = self.apdf * (p[32] + p[45] * self.plg[0][2] * flags.swc[2]);
1093 }
1094 if flags.sw[9] == -1.0 {
1095 t[8] = p[50] * self.apt[0] + p[96] * self.plg[0][2] * self.apt[0] * flags.swc[2];
1096 }
1097 }
1098
1099 if !((flags.sw[10] == 0.0) || (flags.sw[11] == 0.0) || (input.g_long <= -1000.0)) {
1101 t[10] = (1.0
1102 + self.plg[0][1]
1103 * (p[80] * flags.swc[5] * libm::cos(DR * (doy - p[81]))
1104 + p[85] * flags.swc[6] * libm::cos(2.0 * DR * (doy - p[86])))
1105 + p[83] * flags.swc[3] * libm::cos(DR * (doy - p[84]))
1106 + p[87] * flags.swc[4] * libm::cos(2.0 * DR * (doy - p[88])))
1107 * ((p[64] * self.plg[1][2]
1108 + p[65] * self.plg[1][4]
1109 + p[66] * self.plg[1][6]
1110 + p[74] * self.plg[1][1]
1111 + p[75] * self.plg[1][3]
1112 + p[76] * self.plg[1][5])
1113 * libm::cos(DGTR * input.g_long)
1114 + (p[90] * self.plg[1][2]
1115 + p[91] * self.plg[1][4]
1116 + p[92] * self.plg[1][6]
1117 + p[77] * self.plg[1][1]
1118 + p[78] * self.plg[1][3]
1119 + p[79] * self.plg[1][5])
1120 * libm::sin(DGTR * input.g_long));
1121 }
1122 let mut tt = 0.0;
1123 for i in 0..14 {
1124 tt += flags.sw[i + 1].abs() * t[i];
1125 }
1126 tt
1127 }
1128}
1129
1130impl State {
1131 fn gts7(&mut self, input: &NrlmsiseInput, flags: &Flags, output: &mut NrlmsiseOutput) {
1134 use tables::*;
1135
1136 let mut zn1 = [120.0, 110.0, 100.0, 90.0, 72.5];
1137 let mn1 = 5usize;
1138 let alpha = [-0.38, 0.0, 0.0, 0.0, 0.17, 0.0, -0.38, 0.0, 0.0];
1139 let altl = [200.0, 300.0, 160.0, 250.0, 240.0, 450.0, 320.0, 450.0];
1140
1141 let za = PDL[1][15];
1142 zn1[0] = za;
1143 for j in 0..9 {
1144 output.d[j] = 0.0;
1145 }
1146
1147 let tinf = if input.alt > zn1[0] {
1149 PTM[0] * PT[0] * (1.0 + flags.sw[16] * self.globe7(&PT, input, flags))
1150 } else {
1151 PTM[0] * PT[0]
1152 };
1153 output.t[0] = tinf;
1154
1155 let g0 = if input.alt > zn1[4] {
1157 PTM[3] * PS[0] * (1.0 + flags.sw[19] * self.globe7(&PS, input, flags))
1158 } else {
1159 PTM[3] * PS[0]
1160 };
1161 let tlb = PTM[1] * (1.0 + flags.sw[17] * self.globe7(&PD[3], input, flags)) * PD[3][0];
1162 let s = g0 / (tinf - tlb);
1163
1164 if input.alt < 300.0 {
1167 self.meso_tn1[1] =
1168 PTM[6] * PTL[0][0] / (1.0 - flags.sw[18] * self.glob7s(&PTL[0], input, flags));
1169 self.meso_tn1[2] =
1170 PTM[2] * PTL[1][0] / (1.0 - flags.sw[18] * self.glob7s(&PTL[1], input, flags));
1171 self.meso_tn1[3] =
1172 PTM[7] * PTL[2][0] / (1.0 - flags.sw[18] * self.glob7s(&PTL[2], input, flags));
1173 self.meso_tn1[4] = PTM[4] * PTL[3][0]
1174 / (1.0 - flags.sw[18] * flags.sw[20] * self.glob7s(&PTL[3], input, flags));
1175 self.meso_tgn1[1] = PTM[8]
1176 * PMA[8][0]
1177 * (1.0 + flags.sw[18] * flags.sw[20] * self.glob7s(&PMA[8], input, flags))
1178 * self.meso_tn1[4]
1179 * self.meso_tn1[4]
1180 / libm::pow(PTM[4] * PTL[3][0], 2.0);
1181 } else {
1182 self.meso_tn1[1] = PTM[6] * PTL[0][0];
1183 self.meso_tn1[2] = PTM[2] * PTL[1][0];
1184 self.meso_tn1[3] = PTM[7] * PTL[2][0];
1185 self.meso_tn1[4] = PTM[4] * PTL[3][0];
1186 self.meso_tgn1[1] = PTM[8] * PMA[8][0] * self.meso_tn1[4] * self.meso_tn1[4]
1187 / libm::pow(PTM[4] * PTL[3][0], 2.0);
1188 }
1189
1190 let g28 = flags.sw[21] * self.globe7(&PD[2], input, flags);
1192
1193 let zhf = PDL[1][24]
1195 * (1.0
1196 + flags.sw[5]
1197 * PDL[0][24]
1198 * libm::sin(DGTR * input.g_lat)
1199 * libm::cos(DR * (input.doy as f64 - PT[13])));
1200 output.t[0] = tinf;
1201 let xmm = PDM[2][4];
1202 let z = input.alt;
1203
1204 let mut tn1 = self.meso_tn1;
1207 let mut tgn1 = self.meso_tgn1;
1208
1209 let db28 = PDM[2][0] * libm::exp(g28) * PD[2][0];
1211 output.d[2] = self.densu(
1212 z,
1213 db28,
1214 tinf,
1215 tlb,
1216 28.0,
1217 alpha[2],
1218 &mut output.t[1],
1219 PTM[5],
1220 s,
1221 mn1,
1222 &zn1,
1223 &mut tn1,
1224 &mut tgn1,
1225 );
1226 let zh28 = PDM[2][2] * zhf;
1227 let zhm28 = PDM[2][3] * PDL[1][5];
1228 let xmd = 28.0 - xmm;
1229 let mut tz = 0.0;
1230 let b28 = self.densu(
1231 zh28,
1232 db28,
1233 tinf,
1234 tlb,
1235 xmd,
1236 alpha[2] - 1.0,
1237 &mut tz,
1238 PTM[5],
1239 s,
1240 mn1,
1241 &zn1,
1242 &mut tn1,
1243 &mut tgn1,
1244 );
1245 if (flags.sw[15] != 0.0) && (z <= altl[2]) {
1246 self.dm28 = self.densu(
1247 z, b28, tinf, tlb, xmm, alpha[2], &mut tz, PTM[5], s, mn1, &zn1, &mut tn1,
1248 &mut tgn1,
1249 );
1250 output.d[2] = dnet(output.d[2], self.dm28, zhm28, xmm, 28.0);
1251 }
1252
1253 let g4 = flags.sw[21] * self.globe7(&PD[0], input, flags);
1255 let db04 = PDM[0][0] * libm::exp(g4) * PD[0][0];
1256 output.d[0] = self.densu(
1257 z,
1258 db04,
1259 tinf,
1260 tlb,
1261 4.0,
1262 alpha[0],
1263 &mut output.t[1],
1264 PTM[5],
1265 s,
1266 mn1,
1267 &zn1,
1268 &mut tn1,
1269 &mut tgn1,
1270 );
1271 if (flags.sw[15] != 0.0) && (z < altl[0]) {
1272 let zh04 = PDM[0][2];
1273 let b04 = self.densu(
1274 zh04,
1275 db04,
1276 tinf,
1277 tlb,
1278 4.0 - xmm,
1279 alpha[0] - 1.0,
1280 &mut output.t[1],
1281 PTM[5],
1282 s,
1283 mn1,
1284 &zn1,
1285 &mut tn1,
1286 &mut tgn1,
1287 );
1288 self.dm04 = self.densu(
1289 z,
1290 b04,
1291 tinf,
1292 tlb,
1293 xmm,
1294 0.0,
1295 &mut output.t[1],
1296 PTM[5],
1297 s,
1298 mn1,
1299 &zn1,
1300 &mut tn1,
1301 &mut tgn1,
1302 );
1303 let zhm04 = zhm28;
1304 output.d[0] = dnet(output.d[0], self.dm04, zhm04, xmm, 4.0);
1305 let rl = libm::log(b28 * PDM[0][1] / b04);
1306 let zc04 = PDM[0][4] * PDL[1][0];
1307 let hc04 = PDM[0][5] * PDL[1][1];
1308 output.d[0] *= ccor(z, rl, hc04, zc04);
1309 }
1310
1311 let g16 = flags.sw[21] * self.globe7(&PD[1], input, flags);
1313 let db16 = PDM[1][0] * libm::exp(g16) * PD[1][0];
1314 output.d[1] = self.densu(
1315 z,
1316 db16,
1317 tinf,
1318 tlb,
1319 16.0,
1320 alpha[1],
1321 &mut output.t[1],
1322 PTM[5],
1323 s,
1324 mn1,
1325 &zn1,
1326 &mut tn1,
1327 &mut tgn1,
1328 );
1329 if (flags.sw[15] != 0.0) && (z <= altl[1]) {
1330 let zh16 = PDM[1][2];
1331 let b16 = self.densu(
1332 zh16,
1333 db16,
1334 tinf,
1335 tlb,
1336 16.0 - xmm,
1337 alpha[1] - 1.0,
1338 &mut output.t[1],
1339 PTM[5],
1340 s,
1341 mn1,
1342 &zn1,
1343 &mut tn1,
1344 &mut tgn1,
1345 );
1346 self.dm16 = self.densu(
1347 z,
1348 b16,
1349 tinf,
1350 tlb,
1351 xmm,
1352 0.0,
1353 &mut output.t[1],
1354 PTM[5],
1355 s,
1356 mn1,
1357 &zn1,
1358 &mut tn1,
1359 &mut tgn1,
1360 );
1361 let zhm16 = zhm28;
1362 output.d[1] = dnet(output.d[1], self.dm16, zhm16, xmm, 16.0);
1363 let rl =
1364 PDM[1][1] * PDL[1][16] * (1.0 + flags.sw[1] * PDL[0][23] * (input.f107a - 150.0));
1365 let hc16 = PDM[1][5] * PDL[1][3];
1366 let zc16 = PDM[1][4] * PDL[1][2];
1367 let hc216 = PDM[1][5] * PDL[1][4];
1368 output.d[1] *= ccor2(z, rl, hc16, zc16, hc216);
1369 let hcc16 = PDM[1][7] * PDL[1][13];
1370 let zcc16 = PDM[1][6] * PDL[1][12];
1371 let rc16 = PDM[1][3] * PDL[1][14];
1372 output.d[1] *= ccor(z, rc16, hcc16, zcc16);
1373 }
1374
1375 let g32 = flags.sw[21] * self.globe7(&PD[4], input, flags);
1377 let db32 = PDM[3][0] * libm::exp(g32) * PD[4][0];
1378 output.d[3] = self.densu(
1379 z,
1380 db32,
1381 tinf,
1382 tlb,
1383 32.0,
1384 alpha[3],
1385 &mut output.t[1],
1386 PTM[5],
1387 s,
1388 mn1,
1389 &zn1,
1390 &mut tn1,
1391 &mut tgn1,
1392 );
1393 if flags.sw[15] != 0.0 {
1394 if z <= altl[3] {
1395 let zh32 = PDM[3][2];
1396 let b32 = self.densu(
1397 zh32,
1398 db32,
1399 tinf,
1400 tlb,
1401 32.0 - xmm,
1402 alpha[3] - 1.0,
1403 &mut output.t[1],
1404 PTM[5],
1405 s,
1406 mn1,
1407 &zn1,
1408 &mut tn1,
1409 &mut tgn1,
1410 );
1411 self.dm32 = self.densu(
1412 z,
1413 b32,
1414 tinf,
1415 tlb,
1416 xmm,
1417 0.0,
1418 &mut output.t[1],
1419 PTM[5],
1420 s,
1421 mn1,
1422 &zn1,
1423 &mut tn1,
1424 &mut tgn1,
1425 );
1426 let zhm32 = zhm28;
1427 output.d[3] = dnet(output.d[3], self.dm32, zhm32, xmm, 32.0);
1428 let rl = libm::log(b28 * PDM[3][1] / b32);
1429 let hc32 = PDM[3][5] * PDL[1][7];
1430 let zc32 = PDM[3][4] * PDL[1][6];
1431 output.d[3] *= ccor(z, rl, hc32, zc32);
1432 }
1433 let hcc32 = PDM[3][7] * PDL[1][22];
1434 let hcc232 = PDM[3][7] * PDL[0][22];
1435 let zcc32 = PDM[3][6] * PDL[1][21];
1436 let rc32 =
1437 PDM[3][3] * PDL[1][23] * (1.0 + flags.sw[1] * PDL[0][23] * (input.f107a - 150.0));
1438 output.d[3] *= ccor2(z, rc32, hcc32, zcc32, hcc232);
1439 }
1440
1441 let g40 = flags.sw[21] * self.globe7(&PD[5], input, flags);
1443 let db40 = PDM[4][0] * libm::exp(g40) * PD[5][0];
1444 output.d[4] = self.densu(
1445 z,
1446 db40,
1447 tinf,
1448 tlb,
1449 40.0,
1450 alpha[4],
1451 &mut output.t[1],
1452 PTM[5],
1453 s,
1454 mn1,
1455 &zn1,
1456 &mut tn1,
1457 &mut tgn1,
1458 );
1459 if (flags.sw[15] != 0.0) && (z <= altl[4]) {
1460 let zh40 = PDM[4][2];
1461 let b40 = self.densu(
1462 zh40,
1463 db40,
1464 tinf,
1465 tlb,
1466 40.0 - xmm,
1467 alpha[4] - 1.0,
1468 &mut output.t[1],
1469 PTM[5],
1470 s,
1471 mn1,
1472 &zn1,
1473 &mut tn1,
1474 &mut tgn1,
1475 );
1476 self.dm40 = self.densu(
1477 z,
1478 b40,
1479 tinf,
1480 tlb,
1481 xmm,
1482 0.0,
1483 &mut output.t[1],
1484 PTM[5],
1485 s,
1486 mn1,
1487 &zn1,
1488 &mut tn1,
1489 &mut tgn1,
1490 );
1491 let zhm40 = zhm28;
1492 output.d[4] = dnet(output.d[4], self.dm40, zhm40, xmm, 40.0);
1493 let rl = libm::log(b28 * PDM[4][1] / b40);
1494 let hc40 = PDM[4][5] * PDL[1][9];
1495 let zc40 = PDM[4][4] * PDL[1][8];
1496 output.d[4] *= ccor(z, rl, hc40, zc40);
1497 }
1498
1499 let g1 = flags.sw[21] * self.globe7(&PD[6], input, flags);
1501 let db01 = PDM[5][0] * libm::exp(g1) * PD[6][0];
1502 output.d[6] = self.densu(
1503 z,
1504 db01,
1505 tinf,
1506 tlb,
1507 1.0,
1508 alpha[6],
1509 &mut output.t[1],
1510 PTM[5],
1511 s,
1512 mn1,
1513 &zn1,
1514 &mut tn1,
1515 &mut tgn1,
1516 );
1517 if (flags.sw[15] != 0.0) && (z <= altl[6]) {
1518 let zh01 = PDM[5][2];
1519 let b01 = self.densu(
1520 zh01,
1521 db01,
1522 tinf,
1523 tlb,
1524 1.0 - xmm,
1525 alpha[6] - 1.0,
1526 &mut output.t[1],
1527 PTM[5],
1528 s,
1529 mn1,
1530 &zn1,
1531 &mut tn1,
1532 &mut tgn1,
1533 );
1534 self.dm01 = self.densu(
1535 z,
1536 b01,
1537 tinf,
1538 tlb,
1539 xmm,
1540 0.0,
1541 &mut output.t[1],
1542 PTM[5],
1543 s,
1544 mn1,
1545 &zn1,
1546 &mut tn1,
1547 &mut tgn1,
1548 );
1549 let zhm01 = zhm28;
1550 output.d[6] = dnet(output.d[6], self.dm01, zhm01, xmm, 1.0);
1551 let rl = libm::log(b28 * PDM[5][1] * (PDL[1][17] * PDL[1][17]).sqrt() / b01);
1552 let hc01 = PDM[5][5] * PDL[1][11];
1553 let zc01 = PDM[5][4] * PDL[1][10];
1554 output.d[6] *= ccor(z, rl, hc01, zc01);
1555 let hcc01 = PDM[5][7] * PDL[1][19];
1556 let zcc01 = PDM[5][6] * PDL[1][18];
1557 let rc01 = PDM[5][3] * PDL[1][20];
1558 output.d[6] *= ccor(z, rc01, hcc01, zcc01);
1559 }
1560
1561 let g14 = flags.sw[21] * self.globe7(&PD[7], input, flags);
1563 let db14 = PDM[6][0] * libm::exp(g14) * PD[7][0];
1564 output.d[7] = self.densu(
1565 z,
1566 db14,
1567 tinf,
1568 tlb,
1569 14.0,
1570 alpha[7],
1571 &mut output.t[1],
1572 PTM[5],
1573 s,
1574 mn1,
1575 &zn1,
1576 &mut tn1,
1577 &mut tgn1,
1578 );
1579 if (flags.sw[15] != 0.0) && (z <= altl[7]) {
1580 let zh14 = PDM[6][2];
1581 let b14 = self.densu(
1582 zh14,
1583 db14,
1584 tinf,
1585 tlb,
1586 14.0 - xmm,
1587 alpha[7] - 1.0,
1588 &mut output.t[1],
1589 PTM[5],
1590 s,
1591 mn1,
1592 &zn1,
1593 &mut tn1,
1594 &mut tgn1,
1595 );
1596 self.dm14 = self.densu(
1597 z,
1598 b14,
1599 tinf,
1600 tlb,
1601 xmm,
1602 0.0,
1603 &mut output.t[1],
1604 PTM[5],
1605 s,
1606 mn1,
1607 &zn1,
1608 &mut tn1,
1609 &mut tgn1,
1610 );
1611 let zhm14 = zhm28;
1612 output.d[7] = dnet(output.d[7], self.dm14, zhm14, xmm, 14.0);
1613 let rl = libm::log(b28 * PDM[6][1] * (PDL[0][2] * PDL[0][2]).sqrt() / b14);
1614 let hc14 = PDM[6][5] * PDL[0][1];
1615 let zc14 = PDM[6][4] * PDL[0][0];
1616 output.d[7] *= ccor(z, rl, hc14, zc14);
1617 let hcc14 = PDM[6][7] * PDL[0][4];
1618 let zcc14 = PDM[6][6] * PDL[0][3];
1619 let rc14 = PDM[6][3] * PDL[0][5];
1620 output.d[7] *= ccor(z, rc14, hcc14, zcc14);
1621 }
1622
1623 let g16h = flags.sw[21] * self.globe7(&PD[8], input, flags);
1625 let db16h = PDM[7][0] * libm::exp(g16h) * PD[8][0];
1626 let tho = PDM[7][9] * PDL[0][6];
1627 let dd = self.densu(
1628 z,
1629 db16h,
1630 tho,
1631 tho,
1632 16.0,
1633 alpha[8],
1634 &mut output.t[1],
1635 PTM[5],
1636 s,
1637 mn1,
1638 &zn1,
1639 &mut tn1,
1640 &mut tgn1,
1641 );
1642 let zsht = PDM[7][5];
1643 let zmho = PDM[7][4];
1644 let zsho = self.scalh(zmho, 16.0, tho);
1645 output.d[8] = dd * libm::exp(-zsht / zsho * (libm::exp(-(z - zmho) / zsht) - 1.0));
1646
1647 output.d[5] = 1.66E-24
1649 * (4.0 * output.d[0]
1650 + 16.0 * output.d[1]
1651 + 28.0 * output.d[2]
1652 + 32.0 * output.d[3]
1653 + 40.0 * output.d[4]
1654 + output.d[6]
1655 + 14.0 * output.d[7]);
1656
1657 let z = (input.alt * input.alt).sqrt();
1659 let _ = self.densu(
1660 z,
1661 1.0,
1662 tinf,
1663 tlb,
1664 0.0,
1665 0.0,
1666 &mut output.t[1],
1667 PTM[5],
1668 s,
1669 mn1,
1670 &zn1,
1671 &mut tn1,
1672 &mut tgn1,
1673 );
1674
1675 self.meso_tn1 = tn1;
1677 self.meso_tgn1 = tgn1;
1678
1679 if flags.sw[0] != 0.0 {
1680 for i in 0..9 {
1681 output.d[i] *= 1.0E6;
1682 }
1683 output.d[5] /= 1000.0;
1684 }
1685 }
1686
1687 fn gtd7(&mut self, input: &NrlmsiseInput, flags: &Flags, output: &mut NrlmsiseOutput) {
1689 use tables::*;
1690
1691 let mn3 = 5usize;
1692 let zn3 = [32.5, 20.0, 15.0, 10.0, 0.0];
1693 let mn2 = 4usize;
1694 let zn2 = [72.5, 55.0, 45.0, 32.5];
1695 let zmix = 62.5;
1696
1697 let xlat = if flags.sw[2] == 0.0 {
1699 45.0
1700 } else {
1701 input.g_lat
1702 };
1703 self.glatf(xlat);
1704
1705 let xmm = PDM[2][4];
1706
1707 let altt = if input.alt > zn2[0] {
1709 input.alt
1710 } else {
1711 zn2[0]
1712 };
1713
1714 let mut sinput = *input;
1715 sinput.alt = altt;
1716 let mut soutput = NrlmsiseOutput::default();
1717 self.gts7(&sinput, flags, &mut soutput);
1718
1719 let dm28m = if flags.sw[0] != 0.0 {
1720 self.dm28 * 1.0E6
1721 } else {
1722 self.dm28
1723 };
1724 output.t[0] = soutput.t[0];
1725 output.t[1] = soutput.t[1];
1726 if input.alt >= zn2[0] {
1727 output.d = soutput.d;
1728 return;
1729 }
1730
1731 self.meso_tgn2[0] = self.meso_tgn1[1];
1733 self.meso_tn2[0] = self.meso_tn1[4];
1734 self.meso_tn2[1] =
1735 PMA[0][0] * PAVGM[0] / (1.0 - flags.sw[20] * self.glob7s(&PMA[0], input, flags));
1736 self.meso_tn2[2] =
1737 PMA[1][0] * PAVGM[1] / (1.0 - flags.sw[20] * self.glob7s(&PMA[1], input, flags));
1738 self.meso_tn2[3] = PMA[2][0] * PAVGM[2]
1739 / (1.0 - flags.sw[20] * flags.sw[22] * self.glob7s(&PMA[2], input, flags));
1740 self.meso_tgn2[1] = PAVGM[8]
1741 * PMA[9][0]
1742 * (1.0 + flags.sw[20] * flags.sw[22] * self.glob7s(&PMA[9], input, flags))
1743 * self.meso_tn2[3]
1744 * self.meso_tn2[3]
1745 / libm::pow(PMA[2][0] * PAVGM[2], 2.0);
1746 self.meso_tn3[0] = self.meso_tn2[3];
1747
1748 if input.alt < zn3[0] {
1749 self.meso_tgn3[0] = self.meso_tgn2[1];
1751 self.meso_tn3[1] =
1752 PMA[3][0] * PAVGM[3] / (1.0 - flags.sw[22] * self.glob7s(&PMA[3], input, flags));
1753 self.meso_tn3[2] =
1754 PMA[4][0] * PAVGM[4] / (1.0 - flags.sw[22] * self.glob7s(&PMA[4], input, flags));
1755 self.meso_tn3[3] =
1756 PMA[5][0] * PAVGM[5] / (1.0 - flags.sw[22] * self.glob7s(&PMA[5], input, flags));
1757 self.meso_tn3[4] =
1758 PMA[6][0] * PAVGM[6] / (1.0 - flags.sw[22] * self.glob7s(&PMA[6], input, flags));
1759 self.meso_tgn3[1] = PMA[7][0]
1760 * PAVGM[7]
1761 * (1.0 + flags.sw[22] * self.glob7s(&PMA[7], input, flags))
1762 * self.meso_tn3[4]
1763 * self.meso_tn3[4]
1764 / libm::pow(PMA[6][0] * PAVGM[6], 2.0);
1765 }
1766
1767 let mut dmc = 0.0;
1769 if input.alt > zmix {
1770 dmc = 1.0 - (zn2[0] - input.alt) / (zn2[0] - zmix);
1771 }
1772 let dz28 = soutput.d[2];
1773
1774 let tn2 = self.meso_tn2;
1776 let tgn2 = self.meso_tgn2;
1777 let tn3 = self.meso_tn3;
1778 let tgn3 = self.meso_tgn3;
1779 let mut tz = 0.0;
1780
1781 let dmr = soutput.d[2] / dm28m - 1.0;
1783 output.d[2] = self.densm(
1784 input.alt, dm28m, xmm, &mut tz, mn3, &zn3, &tn3, &tgn3, mn2, &zn2, &tn2, &tgn2,
1785 );
1786 output.d[2] *= 1.0 + dmr * dmc;
1787
1788 let dmr = soutput.d[0] / (dz28 * PDM[0][1]) - 1.0;
1790 output.d[0] = output.d[2] * PDM[0][1] * (1.0 + dmr * dmc);
1791
1792 output.d[1] = 0.0;
1794 output.d[8] = 0.0;
1795
1796 let dmr = soutput.d[3] / (dz28 * PDM[3][1]) - 1.0;
1798 output.d[3] = output.d[2] * PDM[3][1] * (1.0 + dmr * dmc);
1799
1800 let dmr = soutput.d[4] / (dz28 * PDM[4][1]) - 1.0;
1802 output.d[4] = output.d[2] * PDM[4][1] * (1.0 + dmr * dmc);
1803
1804 output.d[6] = 0.0;
1806
1807 output.d[7] = 0.0;
1809
1810 output.d[5] = 1.66E-24
1812 * (4.0 * output.d[0]
1813 + 16.0 * output.d[1]
1814 + 28.0 * output.d[2]
1815 + 32.0 * output.d[3]
1816 + 40.0 * output.d[4]
1817 + output.d[6]
1818 + 14.0 * output.d[7]);
1819
1820 if flags.sw[0] != 0.0 {
1821 output.d[5] /= 1000.0;
1822 }
1823
1824 self.dd = self.densm(
1826 input.alt, 1.0, 0.0, &mut tz, mn3, &zn3, &tn3, &tgn3, mn2, &zn2, &tn2, &tgn2,
1827 );
1828 output.t[1] = tz;
1829 }
1830
1831 fn gtd7d(&mut self, input: &NrlmsiseInput, flags: &Flags, output: &mut NrlmsiseOutput) {
1834 self.gtd7(input, flags, output);
1835 output.d[5] = 1.66E-24
1836 * (4.0 * output.d[0]
1837 + 16.0 * output.d[1]
1838 + 28.0 * output.d[2]
1839 + 32.0 * output.d[3]
1840 + 40.0 * output.d[4]
1841 + output.d[6]
1842 + 14.0 * output.d[7]
1843 + 16.0 * output.d[8]);
1844 if flags.sw[0] != 0.0 {
1845 output.d[5] /= 1000.0;
1846 }
1847 }
1848}
1849
1850pub fn gtd7(input: &NrlmsiseInput, flags: &Flags) -> Result<NrlmsiseOutput, AtmosphereError> {
1856 validate(input, flags)?;
1857 let mut output = NrlmsiseOutput::default();
1858 let mut state = State::new();
1859 state.gtd7(input, flags, &mut output);
1860 Ok(output)
1861}
1862
1863pub fn gtd7d(input: &NrlmsiseInput, flags: &Flags) -> Result<NrlmsiseOutput, AtmosphereError> {
1869 validate(input, flags)?;
1870 let mut output = NrlmsiseOutput::default();
1871 let mut state = State::new();
1872 state.gtd7d(input, flags, &mut output);
1873 Ok(output)
1874}
1875
1876pub fn nrlmsise00(input: &NrlmsiseInput) -> Result<NrlmsiseOutput, AtmosphereError> {
1884 gtd7d(input, &Flags::metric())
1885}
1886
1887pub fn nrlmsise00_with_lst(
1896 input: &NrlmsiseInput,
1897 lst: Option<f64>,
1898) -> Result<NrlmsiseOutput, AtmosphereError> {
1899 let mut input = *input;
1900 input.lst = lst.unwrap_or_else(|| local_solar_time(input.sec, input.g_long));
1901 nrlmsise00(&input)
1902}
1903
1904#[cfg(test)]
1905mod tests {
1906 #![allow(clippy::excessive_precision, clippy::unreadable_literal)]
1909 use super::*;
1910
1911 const GTD7_REF: [[f64; 11]; 17] = [
1914 [
1915 6.66517690495152026E+05,
1916 1.13880555975221708E+08,
1917 1.99821092557345442E+07,
1918 4.02276358571251098E+05,
1919 3.55746499451588579E+03,
1920 4.07471353275722314E-15,
1921 3.47531239971714167E+04,
1922 4.09591326829300169E+06,
1923 2.66727320933586889E+04,
1924 1.25053994356079943E+03,
1925 1.24141613001912060E+03,
1926 ],
1927 [
1928 3.40729322316091415E+06,
1929 1.58633336956916809E+08,
1930 1.39111736546111498E+07,
1931 3.26255950959554641E+05,
1932 1.55961815050122459E+03,
1933 5.00184572907224415E-15,
1934 4.85420846334025409E+04,
1935 4.38096671289862506E+06,
1936 6.95668195594226836E+03,
1937 1.16675438375720887E+03,
1938 1.16171045188704238E+03,
1939 ],
1940 [
1941 1.12376724403793560E+05,
1942 6.93413008676059981E+04,
1943 4.24710521747708185E+01,
1944 1.32275014147492764E-01,
1945 2.61884841823217900E-05,
1946 2.75677231926887105E-18,
1947 2.01674985432143185E+04,
1948 5.74125593414717332E+03,
1949 2.37439415198959796E+04,
1950 1.23989211171666511E+03,
1951 1.23989064013305870E+03,
1952 ],
1953 [
1954 5.41155437993667349E+07,
1955 1.91889344393930878E+11,
1956 6.11582559822463086E+12,
1957 1.22520105174012402E+12,
1958 6.02321197308486633E+10,
1959 3.58442630411333278E-10,
1960 1.05987969774054065E+07,
1961 2.61573669370513933E+05,
1962 2.81987935592833352E-42,
1963 1.02731846489999998E+03,
1964 2.06887776403605500E+02,
1965 ],
1966 [
1967 1.85112248619252769E+06,
1968 1.47655483792746186E+08,
1969 1.57935622826449610E+07,
1970 2.63379497731231386E+05,
1971 1.58878139838393008E+03,
1972 4.80963023940745105E-15,
1973 5.81616678078747354E+04,
1974 5.47898447906879056E+06,
1975 1.26444594176100850E+03,
1976 1.21239615212120930E+03,
1977 1.20813542521239174E+03,
1978 ],
1979 [
1980 8.67309523390615708E+05,
1981 1.27886176801412776E+08,
1982 1.82257662717170008E+07,
1983 2.92221419061824679E+05,
1984 2.40296243642370064E+03,
1985 4.35586564264464703E-15,
1986 3.68638924375054994E+04,
1987 3.89727550372696389E+06,
1988 2.66727320933586889E+04,
1989 1.22014641791503209E+03,
1990 1.21271208321180620E+03,
1991 ],
1992 [
1993 5.77625121602324420E+05,
1994 6.97913869366019815E+07,
1995 1.23681355982170273E+07,
1996 2.49286771542910225E+05,
1997 1.40573867417784300E+03,
1998 2.47065139166313234E-15,
1999 5.29198556706664021E+04,
2000 1.06981410936656618E+06,
2001 2.66727320933586780E+04,
2002 1.11638537604315161E+03,
2003 1.11299856821731100E+03,
2004 ],
2005 [
2006 3.74030410550766566E+05,
2007 4.78272012361134216E+07,
2008 5.24038003332420439E+06,
2009 1.75987464039060724E+05,
2010 5.50164877956996406E+02,
2011 1.57188873925484437E-15,
2012 8.89677572293503763E+04,
2013 1.97974083623295487E+06,
2014 9.12181487599149295E+03,
2015 1.03124744071455893E+03,
2016 1.02484849221300897E+03,
2017 ],
2018 [
2019 6.74833876662362367E+05,
2020 1.24531526044373140E+08,
2021 2.36900954105298519E+07,
2022 4.91158315474982315E+05,
2023 4.57878109905442034E+03,
2024 4.56442024536117137E-15,
2025 3.24459477516109328E+04,
2026 5.37083308708603773E+06,
2027 2.66727320933586889E+04,
2028 1.30605204202729215E+03,
2029 1.29337404038953400E+03,
2030 ],
2031 [
2032 5.52860084164518747E+05,
2033 1.19804132404135779E+08,
2034 3.49579776455820650E+07,
2035 9.33961835502814618E+05,
2036 1.09625476549342875E+04,
2037 4.97454311032222049E-15,
2038 2.68642785625980869E+04,
2039 4.88997423297139723E+06,
2040 2.80544483712566507E+04,
2041 1.36186802078492315E+03,
2042 1.34738918372970147E+03,
2043 ],
2044 [
2045 1.37548758418628516E+14,
2046 0.00000000000000000E+00,
2047 2.04968704429075456E+19,
2048 5.49869543371880755E+18,
2049 2.45173315802838592E+17,
2050 1.26106566111855011E-03,
2051 0.00000000000000000E+00,
2052 0.00000000000000000E+00,
2053 0.00000000000000000E+00,
2054 1.02731846489999998E+03,
2055 2.81464757663215607E+02,
2056 ],
2057 [
2058 4.42744258767709297E+13,
2059 0.00000000000000000E+00,
2060 6.59756715773731123E+18,
2061 1.76992934140618854E+18,
2062 7.89167995572748480E+16,
2063 4.05913937579917825E-04,
2064 0.00000000000000000E+00,
2065 0.00000000000000000E+00,
2066 0.00000000000000000E+00,
2067 1.02731846489999998E+03,
2068 2.27417980827261800E+02,
2069 ],
2070 [
2071 2.12782875620718823E+12,
2072 0.00000000000000000E+00,
2073 3.17079055035404288E+17,
2074 8.50627980943479040E+16,
2075 3.79274111680598850E+15,
2076 1.95082224517562141E-05,
2077 0.00000000000000000E+00,
2078 0.00000000000000000E+00,
2079 0.00000000000000000E+00,
2080 1.02731846489999998E+03,
2081 2.37438914587726885E+02,
2082 ],
2083 [
2084 1.41218354559285187E+11,
2085 0.00000000000000000E+00,
2086 2.10436964378315880E+16,
2087 5.64539244337708000E+15,
2088 2.51714174941122531E+14,
2089 1.29470901592856755E-06,
2090 0.00000000000000000E+00,
2091 0.00000000000000000E+00,
2092 0.00000000000000000E+00,
2093 1.02731846489999998E+03,
2094 2.79555112954127935E+02,
2095 ],
2096 [
2097 1.25488440027266960E+10,
2098 0.00000000000000000E+00,
2099 1.87453282921902300E+15,
2100 4.92305098078476250E+14,
2101 2.23968541385638359E+13,
2102 1.14766767151153664E-07,
2103 0.00000000000000000E+00,
2104 0.00000000000000000E+00,
2105 0.00000000000000000E+00,
2106 1.02731846489999998E+03,
2107 2.19073231364195721E+02,
2108 ],
2109 [
2110 5.19647740297288226E+05,
2111 1.27449407296046287E+08,
2112 4.85044986985335723E+07,
2113 1.72083798257490038E+06,
2114 2.35448659054442614E+04,
2115 5.88194044865163260E-15,
2116 2.50007839108092958E+04,
2117 6.27920982501879986E+06,
2118 2.66727320933586780E+04,
2119 1.42641166228242469E+03,
2120 1.40860779555326394E+03,
2121 ],
2122 [
2123 4.26085974879412130E+07,
2124 1.24134201554874313E+11,
2125 4.92956154248814258E+12,
2126 1.04840674909283203E+12,
2127 4.99346508305550461E+10,
2128 2.91430355030879247E-10,
2129 8.83122859257159382E+06,
2130 2.25251550862615462E+05,
2131 2.41524592964891382E-42,
2132 1.02731846489999998E+03,
2133 1.93407106257668147E+02,
2134 ],
2135 ];
2136 const GTD7D_RHO_REF: [f64; 17] = [
2138 4.07542196052162235E-15,
2139 5.00203049854499384E-15,
2140 3.38741140603730843E-18,
2141 3.58442630411333278E-10,
2142 4.80966382309166367E-15,
2143 4.35657407040904624E-15,
2144 2.47135981942753195E-15,
2145 1.57213101465795068E-15,
2146 4.56512867312557136E-15,
2147 4.97528823647096049E-15,
2148 1.26106566111855011E-03,
2149 4.05913937579917825E-04,
2150 1.95082224517562141E-05,
2151 1.29470901592856755E-06,
2152 1.14766767151153664E-07,
2153 5.88264887641603259E-15,
2154 2.91430355030879247E-10,
2155 ];
2156
2157 fn reference_cases() -> ([NrlmsiseInput; 17], usize, usize) {
2159 let aph: ApArray = [100.0; 7];
2160 let base = NrlmsiseInput {
2161 year: 0,
2162 doy: 172,
2163 sec: 29000.0,
2164 alt: 400.0,
2165 g_lat: 60.0,
2166 g_long: -70.0,
2167 lst: 16.0,
2168 f107a: 150.0,
2169 f107: 150.0,
2170 ap: 4.0,
2171 ap_array: None,
2172 };
2173 let mut input = [base; 17];
2174 input[1].doy = 81;
2175 input[2].sec = 75000.0;
2176 input[2].alt = 1000.0;
2177 input[3].alt = 100.0;
2178 input[10].alt = 0.0;
2179 input[11].alt = 10.0;
2180 input[12].alt = 30.0;
2181 input[13].alt = 50.0;
2182 input[14].alt = 70.0;
2183 input[16].alt = 100.0;
2184 input[4].g_lat = 0.0;
2185 input[5].g_long = 0.0;
2186 input[6].lst = 4.0;
2187 input[7].f107a = 70.0;
2188 input[8].f107 = 180.0;
2189 input[9].ap = 40.0;
2190 input[15].ap_array = Some(aph);
2191 input[16].ap_array = Some(aph);
2192 (input, 15, 17)
2195 }
2196
2197 fn rel_err(got: f64, want: f64) -> f64 {
2198 if want == 0.0 {
2199 got.abs()
2200 } else {
2201 ((got - want) / want).abs()
2202 }
2203 }
2204
2205 const REF_TOL: f64 = 1.0e-13;
2216
2217 #[test]
2218 fn gtd7_matches_reference_oracle() {
2219 let (input, n_scalar, n_total) = reference_cases();
2220 let mut worst = 0.0_f64;
2221 for (i, inp) in input.iter().enumerate() {
2222 let mut flags = Flags::standard();
2223 if i >= n_scalar && i < n_total {
2224 flags = Flags::new({
2225 let mut s = [1i32; 24];
2226 s[0] = 0;
2227 s[9] = -1;
2228 s
2229 });
2230 }
2231 let out = gtd7(inp, &flags).unwrap();
2232 for j in 0..9 {
2233 let want = GTD7_REF[i][j];
2234 let got = out.d[j];
2235 let e = rel_err(got, want);
2236 worst = worst.max(e);
2237 assert!(
2238 e <= REF_TOL,
2239 "case {i} d[{j}]: got {got:.17E} want {want:.17E} rel {e:.3E}"
2240 );
2241 }
2242 for k in 0..2 {
2243 let want = GTD7_REF[i][9 + k];
2244 let got = out.t[k];
2245 let e = rel_err(got, want);
2246 worst = worst.max(e);
2247 assert!(
2248 e <= REF_TOL,
2249 "case {i} t[{k}]: got {got:.17E} want {want:.17E} rel {e:.3E}"
2250 );
2251 }
2252 }
2253 assert!(worst <= REF_TOL, "worst relative error {worst:.3E}");
2254 }
2255
2256 #[test]
2257 fn gtd7d_total_density_matches_reference_oracle() {
2258 let (input, n_scalar, n_total) = reference_cases();
2259 for (i, inp) in input.iter().enumerate() {
2260 let mut flags = Flags::standard();
2261 if i >= n_scalar && i < n_total {
2262 flags = Flags::new({
2263 let mut s = [1i32; 24];
2264 s[0] = 0;
2265 s[9] = -1;
2266 s
2267 });
2268 }
2269 let out = gtd7d(inp, &flags).unwrap();
2270 let want = GTD7D_RHO_REF[i];
2271 let e = rel_err(out.d[5], want);
2272 assert!(
2273 e <= REF_TOL,
2274 "case {i} gtd7d rho: got {:.17E} want {want:.17E} rel {e:.3E}",
2275 out.d[5]
2276 );
2277 }
2278 }
2279
2280 #[test]
2281 fn nrlmsise00_metric_units() {
2282 let input = NrlmsiseInput {
2284 year: 0,
2285 doy: 172,
2286 sec: 29000.0,
2287 alt: 0.0,
2288 g_lat: 60.0,
2289 g_long: -70.0,
2290 lst: 16.0,
2291 f107a: 150.0,
2292 f107: 150.0,
2293 ap: 4.0,
2294 ap_array: None,
2295 };
2296 let out = nrlmsise00(&input).unwrap();
2297 assert!((out.density() - 1.26106566111855011).abs() < 1e-6);
2300 assert!(out.temperature_alt() > 270.0 && out.temperature_alt() < 290.0);
2301 }
2302
2303 #[test]
2304 fn density_decreases_with_altitude() {
2305 let make = |alt: f64| NrlmsiseInput {
2306 year: 0,
2307 doy: 172,
2308 sec: 29000.0,
2309 alt,
2310 g_lat: 60.0,
2311 g_long: -70.0,
2312 lst: 16.0,
2313 f107a: 150.0,
2314 f107: 150.0,
2315 ap: 4.0,
2316 ap_array: None,
2317 };
2318 let d0 = nrlmsise00(&make(0.0)).unwrap().density();
2319 let d200 = nrlmsise00(&make(200.0)).unwrap().density();
2320 let d400 = nrlmsise00(&make(400.0)).unwrap().density();
2321 let d800 = nrlmsise00(&make(800.0)).unwrap().density();
2322 assert!(d0 > d200 && d200 > d400 && d400 > d800);
2323 }
2324
2325 #[test]
2326 fn solar_activity_increases_thermospheric_density() {
2327 let make = |f107a: f64| NrlmsiseInput {
2328 year: 0,
2329 doy: 172,
2330 sec: 29000.0,
2331 alt: 400.0,
2332 g_lat: 60.0,
2333 g_long: -70.0,
2334 lst: 16.0,
2335 f107a,
2336 f107: f107a,
2337 ap: 4.0,
2338 ap_array: None,
2339 };
2340 assert!(
2341 nrlmsise00(&make(250.0)).unwrap().density()
2342 > nrlmsise00(&make(70.0)).unwrap().density()
2343 );
2344 }
2345
2346 #[test]
2347 fn local_solar_time_wraps() {
2348 assert!((local_solar_time(43200.0, 0.0) - 12.0).abs() < 0.001);
2349 assert!((local_solar_time(0.0, 0.0) - 0.0).abs() < 0.001);
2350 assert!((local_solar_time(0.0, 180.0) - 12.0).abs() < 0.001);
2351 }
2352
2353 fn sample_input() -> NrlmsiseInput {
2354 NrlmsiseInput {
2355 year: 0,
2356 doy: 172,
2357 sec: 29000.0,
2358 alt: 400.0,
2359 g_lat: 60.0,
2360 g_long: -70.0,
2361 lst: 16.0,
2362 f107a: 150.0,
2363 f107: 150.0,
2364 ap: 4.0,
2365 ap_array: None,
2366 }
2367 }
2368
2369 fn ap_history_flags() -> Flags {
2370 Flags::new({
2371 let mut s = [1i32; 24];
2372 s[0] = 0;
2373 s[9] = -1;
2374 s
2375 })
2376 }
2377
2378 #[test]
2379 fn ap_history_mode_without_array_is_rejected() {
2380 let input = sample_input(); let flags = ap_history_flags();
2382 assert_eq!(gtd7(&input, &flags), Err(AtmosphereError::MissingApArray));
2383 assert_eq!(gtd7d(&input, &flags), Err(AtmosphereError::MissingApArray));
2384 }
2385
2386 #[test]
2387 fn ap_history_mode_with_array_succeeds() {
2388 let mut input = sample_input();
2389 input.ap_array = Some([100.0; 7]);
2390 assert!(gtd7(&input, &ap_history_flags()).is_ok());
2391 }
2392
2393 #[test]
2394 fn non_finite_inputs_are_rejected() {
2395 type Mutate = fn(&mut NrlmsiseInput);
2396 let cases: [(Mutate, &str); 5] = [
2397 (|i| i.alt = f64::NAN, "alt"),
2398 (|i| i.f107 = f64::INFINITY, "f107"),
2399 (|i| i.f107a = f64::NAN, "f107a"),
2400 (|i| i.ap = f64::INFINITY, "ap"),
2401 (|i| i.g_lat = f64::NAN, "g_lat"),
2402 ];
2403 for (mutate, name) in cases {
2404 let mut input = sample_input();
2405 mutate(&mut input);
2406 assert_eq!(
2407 gtd7(&input, &Flags::metric()),
2408 Err(AtmosphereError::NonFiniteInput(name)),
2409 "expected non-finite rejection for {name}"
2410 );
2411 }
2412 }
2413
2414 #[test]
2415 fn out_of_domain_inputs_are_rejected() {
2416 let below = {
2417 let mut i = sample_input();
2418 i.alt = -1.0;
2419 i
2420 };
2421 assert_eq!(
2422 gtd7(&below, &Flags::metric()),
2423 Err(AtmosphereError::OutOfDomain("alt"))
2424 );
2425
2426 let above = {
2427 let mut i = sample_input();
2428 i.alt = MAX_ALTITUDE_KM + 1.0;
2429 i
2430 };
2431 assert_eq!(
2432 gtd7(&above, &Flags::metric()),
2433 Err(AtmosphereError::OutOfDomain("alt"))
2434 );
2435
2436 let neg_f107 = {
2437 let mut i = sample_input();
2438 i.f107 = -1.0;
2439 i
2440 };
2441 assert_eq!(
2442 gtd7(&neg_f107, &Flags::metric()),
2443 Err(AtmosphereError::OutOfDomain("f107"))
2444 );
2445
2446 let neg_ap = {
2447 let mut i = sample_input();
2448 i.ap = -1.0;
2449 i
2450 };
2451 assert_eq!(
2452 gtd7(&neg_ap, &Flags::metric()),
2453 Err(AtmosphereError::OutOfDomain("ap"))
2454 );
2455 }
2456
2457 #[test]
2458 fn domain_boundaries_are_inclusive() {
2459 let sea_level = {
2460 let mut i = sample_input();
2461 i.alt = 0.0;
2462 i
2463 };
2464 assert!(gtd7(&sea_level, &Flags::metric()).is_ok());
2465
2466 let top = {
2467 let mut i = sample_input();
2468 i.alt = MAX_ALTITUDE_KM;
2469 i
2470 };
2471 assert!(gtd7(&top, &Flags::metric()).is_ok());
2472 }
2473
2474 #[test]
2475 fn nrlmsise00_uses_drag_effective_total_density() {
2476 let mut input = sample_input();
2480 input.alt = 800.0;
2481 let drag = nrlmsise00(&input).unwrap();
2482 let no_anom = gtd7(&input, &Flags::metric()).unwrap();
2483 assert!(drag.d[8] > 0.0, "expected non-zero anomalous oxygen");
2484 assert!(
2485 drag.density() > no_anom.density(),
2486 "nrlmsise00 (gtd7d) total {} should exceed gtd7 total {}",
2487 drag.density(),
2488 no_anom.density()
2489 );
2490 }
2491}