Skip to content

Air exchange, live

A space's molar flux to the ambient atmosphere through the area it exchanges across, n = C (p − pₐ) A: out where the space is over the ambient pressure, in where it is under, nothing at equilibrium. Below, the space's pressure is swept from 2000 Pa under the ambient to 2000 Pa over it, 25 Pa a step, twenty steps a second, and at each the library's law (lusine.laws.air_exchange) gives the flux on your GPU as you read. Beside it the same program asks the law at the mirrored excess, with the area doubled and with the excess doubled, and checks what each must give.

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 exchange: the chart draws the flux out against the excess of the space's pressure over the ambient, a point a step over one whole sweep, and beside it the flux at the opposite excess, negated: the two lie one over the other, a straight line through zero. Below it, the excess, the flux, and the flux with twice the area and at twice the excess.
  • The checks, each held at every step of the sweep: follows the excess, gas leaving exactly where the space is over the ambient and entering exactly where it is under; mirror, the opposite excess giving exactly the opposite flux, bit for bit; linear, twice the area or twice the excess giving exactly twice the flux; no area, no flux, a face of no area passing nothing at any excess.

Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The ambient pressure (84,556 Pa, the International Standard Atmosphere's at 1500 m), the area (1 m²), the conductance (0.001 mol/(s Pa m²)) 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 excess and its variants, and the checks are taken.

// The air exchange law's live page: a space's pressure swept from below the ambient to above it, the library's flux
// through its face at each, beside the same law at the mirrored excess, a doubled area and a doubled excess, with the
// page's checks computed here on the GPU and shown by the panels the program declares.

namespace air_exchange;

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

// The reader's settings: the ambient pressure (the International Standard Atmosphere's at 1500 m), the face's area
// and conductance, and the sweep: the excess it starts at and how far each step moves it.
host struct {
  float<Pa> ambient = 84556.0 Pa; float<m2> area = 1.0 m2; float<mol/(s Pa m2)> conductance = 0.001 mol/(s Pa m2);
  float<Pa> lowest = -2000.0 Pa; float<Pa> stride = 25.0 Pa;
} settings;

// The sweep: the step it is at, from 0 to 160 and round again, starting from the middle, and the excess of the
// space's pressure over the ambient there.
struct { integer step = 79; float<Pa> excess; } sweep;

// The flux out at the excess, and the same law at the mirrored excess (its negation drawn), with the area doubled and
// with the excess doubled; and the checks: out where the space is over the ambient and in where it is under, the
// mirrored excess giving exactly the opposite flux, doubling either the area or the excess doubling the flux exactly,
// and a face of no area passing nothing.
struct { float<mol/s> flux; float<mol/s> mirrored; float<mol/s> doubled_area; float<mol/s> doubled_excess; } law;
struct { bool follows = true; bool mirror = true; bool linear = true; bool no_area = 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);
}

output table<lusine.widget> panels = {
  lusine.series { { panel = "the exchange", chart = "flux", quantity = law.flux, steps = 161, against = sweep.excess,
                    label = "flux out" } },
  lusine.series { { panel = "the exchange", chart = "flux", quantity = law.mirrored, steps = 161,
                    against = sweep.excess, label = "mirrored, negated" } },
  lusine.value { { panel = "the exchange", quantity = sweep.excess, label = "excess" } },
  lusine.value { { panel = "the exchange", quantity = law.flux, label = "flux out" } },
  lusine.value { { panel = "the exchange", quantity = law.doubled_area, label = "with twice the area" } },
  lusine.value { { panel = "the exchange", quantity = law.doubled_excess, label = "at twice the excess" } },
  lusine.value { { panel = "the checks", quantity = checks.follows, label = "follows the excess" } },
  lusine.value { { panel = "the checks", quantity = checks.mirror, label = "mirror" } },
  lusine.value { { panel = "the checks", quantity = checks.linear, label = "linear" } },
  lusine.value { { panel = "the checks", quantity = checks.no_area, label = "no area, no flux" } },
};

void main()
{
  sweep.step = sweep.step >= 160 ? 0 : sweep.step + 1;
  sweep.excess = settings.lowest + float(sweep.step) * settings.stride;
  law.flux = lusine.laws.air_exchange(pressure = settings.ambient + sweep.excess, settings.area,
                                      ambient_pressure = settings.ambient, settings.conductance);
  law.mirrored = -lusine.laws.air_exchange(pressure = settings.ambient - sweep.excess, settings.area,
                                           ambient_pressure = settings.ambient, settings.conductance);
  law.doubled_area = lusine.laws.air_exchange(pressure = settings.ambient + sweep.excess, area = 2.0 * settings.area,
                                              ambient_pressure = settings.ambient, settings.conductance);
  law.doubled_excess = lusine.laws.air_exchange(pressure = settings.ambient + 2.0 * sweep.excess, settings.area,
                                                ambient_pressure = settings.ambient, settings.conductance);
  checks.follows = checks.follows && (law.flux > 0.0 mol/s) == (sweep.excess > 0.0 Pa)
                   && (law.flux < 0.0 mol/s) == (sweep.excess < 0.0 Pa);
  checks.mirror = checks.mirror && law.mirrored == law.flux;
  checks.linear = checks.linear && law.doubled_area == 2.0 * law.flux && law.doubled_excess == 2.0 * law.flux;
  checks.no_area = checks.no_area
                   && lusine.laws.air_exchange(pressure = settings.ambient + sweep.excess, area = 0.0 m2,
                                               ambient_pressure = settings.ambient, settings.conductance) == 0.0 mol/s;
  lamps.colour = lamp_colour(holds = checks.follows && checks.mirror && checks.linear && checks.no_area);
}

The law's other proof (the flux at a few pressures and areas) is on its proof page.