Kinetic energy, live¶
A body's kinetic energy of translation, E = ½ m |v|², |v|² the sum of its velocity's components' squares. Below, a 0.7 kg boule is thrown at (3, 8, 2) m/s and stepped by the library's velocity Verlet (lusine.integrator.kick_drift, lusine.integrator.kick) under gravity, a hundredth of a second a step and a hundred steps a second, on your GPU as you read, and at each step the library's law (lusine.properties.kinetic_energy) gives its kinetic energy. Beside it the program adds the potential energy m g h it has climbed into: under gravity alone, energy is neither made nor lost, so the sum stays what the boule was thrown with. The law is also held to the textbook's values and to what it must do whichever way a body moves.
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 throw: the chart draws the kinetic energy, the potential energy m g h and their sum against the time since the throw, a point a step: the kinetic energy falls as the boule climbs and comes back as it falls, and the sum is flat. Every 1.6 seconds the boule is thrown again. Below it, the time, the kinetic energy, the sum and what it was thrown with.
- The checks: the energy of a 2.5 kg body at 4 m/s whose direction turns a little each step, over the sphere, and its worst miss of 20 J so far, a rounding or two; the textbook's values, 1 kg at 2 m/s carrying exactly 2 J, a 1500 kg car at 20 m/s exactly 300 kJ, and a 0.7 kg boule rolling at 3 m/s along z 3.15 J to a rounding; any direction, the turning body within 0.0001% of 20 J; scales, twice the speed carrying exactly four times the energy and three times the mass three times, to a rounding; at rest, and in a plane, a body at rest or of no mass carrying nothing, and one moving in x and z the same bits as in x and y; energy kept, the boule's kinetic and potential energy summing to what it was thrown with, within 0.001%. 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. Gravity, the boule, its throw, the step and the turning body are the program's settings, which the host may write; this page offers no control to change them yet.
The program¶
The boule, its energies and the checks, the panels the page draws, and the one procedure, main, run each step: one velocity Verlet step, the energies after it, and the checks.
// The kinetic energy's live page: a boule thrown in space and stepped by the library's velocity Verlet under gravity,
// its kinetic energy from the library's law traded with the potential energy m g h and their sum kept, and the law
// held to the textbook's values and to its invariants, with the page's checks computed here on the GPU and shown by
// the panels the program declares.
namespace kinetic_energy;
lusine.version version = { { number = 1 } };
// The reader's settings: gravity; the boule's mass; its throw, each component of its velocity; the step; how long each
// throw is followed; and a 2.5 kg body at 4 m/s whose direction turns a little each step, round and over the sphere.
host struct {
float<m/s2> gravity = 9.81 m/s2; float<kg> mass = 0.7 kg;
float<m/s> across = 3.0 m/s; float<m/s> up = 8.0 m/s; float<m/s> along = 2.0 m/s;
float<s> dt = 0.01 s; float<s> flight = 1.6 s; float<kg> turning_mass = 2.5 kg; float<m/s> turning_speed = 4.0 m/s;
} settings;
// The boule: where it is, how it moves, and its time since the throw.
struct { vector<float<m>, 3> position; vector<float<m/s>, 3> velocity; float<s> time; } boule;
// The boule's kinetic energy (the library's), its potential energy m g h above the hand, their sum and the sum it was
// thrown with; the turning body's direction's angle and its energy.
struct {
float<J> kinetic; float<J> potential; float<J> total; float<J> start; float turn; float<J> turning; float<J> worst;
} energy;
// The checks: 1 kg at 2 m/s carrying exactly 2 J, a 1500 kg car at 20 m/s exactly 300 kJ, and a 0.7 kg boule rolling at
// 3 m/s along z 3.15 J to a rounding; 2.5 kg at 4 m/s carrying 20 J within 0.0001% in every direction it turns
// through; twice the speed exactly four times the energy and three times the mass three times within 0.00005%; a body
// at rest, or of no mass, carrying nothing, and one moving in x and z the same bits as in x and y; and the thrown
// boule's kinetic and potential energy summing to what it was thrown with, within 0.001%, at every step. (The turning
// direction's point moves from -4 to 4 and round again.)
struct {
bool textbook = true; bool direction = true; bool scales = true; bool limits = true; bool kept = 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);
}
vector<float<m/s>, 3> along_x(float<m/s> speed) { return vector<float<m/s>, 3>(speed, 0.0 m/s, 0.0 m/s); }
vector<float<m/s>, 3> along_z(float<m/s> speed) { return vector<float<m/s>, 3>(0.0 m/s, 0.0 m/s, speed); }
vector<float<m/s2>, 3> down(float<m/s2> gravity) { return vector<float<m/s2>, 3>(0.0 m/s2, -gravity, 0.0 m/s2); }
vector<float<m>, 3> hand() { return vector<float<m>, 3>(0.0 m, 0.0 m, 0.0 m); }
bool textbook()
{
return lusine.properties.kinetic_energy(mass = 1.0 kg, velocity = along_x(speed = 2.0 m/s)) == 2.0 J
&& lusine.properties.kinetic_energy(mass = 1500.0 kg, velocity = along_x(speed = 20.0 m/s)) == 300000.0 J
&& abs(lusine.properties.kinetic_energy(mass = 0.7 kg, velocity = along_z(speed = 3.0 m/s)) - 3.15 J)
<= 0.0000005 * 3.15 J;
}
// A direction on the sphere from a point (a, b) of the plane, by the stereographic projection, which takes every point
// to a unit vector with no trigonometry: (2a, 2b, 1 - a^2 - b^2) / (1 + a^2 + b^2).
vector<float<m/s>, 3> heading(float<m/s> speed, float turn)
{
let a = turn;
let b = turn / 3.0 - 0.5;
let q = 1.0 + a * a + b * b;
return vector<float<m/s>, 3>(speed * (2.0 * a / q), speed * (2.0 * b / q), speed * ((1.0 - a * a - b * b) / q));
}
bool scales(float<kg> mass, vector<float<m/s>, 3> velocity)
{
let once = lusine.properties.kinetic_energy(mass, velocity);
return lusine.properties.kinetic_energy(mass, velocity = 2.0 * velocity) == 4.0 * once
&& abs(lusine.properties.kinetic_energy(mass = 3.0 * mass, velocity) - 3.0 * once) <= 0.0000005 * 3.0 * once;
}
// At rest or of no mass, nothing; in x and z the same bits as in x and y.
bool limits(vector<float<m/s>, 3> velocity)
{
let still = vector<float<m/s>, 3>(0.0 m/s, 0.0 m/s, 0.0 m/s);
let xz = vector<float<m/s>, 3>(velocity.x, 0.0 m/s, velocity.y);
let xy = vector<float<m/s>, 3>(velocity.x, velocity.y, 0.0 m/s);
return lusine.properties.kinetic_energy(mass = 5.0 kg, velocity = still) == 0.0 J
&& lusine.properties.kinetic_energy(mass = 0.0 kg, velocity) == 0.0 J
&& lusine.properties.kinetic_energy(mass = 0.7 kg, velocity = xz)
== lusine.properties.kinetic_energy(mass = 0.7 kg, velocity = xy);
}
output table<lusine.widget> panels = {
lusine.series { { panel = "the throw", chart = "energy", quantity = energy.kinetic, steps = 160,
against = boule.time, label = "kinetic" } },
lusine.series { { panel = "the throw", chart = "energy", quantity = energy.potential, steps = 160,
against = boule.time, label = "m g h" } },
lusine.series { { panel = "the throw", chart = "energy", quantity = energy.total, steps = 160,
against = boule.time, label = "their sum" } },
lusine.value { { panel = "the throw", quantity = boule.time, label = "time" } },
lusine.value { { panel = "the throw", quantity = energy.kinetic, label = "kinetic" } },
lusine.value { { panel = "the throw", quantity = energy.total, label = "their sum" } },
lusine.value { { panel = "the throw", quantity = energy.start, label = "thrown with" } },
lusine.value { { panel = "the checks", quantity = energy.turning, label = "2.5 kg at 4 m/s, turning" } },
lusine.value { { panel = "the checks", quantity = energy.worst, label = "its worst miss of 20 J" } },
lusine.value { { panel = "the checks", quantity = checks.textbook, label = "the textbook's values" } },
lusine.value { { panel = "the checks", quantity = checks.direction, label = "any direction" } },
lusine.value { { panel = "the checks", quantity = checks.scales, label = "scales" } },
lusine.value { { panel = "the checks", quantity = checks.limits, label = "at rest, and in a plane" } },
lusine.value { { panel = "the checks", quantity = checks.kept, label = "energy kept" } },
};
void main()
{
// Every flight the boule is thrown again from the hand.
boule.time = boule.time >= settings.flight ? 0.0 s : boule.time;
boule.position = boule.time == 0.0 s ? hand() : boule.position;
boule.velocity = boule.time == 0.0 s ? vector<float<m/s>, 3>(settings.across, settings.up, settings.along)
: boule.velocity;
energy.start = boule.time == 0.0 s ? lusine.properties.kinetic_energy(settings.mass, boule.velocity) : energy.start;
(boule.position, boule.velocity) = lusine.integrator.kick_drift(boule.position, boule.velocity,
acceleration = down(settings.gravity), settings.dt);
boule.velocity = lusine.integrator.kick(boule.velocity, acceleration = down(settings.gravity), settings.dt);
boule.time = boule.time + settings.dt;
energy.kinetic = lusine.properties.kinetic_energy(settings.mass, boule.velocity);
energy.potential = settings.mass * settings.gravity * boule.position.y;
energy.total = energy.kinetic + energy.potential;
energy.turn = energy.turn >= 4.0 ? -4.0 : energy.turn + 0.05;
energy.turning = lusine.properties.kinetic_energy(mass = settings.turning_mass,
velocity = heading(speed = settings.turning_speed,
turn = energy.turn));
checks.textbook = checks.textbook && textbook();
energy.worst = abs(energy.turning - 20.0 J) > energy.worst ? abs(energy.turning - 20.0 J) : energy.worst;
checks.direction = checks.direction && abs(energy.turning - 20.0 J) <= 0.000001 * 20.0 J;
checks.scales = checks.scales && scales(settings.mass, boule.velocity);
checks.limits = checks.limits && limits(boule.velocity);
checks.kept = checks.kept && abs(energy.total - energy.start) <= 0.00001 * energy.start;
lamps.colour = lamp_colour(holds = checks.textbook && checks.direction && checks.scales && checks.limits
&& checks.kept);
}
The law's other proof (the textbook's values and the invariants over velocities at random) is on its proof page.