Skip to content

The density of a gas, live

The ideal gas law as a density, ρ = p M / (R T), from a gas's pressure p, its temperature T and its molar mass M, with R the molar gas constant. Below, dry air's pressure is swept from none to 2 bar, 1250 Pa a step, twenty steps a second, and at each the library's law (lusine.properties.gas_density) gives its density at 250 K, 288.15 K and 350 K on your GPU as you read. Beside it the program holds the law to two standards it does not restate, the International Standard Atmosphere's sea-level air and the molar volume of an ideal gas, and to Boyle's and Charles's laws.

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.
  • Dry air: the chart draws the density against the pressure at the three temperatures, a point a step over one whole sweep: three straight lines through the origin, the coldest air the densest. Below it, the pressure and the density at 288.15 K.
  • The standards: dry air (28.9645 g/mol) at the standard sea level, 288.15 K and 101,325 Pa, beside the standard atmosphere's 1.225 kg/m³; and the mass of one molar volume of it at 273.15 K and 101,325 Pa, 22.413969 litres (CODATA's), beside the molar mass it must weigh.
  • The checks, each held at every step: sea-level air, within 0.001 kg/m³ of 1.225; molar volume, a molar volume weighing the molar mass within 0.0001%; Boyle, twice the pressure giving exactly twice the density; Charles, ρT the same at all three temperatures within 0.0001%.

Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The gas, the temperatures, the standards 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 each temperature and at the standards, and the checks are taken.

// The gas density's live page: dry air's pressure swept from none at three temperatures, the library's ideal-gas
// density at each, held to the International Standard Atmosphere's sea-level density, to the molar volume of an ideal
// gas and to Boyle's and Charles's laws, with the page's checks computed here on the GPU and shown by the panels the
// program declares.

namespace gas_density;

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

// The reader's settings: dry air's molar mass; three temperatures, cold, the standard sea level's and warm; the
// standard sea-level pressure and the ice point; the molar volume of an ideal gas at the ice point and that pressure
// (CODATA's, 22.413969 litres a mole); the standard atmosphere's sea-level density; and how far each step moves the
// pressure, from none.
host struct {
  float<kg/mol> molar_mass = 0.0289645 kg/mol;
  float<K> cold = 250.0 K; float<K> standard = 288.15 K; float<K> warm = 350.0 K;
  float<Pa> sea_level = 101325.0 Pa; float<K> ice_point = 273.15 K; float<m3/mol> molar_volume = 0.022413969 m3/mol;
  float<kg/m3> isa = 1.225 kg/m3; float<Pa> stride = 1250.0 Pa;
} settings;

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

// The air's density at the pressure at each temperature; at the standard sea level; and a mole's mass at the ice point,
// the density there times the molar volume.
struct {
  float<kg/m3> cold; float<kg/m3> standard; float<kg/m3> warm; float<kg/m3> sea_level; float<kg/mol> mole;
} law;

// The checks: the standard sea level's air within 0.001 kg/m3 of the standard atmosphere's 1.225; a molar volume of
// air at the ice point weighing its molar mass within 0.0001%; twice the pressure exactly twice the density (Boyle's
// law); and rho T the same at all three temperatures within 0.0001% (Charles's), each at every step.
struct { bool isa = true; bool molar_volume = true; bool boyle = true; bool charles = 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 doubles(float<Pa> pressure, float<K> temperature, float<kg/mol> molar_mass)
{
  return lusine.properties.gas_density(pressure = 2.0 * pressure, temperature, molar_mass)
         == 2.0 * lusine.properties.gas_density(pressure, temperature, molar_mass);
}

bool same(float<kg*K/m3> a, float<kg*K/m3> b) { return abs(a - b) <= 0.000001 * b; }

output table<lusine.widget> panels = {
  lusine.series { { panel = "dry air", chart = "density", quantity = law.cold, steps = 160, against = sweep.pressure,
                    label = "250 K" } },
  lusine.series { { panel = "dry air", chart = "density", quantity = law.standard, steps = 160,
                    against = sweep.pressure, label = "288.15 K" } },
  lusine.series { { panel = "dry air", chart = "density", quantity = law.warm, steps = 160, against = sweep.pressure,
                    label = "350 K" } },
  lusine.value { { panel = "dry air", quantity = sweep.pressure, label = "pressure" } },
  lusine.value { { panel = "dry air", quantity = law.standard, label = "at 288.15 K" } },
  lusine.value { { panel = "the standards", quantity = law.sea_level, label = "standard sea level" } },
  lusine.value { { panel = "the standards", quantity = settings.isa, label = "the standard atmosphere's" } },
  lusine.value { { panel = "the standards", quantity = law.mole, label = "a molar volume weighs" } },
  lusine.value { { panel = "the standards", quantity = settings.molar_mass, label = "the molar mass" } },
  lusine.value { { panel = "the checks", quantity = checks.isa, label = "sea-level air" } },
  lusine.value { { panel = "the checks", quantity = checks.molar_volume, label = "molar volume" } },
  lusine.value { { panel = "the checks", quantity = checks.boyle, label = "Boyle" } },
  lusine.value { { panel = "the checks", quantity = checks.charles, label = "Charles" } },
};

void main()
{
  sweep.step = sweep.step >= 159 ? 0 : sweep.step + 1;
  sweep.pressure = float(sweep.step) * settings.stride;
  law.cold = lusine.properties.gas_density(sweep.pressure, temperature = settings.cold, settings.molar_mass);
  law.standard = lusine.properties.gas_density(sweep.pressure, temperature = settings.standard, settings.molar_mass);
  law.warm = lusine.properties.gas_density(sweep.pressure, temperature = settings.warm, settings.molar_mass);
  law.sea_level = lusine.properties.gas_density(pressure = settings.sea_level, temperature = settings.standard,
                                                settings.molar_mass);
  law.mole = lusine.properties.gas_density(pressure = settings.sea_level, temperature = settings.ice_point,
                                           settings.molar_mass) * settings.molar_volume;
  checks.isa = checks.isa && abs(law.sea_level - settings.isa) < 0.001 kg/m3;
  checks.molar_volume = checks.molar_volume && abs(law.mole / settings.molar_mass - 1.0) <= 0.000001;
  checks.boyle = checks.boyle && doubles(sweep.pressure, temperature = settings.cold, settings.molar_mass)
                 && doubles(sweep.pressure, temperature = settings.warm, settings.molar_mass);
  checks.charles = checks.charles && same(a = law.cold * settings.cold, b = law.standard * settings.standard)
                   && same(a = law.warm * settings.warm, b = law.standard * settings.standard);
  lamps.colour = lamp_colour(holds = checks.isa && checks.molar_volume && checks.boyle && checks.charles);
}

The law's other proof (one gas alone, and the density over a sweep of temperatures) is on its proof page.