8.6 KiB
nrlmsise00
The NRLMSISE-00 empirical atmosphere model, generic over its scalar type.
Evaluated at f64 it gives density and temperature. Evaluated at a forward-mode dual
number it gives density and its exact derivatives, which is the reason this crate exists:
density falls by a factor of e about every fifty kilometers, so a drag partial that drops
its dependence on position is badly wrong rather than slightly wrong. Two other Rust
implementations exist and both are f64 only.
use nrlmsise00::{Input, MagneticActivity, Model};
let model = Model::new();
let output = model.gtd7d(&Input::consistent(
172, // day of year
29000.0, // seconds of universal time
400.0, // altitude, km
60.0, // latitude, degrees north
-70.0, // longitude, degrees east
150.0, // 81-day average F10.7, centered
150.0, // previous day's F10.7
MagneticActivity::Daily { ap: 4.0 },
));
// SI at the boundary: kg/m^3 and m^-3. Local solar time comes out at 3.4 h here, since
// `consistent` derives it from the universal time and the longitude.
assert!((output.total_mass - 2.400e-12).abs() < 0.001e-12);
assert!((output.temperature - 1098.2).abs() < 0.1);
And the derivative, through the model rather than around it:
# #[cfg(feature = "autodiff")]
# fn main() {
use num_dual::{Dual64, DualNum};
use nrlmsise00::{Input, MagneticActivity, Model, Scalar};
let model = Model::new();
let altitude = Dual64::from(400.0).derivative();
let output = model.gtd7d(&Input::consistent(
172,
Dual64::lift(29000.0),
altitude,
Dual64::lift(60.0),
Dual64::lift(-70.0),
Dual64::lift(150.0),
Dual64::lift(150.0),
MagneticActivity::Daily { ap: Dual64::lift(4.0) },
));
// d(rho)/dz, in kg/m^3 per km, and the scale height it implies.
let scale_height = -output.total_mass.re / output.total_mass.eps;
assert!((scale_height - 55.8).abs() < 0.1);
# }
# #[cfg(not(feature = "autodiff"))]
# fn main() {}
gtd7 or gtd7d
They differ in one number. gtd7's total mass density sums helium, atomic oxygen,
molecular nitrogen, molecular oxygen, argon, hydrogen and atomic nitrogen. gtd7d also
includes anomalous oxygen, which is hot atomic oxygen and is 18.6% of the mass at 1000 km
in the model's own standard case. A drag calculation wants gtd7d. The individual
number densities are identical either way.
How closely it matches the reference
At f64, bit for bit, over every case there is to compare against:
| What | Cases | Values compared |
|---|---|---|
| The standard test cases | 17 | 187 |
| A wide input grid, fifty altitudes against sixty scenarios under eight switch settings | 2880 | 31680 |
| The individual helpers, expansions and profiles | 819 | several thousand |
The comparisons are exact rather than approximate because an approximate one would not
notice a term dropped from an expansion whose contribution happens to be small where it is
sampled. The fixtures hold the reference's own output at seventeen significant digits and
are regenerated by the scripts in scripts/, which fetch
Dominik Brodowski's C translation of the NRL
Fortran, run it, and discard the source: what is committed is numbers, which are facts
about the model rather than anyone's code.
At a dual type the value agrees to about 1e-14 rather than exactly, because num-dual
computes a division as a reciprocal and a multiply. See the scalar module docs.
Where it deliberately differs from the reference
The reference keeps its working state in file-scope statics, so two of its answers depend on what was asked before them. Both are fixed here, and both are fixed by computing a value the reference reads without having written rather than by changing any arithmetic:
- At exactly 32.5 km, it splines through temperature nodes its own call never filled.
- With switch 15 off, below 72.5 km, it scales every species from a mixed nitrogen density its own call never computed.
These nodes and that density do not depend on altitude, so filling them reproduces what a preceding call would have left, which is why the grid fixture still matches at 32.5 km in sixty scenarios. Nothing else departs from the reference's control flow.
It is not continuous in altitude
Worth knowing before integrating through it. Each species' mixed correction is dropped above a fitted altitude by a test on the altitude alone, and the model's three vertical regions are joined by switches rather than smoothly, so the density steps. Sweeping 72.5 to 1000 km at ten meters and comparing each interval against the model's own derivative finds eight places where it does, and no others:
| Altitude | Total mass density | What happens there |
|---|---|---|
| 72.5 km | 1.0e-3 | the join between the model's two halves |
| 123.435 km | 1.5e-3 | za, the join between the Bates profile and the spline below it |
| 160 km | 7.1e-5 | molecular nitrogen's mixed correction is dropped |
| 240 km | 2.3e-6 | argon's |
| 250 km | 1.9e-5 | molecular oxygen's |
| 300 km | 5.3e-5 | atomic oxygen's, and the lower thermosphere temperature variation |
| 428.475 km | 6.9e-7 | atomic nitrogen's turbopause blend saturates, a step of about exp(-10) |
| 450 km | 3.5e-5 | atomic nitrogen's mixed correction is dropped |
Individual species move more than the total does — up to 3e-3 — because the species that steps is usually not the one carrying the mass. Above 500 km there are no jumps at all.
The temperature is continuous at all of these but za, where the exospheric temperature
steps by nearly two fifths: 1027.3 K to 1426.4 K in the standard case. That is the
reference's documented behavior rather than an artifact, its header saying the exospheric
temperature "is set to global average for altitudes below 120 km", and it is what makes
123.435 km the largest discontinuity in the model above 72.5 km.
What is not implemented
ghp7, the reference's pressure-level entry point, which answers at a given pressure
rather than a given altitude. It is a secant iteration wrapped around gtd7, so a caller
who wants it can write it against this crate; nothing in the model needs it.
Units and switches
The crate answers in SI by default — m^-3, kg/m^3 and K — using the model's own metric
switch, so the conversion happens in one place and is the reference's own. Units::Native
gives the cm^-3 and g/cm^3 the published cases are quoted in.
The twenty-three switches are named rather than indexed, and each has three states: the
reference's 2 keeps a term's cross terms while dropping the term itself. The reference's
switch 9 does two jobs, one of which is to select between a daily ap value and a
seven-element history; that is a statement about the data, so it lives on the input as
MagneticActivity and the mode cannot disagree with the values.
Drivers
The solar and geomagnetic values the model reads are measurements, and this crate does not
own them: f107, f107_average and the ap history are plain numbers on the input, so a
caller with its own record passes them straight in and pays nothing for the convenience it
is not using.
The drivers feature, on by default, is not implemented yet. It will add a
compiled-in record of those measurements and a lookup by date, for callers who would rather
not source their own. The feature is declared now so that turning it on later is not a
change to what default means.
The model is not this crate's work
NRLMSISE-00 is the work of Mike Picone, Alan Hedin and Doug Drob at the US Naval Research Laboratory, published as Picone et al. (2002), NRLMSISE-00 empirical model of the atmosphere: Statistical comparisons and scientific issues, J. Geophys. Res. 107(A12). It is believed to be in the public domain as a work of the US Government under 17 U.S.C. 105; NRL attached no explicit license to it. The coefficients compiled into this crate are the model's own.
MSIS is a registered trademark of the Government of the United States of America, as represented by the Secretary of the Navy. It is used here only to identify which model this implements, which is nominative fair use; this crate is not endorsed by or affiliated with NRL or the US Navy.
Later versions of the model — NRLMSIS 2.0 and 2.1 — are not here and cannot be, on
these terms: they are licensed for academic and non-commercial use only and their methods
are covered by US Patent 10,641,925. See nrlmsis2/readme.md in the repository.
License
MIT, for this implementation.