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