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