The density of a condensed mixture, live¶
An ideal mixture's volumes add: a kilogram of it takes the mass-weighted sum of its components' volumes, so 1 / ρ = Σ wᵢ / ρᵢ, wᵢ the mass fractions. The library's kernel (lusine.properties.mixture_density) takes a game's whole table of parts, each a mixture's mass of a component, and is bound over the game's mixtures. Below, a game's tables are written out: water (1000 kg/m³) and quartz (2650 kg/m³), wet sand of equal masses of the two, water alone, three parts water to one of quartz in kilograms and in hundreds of kilograms, and a mixture whose quartz is swept from none to all of its mass, a hundredth a step, twenty steps a second, the kernel giving every mixture's density at each step on your GPU as you read.
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 sweep: the chart draws the swept mixture's density against its quartz's share of the mass, a point a step over one whole sweep, and beside it the same mixture worked out from its components' volumes added, 1 / ((1 − w) / ρ_water + w / ρ_quartz): the two lie one over the other, a curve below the straight line the densities' mean would draw. Below it, the share and the density there.
- The mixtures: wet sand, water alone, and three parts water to one of quartz in kilograms and in hundreds of kilograms.
- The checks, each held at every step: wet sand, at 1452.05 kg/m³ within 0.01; a component alone, water alone its own density to a rounding, and the swept mixture water's at no quartz and quartz's at all quartz; proportions, the same mixture in kilograms and in hundreds of kilograms the same within 0.00003%; between, and rising, the swept mixture between its two components and denser at every step as its quartz rises; the volumes add, its density within 0.0001% of the volumes added.
Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The components, the mixtures and the sweep are the program's tables and settings, which the host may write; this page offers no control to change them yet.
The program¶
The game's tables, the sweep, the kernel's answers and the checks, the panels the page draws, and the one procedure, main, run each step: the sweep writes the swept mixture's masses, the kernel is bound over the mixtures, and the checks are taken.
// The condensed mixture's density's live page: a game's tables of components, mixtures and parts, the library's
// mixture density bound over the mixtures, wet sand's quartz swept from none to all of its mass, against the volumes
// added, a component alone, the same mixture in other masses, and wet sand's own density, with the page's checks
// computed here on the GPU and shown by the panels the program declares.
namespace mixture_density;
lusine.version version = { { number = 1 } };
// The reader's settings: the sweep, how far each step moves the swept mixture's quartz by mass, from none.
host struct { float stride = 0.01; } settings;
// The game's components, each a density: water and quartz.
struct component { text name; float<kg/m3> density; };
table<component> components(capacity = 2, key = name) = {
{ name = "water", density = 1000.0 kg/m3 }, { name = "quartz", density = 2650.0 kg/m3 },
};
// The game's mixtures, each numbered, its density the kernel's: equal masses of water and quartz (wet sand), water
// alone in five kilograms, three parts water to one of quartz in kilograms and in hundreds of kilograms, and the swept
// mixture.
struct mixture { text name; integer number; float<kg/m3> density; };
table<mixture> mixtures(capacity = 8, key = name) = {
{ name = "wet", number = 0 }, { name = "water", number = 1 }, { name = "three", number = 2 },
{ name = "three00", number = 3 }, { name = "swept", number = 4 },
};
// The parts, each a mixture's mass of a component; the swept mixture's two the sweep writes (its water role 1, its
// quartz role 2).
struct part { relation<mixtures> mixture; float fraction; relation<components> component; integer role; };
table<part> parts(capacity = 16) = {
{ mixture = mixtures["wet"], fraction = 1.0, component = components["water"] },
{ mixture = mixtures["wet"], fraction = 1.0, component = components["quartz"] },
{ mixture = mixtures["water"], fraction = 5.0, component = components["water"] },
{ mixture = mixtures["three"], fraction = 3.0, component = components["water"] },
{ mixture = mixtures["three"], fraction = 1.0, component = components["quartz"] },
{ mixture = mixtures["three00"], fraction = 300.0, component = components["water"] },
{ mixture = mixtures["three00"], fraction = 100.0, component = components["quartz"] },
{ mixture = mixtures["swept"], fraction = 1.0, component = components["water"], role = 1 },
{ mixture = mixtures["swept"], fraction = 0.0, component = components["quartz"], role = 2 },
};
// The sweep: the step it is at, from 0 to 100 and round again, and the swept mixture's quartz's share of its mass.
struct { integer step = -1; float quartz; } sweep;
// Each mixture's density, read from its table; the swept one's at the last step; and the swept one with the volumes
// of its components added, 1 / ((1 - w) / rho_water + w / rho_quartz).
struct {
float<kg/m3> wet; float<kg/m3> water; float<kg/m3> three; float<kg/m3> three00; float<kg/m3> swept;
float<kg/m3> before; float<kg/m3> volumes;
} law;
// The checks: wet sand at 1452.05 kg/m3 within 0.01; water alone its own density to a rounding, and the swept mixture
// water's or quartz's at its ends; the same mixture in kilograms and in hundreds of kilograms the same within 0.00003%;
// the swept mixture between its components and denser at every step as its quartz rises; and its density the volumes
// added within 0.0001%.
struct {
bool wet = true; bool alone = true; bool proportions = true; bool between = true; bool volumes = 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);
}
// The swept mixture's masses for its quartz's share.
float swept(float fraction, integer role, float quartz)
{
return role == 1 ? 1.0 - quartz : role == 2 ? quartz : fraction;
}
float<kg/m3> density_of(table<float<kg/m3>> density, table<integer> number, integer which)
{
return sum(density) where number == which;
}
float<kg/m3> density(relation<components> row) { return row.density; }
float<kg/m3> volumes(relation<components> water, relation<components> quartz, float share)
{
return 1.0 / ((1.0 - share) / density(row = water) + share / density(row = quartz));
}
bool near(float<kg/m3> got, float<kg/m3> want, float share) { return abs(got - want) <= share * want; }
output table<lusine.widget> panels = {
lusine.series { { panel = "the sweep", chart = "density", quantity = law.swept, steps = 101, against = sweep.quartz,
label = "the kernel" } },
lusine.series { { panel = "the sweep", chart = "density", quantity = law.volumes, steps = 101,
against = sweep.quartz, label = "the volumes added" } },
lusine.value { { panel = "the sweep", quantity = sweep.quartz, label = "quartz, by mass" } },
lusine.value { { panel = "the sweep", quantity = law.swept, label = "density" } },
lusine.value { { panel = "the mixtures", quantity = law.wet, label = "wet sand" } },
lusine.value { { panel = "the mixtures", quantity = law.water, label = "water alone" } },
lusine.value { { panel = "the mixtures", quantity = law.three, label = "three to one, in kilograms" } },
lusine.value { { panel = "the mixtures", quantity = law.three00, label = "in hundreds of kilograms" } },
lusine.value { { panel = "the checks", quantity = checks.wet, label = "wet sand" } },
lusine.value { { panel = "the checks", quantity = checks.alone, label = "a component alone" } },
lusine.value { { panel = "the checks", quantity = checks.proportions, label = "proportions" } },
lusine.value { { panel = "the checks", quantity = checks.between, label = "between, and rising" } },
lusine.value { { panel = "the checks", quantity = checks.volumes, label = "the volumes add" } },
};
void main()
{
sweep.step = sweep.step >= 100 ? 0 : sweep.step + 1;
sweep.quartz = float(sweep.step) * settings.stride;
parts.fraction = swept(parts.fraction, parts.role, sweep.quartz);
mixtures.density = lusine.properties.mixture_density(parts);
law.before = law.swept;
law.wet = density_of(mixtures.density, mixtures.number, which = 0);
law.water = density_of(mixtures.density, mixtures.number, which = 1);
law.three = density_of(mixtures.density, mixtures.number, which = 2);
law.three00 = density_of(mixtures.density, mixtures.number, which = 3);
law.swept = density_of(mixtures.density, mixtures.number, which = 4);
law.volumes = volumes(water = components["water"], quartz = components["quartz"], share = sweep.quartz);
checks.wet = checks.wet && abs(law.wet - 1452.05 kg/m3) < 0.01 kg/m3;
checks.alone = checks.alone && near(got = law.water, want = density(row = components["water"]), share = 0.00000012)
&& (sweep.step != 0 || near(got = law.swept, want = density(row = components["water"]),
share = 0.00000012))
&& (sweep.step != 100 || near(got = law.swept, want = density(row = components["quartz"]),
share = 0.00000012));
checks.proportions = checks.proportions && near(got = law.three00, want = law.three, share = 0.0000003);
checks.between = checks.between && law.swept >= 0.9999998 * density(row = components["water"])
&& law.swept <= 1.0000002 * density(row = components["quartz"])
&& (sweep.step == 0 || law.swept > law.before);
checks.volumes = checks.volumes && near(got = law.swept, want = law.volumes, share = 0.000001);
lamps.colour = lamp_colour(holds = checks.wet && checks.alone && checks.proportions && checks.between
&& checks.volumes);
}
The law's other proof (every mixture bit for bit against a binary32 model of the kernel's folds, and a planted fault caught) is on its proof page.