172 lines
8.0 KiB
Bash
Executable File
172 lines
8.0 KiB
Bash
Executable File
#!/bin/sh
|
|
# Generates tests/fixtures/grid-cases.csv: a wide sweep of the model's inputs, to catch
|
|
# what the seventeen standard cases happen not to exercise and to hold a performance
|
|
# change to the same answers.
|
|
#
|
|
# The seventeen vary one input at a time from one baseline. That leaves most of the input
|
|
# space untouched: no case combines a storm with low flux, none is in the southern
|
|
# hemisphere in winter, none sits just either side of the 72.5 and 120 km seams between the
|
|
# model's three vertical regions, and only two use the ap history. This sweeps a dense
|
|
# ladder of altitudes against a spread of the other inputs, and against eight switch
|
|
# settings.
|
|
#
|
|
# Fetches Dominik Brodowski's C translation of the NRL Fortran, runs it, and throws the
|
|
# source away. What is committed is the numbers, which are facts about the model rather than
|
|
# anyone's code. The model itself is the work of Picone, Hedin and Drob at the US Naval
|
|
# Research Laboratory. Needs a C compiler and network access.
|
|
#
|
|
# Usage: scripts/generate-grid-cases.sh
|
|
|
|
set -e
|
|
|
|
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
out="$root/tests/fixtures/grid-cases.csv"
|
|
work=$(mktemp -d)
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
base="https://raw.githubusercontent.com/magnific0/nrlmsise-00/master"
|
|
echo "fetching the reference from $base"
|
|
for f in nrlmsise-00.c nrlmsise-00.h nrlmsise-00_data.c; do
|
|
curl -sS --fail --max-time 120 -L "$base/$f" -o "$work/$f"
|
|
done
|
|
digest=$(cat "$work/nrlmsise-00.c" "$work/nrlmsise-00_data.c" | sha256sum | cut -d' ' -f1)
|
|
echo "source digest $digest"
|
|
|
|
cat > "$work/driver.c" <<'DRIVER'
|
|
/* Writes the grid as CSV: the inputs at the digits they need, the outputs at seventeen
|
|
* significant digits. Scenarios are drawn from a fixed linear congruential sequence, so
|
|
* the same grid comes out every time without a table of it having to be written down. */
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include "nrlmsise-00.h"
|
|
|
|
/* The altitude ladder. Dense through the lower thermosphere, where the model switches
|
|
* between its three vertical regions, and either side of every altitude at which a
|
|
* species' mixed correction is dropped. */
|
|
static const double ALTS[] = {
|
|
0.0, 0.5, 2.0, 5.0, 10.0, 20.0, 32.4, 32.5, 32.6, 45.0, 55.0, 62.5, 65.0, 70.0,
|
|
72.4, 72.5, 72.6, 80.0, 90.0, 100.0, 110.0, 119.0, 122.0, 123.0, 125.0, 140.0,
|
|
159.0, 161.0, 180.0, 199.0, 201.0, 220.0, 239.0, 241.0, 249.0, 251.0, 280.0,
|
|
299.0, 301.0, 319.0, 321.0, 360.0, 400.0, 449.0, 451.0, 500.0, 600.0, 700.0,
|
|
850.0, 1000.0
|
|
};
|
|
#define NALTS ((int)(sizeof ALTS / sizeof *ALTS))
|
|
|
|
/* How many scenarios of the other inputs to sweep the ladder against. */
|
|
#define NSCENARIOS 60
|
|
|
|
static unsigned long seed = 20261002UL;
|
|
|
|
/* A fixed sequence, so the grid is reproducible without being written down. */
|
|
static double next_unit(void) {
|
|
seed = seed * 6364136223846793005UL + 1442695040888963407UL;
|
|
return (double)((seed >> 33) & 0x7FFFFF) / (double)0x800000;
|
|
}
|
|
|
|
static double between(double low, double high) {
|
|
return low + (high - low) * next_unit();
|
|
}
|
|
|
|
int main(void) {
|
|
struct nrlmsise_output output;
|
|
struct nrlmsise_input input;
|
|
struct nrlmsise_flags flags;
|
|
struct ap_array aph;
|
|
int scenario, i, j, set;
|
|
char switches[25];
|
|
|
|
printf("switches,day_of_year,seconds_of_day,altitude_km,latitude_deg,longitude_deg,"
|
|
"local_solar_time_h,f107_average,f107,ap_daily,ap1,ap2,ap3,ap4,ap5,ap6,ap7,"
|
|
"he,o,n2,o2,ar,total_mass,h,n,anomalous_o,exospheric_temperature,temperature\n");
|
|
|
|
for (scenario = 0; scenario < NSCENARIOS; scenario++) {
|
|
/* Eight switch settings, cycled across the scenarios: everything on in each of
|
|
* the two magnetic modes, then one family at a time turned off or set to cross
|
|
* terms only. */
|
|
set = scenario % 8;
|
|
for (i = 0; i < 24; i++) flags.switches[i] = 1;
|
|
flags.switches[0] = 0;
|
|
switch (set) {
|
|
case 0: flags.switches[9] = -1; break;
|
|
case 1: break;
|
|
case 2: flags.switches[9] = 0; break;
|
|
case 3: flags.switches[9] = -1; flags.switches[15] = 0; break;
|
|
case 4: flags.switches[9] = -1; flags.switches[7] = 0;
|
|
flags.switches[8] = 0; flags.switches[14] = 0; break;
|
|
case 5: flags.switches[9] = -1; flags.switches[3] = 2;
|
|
flags.switches[5] = 2; flags.switches[6] = 2; break;
|
|
case 6: flags.switches[9] = -1; flags.switches[10] = 0; break;
|
|
case 7: flags.switches[9] = -1; flags.switches[16] = 0;
|
|
flags.switches[17] = 0; flags.switches[18] = 0;
|
|
flags.switches[20] = 0; flags.switches[22] = 0; break;
|
|
}
|
|
/* gtd7 resolves the switches itself; these are the raw values. */
|
|
for (i = 0; i < 24; i++) {
|
|
if (i == 9) {
|
|
switches[i] = flags.switches[i] == -1 ? 'h'
|
|
: (char)('0' + flags.switches[i]);
|
|
} else {
|
|
switches[i] = (char)('0' + flags.switches[i]);
|
|
}
|
|
}
|
|
switches[24] = '\0';
|
|
|
|
input.year = 0;
|
|
input.doy = 1 + (int)between(0.0, 365.0);
|
|
input.sec = between(0.0, 86400.0);
|
|
input.g_lat = between(-90.0, 90.0);
|
|
input.g_long = between(-180.0, 180.0);
|
|
/* Consistent with the universal time and the longitude, as the model asks, except
|
|
* where a scenario deliberately breaks it to exercise the independent inputs. */
|
|
input.lst = input.sec / 3600.0 + input.g_long / 15.0;
|
|
if (scenario % 7 == 3) input.lst = between(0.0, 24.0);
|
|
input.f107A = between(65.0, 250.0);
|
|
input.f107 = between(65.0, 300.0);
|
|
input.ap = between(0.0, 300.0);
|
|
for (i = 0; i < 7; i++) aph.a[i] = between(0.0, 300.0);
|
|
input.ap_a = &aph;
|
|
|
|
for (j = 0; j < NALTS; j++) {
|
|
input.alt = ALTS[j];
|
|
/* With switch 15 off, the reference reads a mixed nitrogen density that its
|
|
* own call never wrote, so below 72.5 km its answer depends on which call
|
|
* last happened to write that static. There is nothing to validate against
|
|
* there, so those rows are left out; the same switch setting above 72.5 km,
|
|
* where the value is unused, is kept. */
|
|
if (flags.switches[15] == 0 && input.alt < 72.5) continue;
|
|
gtd7(&input, &flags, &output);
|
|
printf("%s,%d,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
|
|
switches, input.doy, input.sec, input.alt, input.g_lat, input.g_long,
|
|
input.lst, input.f107A, input.f107, input.ap);
|
|
for (i = 0; i < 7; i++) printf(",%.17g", aph.a[i]);
|
|
for (i = 0; i < 9; i++) printf(",%.17e", output.d[i]);
|
|
printf(",%.17e,%.17e\n", output.t[0], output.t[1]);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
DRIVER
|
|
|
|
cc -O2 -o "$work/driver" "$work/nrlmsise-00.c" "$work/nrlmsise-00_data.c" "$work/driver.c" -lm
|
|
mkdir -p "$(dirname "$out")"
|
|
{
|
|
echo "# The NRLMSISE-00 model over a wide sweep of its inputs, for validating a port."
|
|
echo "# model: NRLMSISE-00, by Picone, Hedin and Drob at the US Naval Research Laboratory"
|
|
echo "# reference: $base, Dominik Brodowski's C translation of the NRL Fortran"
|
|
echo "# reference_sha256: $digest"
|
|
echo "# generated: $(date -u +%Y-%m-%d) by scripts/generate-grid-cases.sh"
|
|
echo "# units: the model's own, since switch 0 is left at its standard value."
|
|
echo "# altitude km, angles degrees, number densities cm^-3, total mass g/cm^3, K."
|
|
echo "# switches: the reference's 24, in order, as 0, 1 or 2; index 9 is h for the ap"
|
|
echo "# history mode, which is the reference's -1."
|
|
echo "# Rows with switch 15 off and an altitude below 72.5 km are absent: there the"
|
|
echo "# reference reads a density its own call never wrote, so its answer depends on"
|
|
echo "# what was asked before it and there is nothing to compare against."
|
|
"$work/driver"
|
|
} > "$out"
|
|
|
|
python3 -c "
|
|
rows = [l for l in open('$out') if not l.startswith('#')]
|
|
print(f'wrote tests/fixtures/grid-cases.csv: {len(rows) - 1} cases, {len(open(\"$out\").read()) / 1e6:.2f} MB')
|
|
"
|