Skip to content

The standard temperature, live

The temperature of a world's standard atmosphere at an altitude: its layers' piecewise-linear profile, T = T_b + a (h − h_b) in the layer the altitude is in, each layer's base h_b, its temperature there T_b and its gradient a a row of the library's atmosphere layers, the layer the highest whose base is at or below the altitude. Below, the altitude is swept up the Earth's standard atmosphere, ISO 2533's seven layers, 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_temperature, given the world as its row of the bodies) gives the temperature on your GPU as you read. Beside it the same program asks the law for the temperatures ISO 2533 tabulates, and checks each.

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 temperature against the altitude (geopotential, as ISO 2533 gives it), a point a step over one whole sweep: falling 6.5 K a kilometre through the troposphere, holding through the tropopause, warming through the stratosphere and cooling again above it. Below it, the altitude and the temperature, and Mars's temperature just below and at 7 km, where its fit steps by 0.95 K, kept as its source gives it.
  • The checks: the Earth's temperature at sea level and Mars's at its datum; ISO 2533, true while the law has given ISO 2533's temperature at each of its layers' bases, its top, points inside the layers and 2 km below sea level, each within 0.0001 K (the standard's last place is 0.001 K), at every step; the layers meet, while the temperature a hundredth of a metre below each base has been within a thousandth of a kelvin of the temperature at it; sea level, while at sea level, on the Earth and on Mars, the law has given the world's own sea-level temperature exactly.

Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The worlds and the sweep's step 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, at ISO 2533's altitudes, at the bases and at sea level, and the checks are taken.

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

namespace standard_temperature;

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

// The reader's settings: the world swept, the Earth, and the sweep's step; and Mars, the other world with an
// atmosphere.
host struct {
  relation<lusine.bodies> world = lusine.bodies["earth"]; relation<lusine.bodies> mars = lusine.bodies["mars"];
  float<m> stride = 500.0 m;
} settings;

// The sweep: the step it is at, from 0 to 169 and round again, and the geopotential altitude there.
struct { integer step = -1; float<m> altitude; } sweep;

// The temperature at the altitude, and at sea level on the Earth and on Mars, with each world's sea-level temperature
// as its row of the atmospheres gives it; and Mars's just below its fit's 7 km and at it, where the fit steps.
struct {
  float<K> temperature; float<K> earth_datum; float<K> earth_row; float<K> mars_datum; float<K> mars_row;
  float<K> mars_below; float<K> mars_at;
} law;

// The checks: ISO 2533's temperature at each of its seven layers' bases, the top, points inside the layers and 2 km
// below sea level, each within 0.0001 K (the standard's last place is 0.001 K); the layers meeting at every base, the
// profile continuous; and at sea level, on both worlds, the world's sea-level temperature exactly.
struct { bool iso = true; bool continuous = 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<K> got, float<K> want) { return abs(got - want) <= 0.0001 K; }

// ISO 2533's Table 4 (the U.S. Standard Atmosphere 1976's, which agrees), at geopotential altitudes.
bool iso_table(relation<lusine.bodies> world)
{
  return near(got = lusine.laws.standard_temperature(world, altitude = -2000.0 m), want = 301.15 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 0.0 m), want = 288.15 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 1000.0 m), want = 281.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 5000.0 m), want = 255.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 8000.0 m), want = 236.15 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 11000.0 m), want = 216.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 15000.0 m), want = 216.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 20000.0 m), want = 216.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 25000.0 m), want = 221.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 32000.0 m), want = 228.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 40000.0 m), want = 251.05 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 47000.0 m), want = 270.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 51000.0 m), want = 270.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 60000.0 m), want = 245.45 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 71000.0 m), want = 214.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 80000.0 m), want = 196.65 K)
      && near(got = lusine.laws.standard_temperature(world, altitude = 84852.0 m), want = 186.946 K);
}

// Whether the layers meet at a base: the temperature a hundredth of a metre below it within a thousandth of a kelvin of
// the temperature at it.
bool meets(relation<lusine.bodies> world, float<m> base)
{
  return abs(lusine.laws.standard_temperature(world, altitude = base - 0.01 m)
             - lusine.laws.standard_temperature(world, altitude = base)) <= 0.001 K;
}

bool layers_meet(relation<lusine.bodies> world)
{
  return meets(world, base = 11000.0 m) && meets(world, base = 20000.0 m) && meets(world, base = 32000.0 m)
      && meets(world, base = 47000.0 m) && meets(world, base = 51000.0 m) && meets(world, base = 71000.0 m);
}

float<K> sea_level(relation<lusine.bodies> world) { return world.atmospheres.sea_level_temperature; }

output table<lusine.widget> panels = {
  lusine.series { { panel = "the air", chart = "temperature", quantity = law.temperature, steps = 170,
                    against = sweep.altitude, label = "the Earth's" } },
  lusine.value { { panel = "the air", quantity = sweep.altitude, label = "altitude" } },
  lusine.value { { panel = "the air", quantity = law.temperature, label = "temperature" } },
  lusine.value { { panel = "the air", quantity = law.mars_below, label = "Mars, just below 7 km" } },
  lusine.value { { panel = "the air", quantity = law.mars_at, label = "Mars, at 7 km" } },
  lusine.value { { panel = "the checks", quantity = law.earth_datum, label = "the Earth at sea level" } },
  lusine.value { { panel = "the checks", quantity = law.mars_datum, label = "Mars at its datum" } },
  lusine.value { { panel = "the checks", quantity = checks.iso, label = "ISO 2533" } },
  lusine.value { { panel = "the checks", quantity = checks.continuous, label = "the layers meet" } },
  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.temperature = lusine.laws.standard_temperature(settings.world, sweep.altitude);
  law.earth_datum = lusine.laws.standard_temperature(settings.world, altitude = 0.0 m);
  law.mars_datum = lusine.laws.standard_temperature(world = settings.mars, altitude = 0.0 m);
  law.earth_row = sea_level(settings.world);
  law.mars_row = sea_level(world = settings.mars);
  law.mars_below = lusine.laws.standard_temperature(world = settings.mars, altitude = 6999.99 m);
  law.mars_at = lusine.laws.standard_temperature(world = settings.mars, altitude = 7000.0 m);
  checks.iso = checks.iso && iso_table(settings.world);
  checks.continuous = checks.continuous && layers_meet(settings.world);
  checks.datum = checks.datum && law.earth_datum == law.earth_row && law.mars_datum == law.mars_row;
  lamps.colour = lamp_colour(holds = checks.iso && checks.continuous && checks.datum);
}

The law's other checks (bit for bit against a binary32 model over 4,000 altitudes on the Earth and on Mars, and each layer's slope) are on its proof page.