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