The weighted mean, live¶
The weighted arithmetic mean of a table's members of the item's own, Σ wᵢ vᵢ / Σ wᵢ, each sum in Trait's fixed tree; 0 for an item with no member or with weights summing to nothing (lusine.statistics.weighted_mean). The kernel takes a game's whole table of members, each its owner, its value and its weight. Below, a game's tables of items and members are written out and the library's kernel gives every item's mean on your GPU as you read: Bevington's three measurements, values weighted 1 to 6 and the same tripled, values at equal weights, the limits, and a sweep that sets a value's weight to k beside k copies of it at weight 1.
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: one item holds 2.5 at weight k and 7 at weight 1; another holds k copies of 2.5 at weight 1 and 7 at weight 1. A weight is a count of copies, so the two means are one. The chart draws both against k, from 0 to 10, a step each: the mean falls from 7 toward 2.5, the two one over the other. Below it, k and the two means.
- The items: each item's mean, the kernel's.
- The checks: Bevington's mean, 10.2 ± 0.1, 9.8 ± 0.2 and 10.5 ± 0.5 weighted by 1/σ² averaging to 10.132 within 0.001 (Bevington & Robinson, ch. 4), two members of no weight beside them changing nothing; equal weights, 1 to 6 at weight 1 their plain mean, 3.5, exactly; the weights' scale, the weights 1 to 6 tripled leaving the mean within 8 unit roundoffs (2⁻²⁴); among its values, each mean between its members' least and greatest value; none, nothing, one, an item with no member and one whose weights sum to nothing reading 0, and one member its own value; weight k is k copies, the sweep's two means the same bits at every k. Each is held at every step.
Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The most copies the sweep reaches is the program's setting, which the host may write; this page offers no control to change it yet.
The program¶
The items, the members, the sweep and the checks, the panels the page draws, and the one procedure, main, run each step: the sweep's weights set, every item's mean from the kernel, and the checks.
// The weighted mean's live page: a game's tables of items and their members, the library's weighted mean bound over
// them, held to Bevington's weighted mean of three measurements, to the plain mean at equal weights, to the weights'
// scale, to lying among its values and to its limits, and a member weighted by k swept against k copies of it at
// weight 1, with the page's checks computed here on the GPU and shown by the panels the program declares.
namespace weighted_mean;
lusine.version version = { { number = 1 } };
// The reader's settings: the most copies the sweep reaches.
host struct { integer most = 10; } settings;
// The items, each with its mean, the kernel's: Bevington's three measurements (and two members of no weight);
// six values weighted 1 to 6, and the same weighted 3 to 18; a lone member; no member; three members of no weight;
// six values at equal weights; and the sweep's two, a value at weight k beside another at weight 1, and k copies of it
// at weight 1 beside the other.
struct item { text name; float mean; };
table<item> items(capacity = 16, key = name) = {
{ name = "plain" }, { name = "linear" }, { name = "tripled" }, { name = "lone" }, { name = "empty" },
{ name = "weightless" }, { name = "equal" }, { name = "weighted" }, { name = "copies" },
};
// The members, each its owner, its value and its weight; a sweep member's weight is the sweep's (role 1, the weighted
// value, k; role 2, a copy, 1 while its number is below k, else 0).
struct member { relation<items> owner; float value; float weight; integer role; integer copy; };
table<member> members(capacity = 48) = {
// Bevington & Robinson, ch. 4: 10.2 +- 0.1, 9.8 +- 0.2 and 10.5 +- 0.5, each weighted by 1 / sigma^2.
{ owner = items["plain"], value = 10.2, weight = 100.0 }, { owner = items["plain"], value = 9.8, weight = 25.0 },
{ owner = items["plain"], value = 10.5, weight = 4.0 }, { owner = items["plain"], value = 0.0, weight = 0.0 },
{ owner = items["plain"], value = 0.0, weight = 0.0 },
{ owner = items["linear"], value = 1.0, weight = 1.0 }, { owner = items["linear"], value = 2.0, weight = 2.0 },
{ owner = items["linear"], value = 3.0, weight = 3.0 }, { owner = items["linear"], value = 4.0, weight = 4.0 },
{ owner = items["linear"], value = 5.0, weight = 5.0 }, { owner = items["linear"], value = 6.0, weight = 6.0 },
{ owner = items["tripled"], value = 1.0, weight = 3.0 }, { owner = items["tripled"], value = 2.0, weight = 6.0 },
{ owner = items["tripled"], value = 3.0, weight = 9.0 }, { owner = items["tripled"], value = 4.0, weight = 12.0 },
{ owner = items["tripled"], value = 5.0, weight = 15.0 }, { owner = items["tripled"], value = 6.0, weight = 18.0 },
{ owner = items["lone"], value = 7.0, weight = 0.5 },
{ owner = items["weightless"], value = 1.0, weight = 0.0 },
{ owner = items["weightless"], value = 2.0, weight = 0.0 },
{ owner = items["weightless"], value = 3.0, weight = 0.0 },
{ owner = items["equal"], value = 1.0, weight = 1.0 }, { owner = items["equal"], value = 2.0, weight = 1.0 },
{ owner = items["equal"], value = 3.0, weight = 1.0 }, { owner = items["equal"], value = 4.0, weight = 1.0 },
{ owner = items["equal"], value = 5.0, weight = 1.0 }, { owner = items["equal"], value = 6.0, weight = 1.0 },
{ owner = items["weighted"], value = 2.5, role = 1 }, { owner = items["weighted"], value = 7.0, weight = 1.0 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 0 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 1 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 2 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 3 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 4 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 5 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 6 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 7 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 8 },
{ owner = items["copies"], value = 2.5, role = 2, copy = 9 },
{ owner = items["copies"], value = 7.0, weight = 1.0 },
};
// The sweep: k, from 0 to the most and round again, and as a float for the chart.
struct { integer k = -1; float copies; } sweep;
// Each item's mean, read from its row, for the panels.
struct {
float plain; float linear; float tripled; float lone; float empty; float weightless; float equal; float weighted;
float copies;
} means;
// The checks: Bevington's three measurements averaging to 10.132 within 0.001; six values at equal weights their plain
// mean, 3.5, exactly; the weights tripled leaving the mean within 8 unit roundoffs (2^-24); each mean among its
// members' values; no member, and weights summing to nothing, reading 0, and one member its own value; and at every k
// the value at weight k and k copies of it at weight 1 the same mean, bit for bit.
struct {
bool bevington = true; bool equal = true; bool scale = true; bool among = true; bool limits = true;
bool copies = 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);
}
float swept(float weight, integer role, integer copy, integer k)
{
if (role == 1) return float(k);
if (role == 2) return copy < k ? 1.0 : 0.0;
return weight;
}
float mean_of(relation<items> item) { return item.mean; }
// Whether an item's mean lies between the least and the greatest of its members' values.
bool among(table<relation<items>> owner, table<float> value, relation<items> item, float mean)
{
let which = item;
let least = min(value) where owner == which;
let most = max(value) where owner == which;
return mean >= least && mean <= most;
}
output table<lusine.widget> panels = {
lusine.series { { panel = "the sweep", chart = "mean", quantity = means.weighted, steps = 11, against = sweep.copies,
label = "weight k" } },
lusine.series { { panel = "the sweep", chart = "mean", quantity = means.copies, steps = 11, against = sweep.copies,
label = "k copies" } },
lusine.value { { panel = "the sweep", quantity = sweep.k, label = "k" } },
lusine.value { { panel = "the sweep", quantity = means.weighted, label = "2.5 at weight k, 7 at 1" } },
lusine.value { { panel = "the sweep", quantity = means.copies, label = "k copies of 2.5, 7" } },
lusine.value { { panel = "the items", quantity = means.plain, label = "Bevington's three" } },
lusine.value { { panel = "the items", quantity = means.linear, label = "weighted 1 to 6" } },
lusine.value { { panel = "the items", quantity = means.tripled, label = "weighted 3 to 18" } },
lusine.value { { panel = "the items", quantity = means.equal, label = "equal weights" } },
lusine.value { { panel = "the items", quantity = means.lone, label = "one member" } },
lusine.value { { panel = "the items", quantity = means.empty, label = "no member" } },
lusine.value { { panel = "the items", quantity = means.weightless, label = "no weight" } },
lusine.value { { panel = "the checks", quantity = checks.bevington, label = "Bevington's mean" } },
lusine.value { { panel = "the checks", quantity = checks.equal, label = "equal weights" } },
lusine.value { { panel = "the checks", quantity = checks.scale, label = "the weights' scale" } },
lusine.value { { panel = "the checks", quantity = checks.among, label = "among its values" } },
lusine.value { { panel = "the checks", quantity = checks.limits, label = "none, nothing, one" } },
lusine.value { { panel = "the checks", quantity = checks.copies, label = "weight k is k copies" } },
};
void main()
{
sweep.k = sweep.k >= settings.most ? 0 : sweep.k + 1;
sweep.copies = float(sweep.k);
members.weight = swept(members.weight, members.role, members.copy, sweep.k);
items.mean = lusine.statistics.weighted_mean(members);
means.plain = mean_of(item = items["plain"]);
means.linear = mean_of(item = items["linear"]);
means.tripled = mean_of(item = items["tripled"]);
means.lone = mean_of(item = items["lone"]);
means.empty = mean_of(item = items["empty"]);
means.weightless = mean_of(item = items["weightless"]);
means.equal = mean_of(item = items["equal"]);
means.weighted = mean_of(item = items["weighted"]);
means.copies = mean_of(item = items["copies"]);
checks.bevington = checks.bevington && abs(means.plain - 10.132) < 0.001;
checks.equal = checks.equal && means.equal == 3.5;
checks.scale = checks.scale && abs(means.tripled - means.linear) <= 8.0 / 16777216.0 * means.linear;
checks.among = checks.among
&& among(members.owner, members.value, item = items["plain"], mean = means.plain)
&& among(members.owner, members.value, item = items["linear"], mean = means.linear)
&& among(members.owner, members.value, item = items["weighted"], mean = means.weighted);
checks.limits = checks.limits && means.empty == 0.0 && means.weightless == 0.0 && means.lone == 7.0;
checks.copies = checks.copies && means.weighted == means.copies;
lamps.colour = lamp_colour(holds = checks.bevington && checks.equal && checks.scale && checks.among
&& checks.limits && checks.copies);
}
The kernel's other proof (three hundred random members against a float64 reference, the binary32 model bit for bit, a planted undivided sum) is on its proof page.