Skip to content

The standard atmosphere, live

The pressure of a world's standard atmosphere at an altitude: the hydrostatic equation, dp/dh = −ρ g, integrated up the world's layers, a power law where the temperature changes with height and an exponential where it holds, each layer's base pressure the layer below's carried across it from the world's sea-level pressure. Every number it runs on is a row's: the world's layers, its sea-level pressure and its air's molar mass, and the gas constant. Below, the altitude is swept up the Earth's standard atmosphere, ISO 2533's seven layers under the standard gravity, from sea level to 84.5 km, 500 m a step, twenty steps a second, and at each the library's law (lusine.laws.standard_atmosphere) gives the pressure on your GPU as you read. Beside it the same program takes the pressure's slope up the air from the law itself and compares it with the hydrostatic equation, the air's density there (lusine.properties.gas_density of the pressure and lusine.laws.standard_temperature) times gravity, and asks the law for the pressures ISO 2533 tabulates.

This page runs the program on your GPU through WebGPU, so it needs a browser with WebGPU (a recent Chrome, Edge, Safari or Firefox). Without it the program does not run and nothing is drawn below.

What it shows

  • The strip above the controls is green while every check holds, and turns red for good once one fails.
  • The air: the chart draws the pressure against the altitude (geopotential, as ISO 2533 gives it) on a log scale, a point a step over one whole sweep: from 101,325 Pa at sea level to under half a pascal at the top, five and a half orders of magnitude. Below it, the altitude and the pressure, and the pressure at 5 km on the Earth and on Mars, Mars under its own surface gravity.
  • The checks: the pressure's slope at the altitude, a central difference of the law over 50 m either side, beside −ρ g; ISO 2533, true while the law has given ISO 2533's pressure at each of its layers' bases and its top within 0.03% at every step (the standard was tabulated with an older gas constant, which parts it from today's by up to about 0.02%); hydrostatic, while the slope has stayed within 0.1% of −ρ g at every altitude; falls with height, while the pressure has fallen at every step up; sea level, while the law at sea level has given the world's sea-level pressure exactly.

Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The worlds, the gravity and the sweep are the program's settings, which the host may write; this page offers no control to change them yet.

The program

The sweep, the law's answers and the checks, the panels the page draws, and the one procedure, main, run each step: the sweep moves on, the law is asked at the altitude and either side of it, at ISO 2533's altitudes and at sea level, and the checks are taken.

// The standard atmosphere's live page: the altitude swept from sea level to the top of the Earth's standard
// atmosphere, the library's pressure at each, against ISO 2533's tabulated pressures and the hydrostatic equation
// itself, with the page's checks computed here on the GPU and shown by the panels the program declares.

namespace standard_atmosphere;

lusine.version version = { { number = 1 } };

// The reader's settings: the world swept, the Earth, and the gravity its standard atmosphere is defined under (ISO
// 2533's, the standard gravity); Mars, whose is the surface gravity its row keeps; the sweep's step, and the half-span
// of the difference the hydrostatic check takes.
host struct {
  relation<lusine.bodies> world = lusine.bodies["earth"]; float<m/s2> gravity = 9.80665 m/s2;
  relation<lusine.bodies> mars = lusine.bodies["mars"];
  float<m> stride = 500.0 m; float<m> span = 50.0 m;
} settings;

// The sweep: the step it is at, from 0 to 169 and round again, the geopotential altitude there, and whether a step
// has been taken before this one.
struct { integer step = -1; float<m> altitude; bool begun; } sweep;

// The pressure at the altitude and at the last step's; the air's density there (the library's, from the pressure and
// the standard temperature); the pressure's slope up the air, a central difference of the law, beside the hydrostatic
// equation's, -rho g; the pressure at sea level and its row's; and Mars's at 5 km, beside the Earth's.
struct {
  float<Pa> pressure; float<Pa> before; float<kg/m3> density; float<Pa/m> slope; float<Pa/m> hydrostatic;
  float<Pa> datum; float<Pa> row; float<Pa> mars_5km; float<Pa> earth_5km;
} law;

// The checks: ISO 2533's pressure at each of its seven layers' bases and its top, each within 0.03% (the standard was
// tabulated with an older gas constant, which parts it from today's by up to about 0.02%); the pressure's slope the
// hydrostatic equation's, within 0.1% at every altitude; the pressure falling with every step up; and at sea level, the
// row's sea-level pressure exactly.
struct { bool iso = true; bool hydrostatic = true; bool falls = true; bool datum = true; } checks;

// A strip of lamps the page draws, lit green while every check holds and red once one fails.
struct lamp { vector<float, 4> colour; };
lattice<lamp, 2> lamps(spacing = 1 m, boundary = wall);

vector<float, 4> lamp_colour(bool holds)
{
  return holds ? vector<float, 4>(0.15, 0.6, 0.3, 1.0) : vector<float, 4>(0.75, 0.15, 0.15, 1.0);
}

bool near(float<Pa> got, float<Pa> want) { return abs(got / want - 1.0) <= 0.0003; }

// ISO 2533's pressures at its layers' bases and its top (the U.S. Standard Atmosphere 1976's, which agrees).
bool iso_table(relation<lusine.bodies> world, float<m/s2> gravity)
{
  return near(got = lusine.laws.standard_atmosphere(world, altitude = 0.0 m, gravity), want = 101325.0 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 11000.0 m, gravity), want = 22632.06 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 20000.0 m, gravity), want = 5474.889 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 32000.0 m, gravity), want = 868.0187 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 47000.0 m, gravity), want = 110.9063 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 51000.0 m, gravity), want = 66.93887 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 71000.0 m, gravity), want = 3.95642 Pa)
      && near(got = lusine.laws.standard_atmosphere(world, altitude = 84852.0 m, gravity), want = 0.3733836 Pa);
}

// The law's slope at an altitude, a central difference over a span either side.
float<Pa/m> slope(relation<lusine.bodies> world, float<m> altitude, float<m/s2> gravity, float<m> span)
{
  return (lusine.laws.standard_atmosphere(world, altitude = altitude + span, gravity)
          - lusine.laws.standard_atmosphere(world, altitude = altitude - span, gravity)) / (2.0 * span);
}

float<kg/mol> molar_mass(relation<lusine.bodies> world) { return world.atmospheres.air_molar_mass; }
float<Pa> sea_level(relation<lusine.bodies> world) { return world.atmospheres.sea_level_pressure; }
float<m/s2> own_gravity(relation<lusine.bodies> world) { return world.surface_gravity; }

output table<lusine.widget> panels = {
  lusine.series { { panel = "the air", chart = "pressure", quantity = law.pressure, steps = 170,
                    against = sweep.altitude, log = true, label = "the Earth's" } },
  lusine.value { { panel = "the air", quantity = sweep.altitude, label = "altitude" } },
  lusine.value { { panel = "the air", quantity = law.pressure, label = "pressure" } },
  lusine.value { { panel = "the air", quantity = law.earth_5km, label = "the Earth at 5 km" } },
  lusine.value { { panel = "the air", quantity = law.mars_5km, label = "Mars at 5 km" } },
  lusine.value { { panel = "the checks", quantity = law.slope, label = "the pressure's slope" } },
  lusine.value { { panel = "the checks", quantity = law.hydrostatic, label = "-rho g" } },
  lusine.value { { panel = "the checks", quantity = checks.iso, label = "ISO 2533" } },
  lusine.value { { panel = "the checks", quantity = checks.hydrostatic, label = "hydrostatic" } },
  lusine.value { { panel = "the checks", quantity = checks.falls, label = "falls with height" } },
  lusine.value { { panel = "the checks", quantity = checks.datum, label = "sea level" } },
};

void main()
{
  sweep.step = sweep.step >= 169 ? 0 : sweep.step + 1;
  sweep.altitude = float(sweep.step) * settings.stride;
  law.before = law.pressure;
  law.pressure = lusine.laws.standard_atmosphere(settings.world, sweep.altitude, settings.gravity);
  law.density = lusine.properties.gas_density(law.pressure,
                                              temperature = lusine.laws.standard_temperature(settings.world,
                                                                                             sweep.altitude),
                                              molar_mass = molar_mass(settings.world));
  law.slope = slope(settings.world, sweep.altitude, settings.gravity, settings.span);
  law.hydrostatic = -law.density * settings.gravity;
  law.datum = lusine.laws.standard_atmosphere(settings.world, altitude = 0.0 m, settings.gravity);
  law.row = sea_level(settings.world);
  law.earth_5km = lusine.laws.standard_atmosphere(settings.world, altitude = 5000.0 m, settings.gravity);
  law.mars_5km = lusine.laws.standard_atmosphere(world = settings.mars, altitude = 5000.0 m,
                                                 gravity = own_gravity(world = settings.mars));
  checks.iso = checks.iso && iso_table(settings.world, settings.gravity);
  checks.hydrostatic = checks.hydrostatic && abs(law.slope / law.hydrostatic - 1.0) <= 0.001;
  checks.falls = checks.falls && (!sweep.begun || sweep.step == 0 || law.pressure < law.before);
  sweep.begun = true;
  checks.datum = checks.datum && law.datum == law.row;
  lamps.colour = lamp_colour(holds = checks.iso && checks.hydrostatic && checks.falls && checks.datum);
}

The law's other checks (the closed forms against the hydrostatic equation integrated numerically, each layer's base with the old gas constant, and a planted fault the checks refuse) are on its proof page.