Kinetic energy: half the mass times the speed squared¶
A body's kinetic energy of translation, E = 1/2 m |v|^2, |v|^2 written as the sum of the velocity's components' squares, x, y, z in that order.
The 1/2 is the law's own number. A body moving in a plane gives the third component as zero, which adds nothing. Source: Taylor, Classical Mechanics, 4.1. Checked against a reference in numpy, which has its own tests.
- Source
- Taylor, Classical Mechanics, 4.1.
- Proof
- reference, invariant, limit.
kinetic_energy.trait
namespace lusine.properties;
float<J> kinetic_energy(float<kg> mass, vector<float<m/s>, 3> velocity)
{
return 0.5 * mass * (velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
}
A body's kinetic energy of translation, E = ½ m |v|², |v|² the sum of the velocity's components' squares; the ½ is the law's own number.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
import matplotlib.pyplot as plt
from lusine import trait_run
EPS = float(np.finfo(np.float32).eps) # Trait's float is binary32
The kernel¶
The law as the library states it.
print(trait_run.source('properties/kinetic_energy.trait'))
// lusine.properties.kinetic_energy: a body's kinetic energy of translation, E = 1/2 m |v|^2, |v|^2 written as the sum
// of the velocity's components' squares, x, y, z in that order. The 1/2 is the law's own number. A body moving in a
// plane gives the third component as zero, which adds nothing.
// Source: Taylor, Classical Mechanics, 4.1.
// Checked against a reference in numpy, which has its own tests.
namespace lusine.properties;
float<J> kinetic_energy(float<kg> mass, vector<float<m/s>, 3> velocity)
{
return 0.5 * mass * (velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
}
The program¶
Each section runs the kernel over a lattice of items, one a case: the host sets every parameter, and main() binds the kernel over the items.
print(trait_run.law_program('properties/kinetic_energy.trait', 'kinetic_energy'))
namespace probe;
lusine.version version = { { number = 1 } };
struct item {
host float<kg> mass;
host vector<float<m/s>, 3> velocity;
float<J> result_0;
};
lattice<item, 1> items(spacing = 1 m, boundary = wall);
void main() { items.result_0 = lusine.properties.kinetic_energy(items.mass, items.velocity); }
Reference: the textbook's values¶
1 kg at 2 m/s carries 2 J; a 1500 kg car at 20 m/s carries 300 kJ, both exact in binary32; a 0.7 kg boule rolling at 3 m/s along z carries 3.15 J, to binary32's rounding.
e = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=[1.0, 1500.0, 0.7],
velocity=[[2.0, 0.0, 0.0], [0.0, 20.0, 0.0], [0.0, 0.0, 3.0]])
assert e[0] == 2.0 and e[1] == 300000.0, e
assert abs(e[2] - 3.15) <= 4 * EPS * 3.15, e[2]
print(e)
[2.00000000e+00 3.00000000e+05 3.14999986e+00]
Invariant: the speed's, whatever the direction¶
2.5 kg at 4 m/s carries 20 J in each of 64 directions, to binary32's rounding of the components; twice the velocity carries four times the energy, bit for bit (a power of two scales without rounding), and three times the mass three times, to a rounding.
rng = np.random.default_rng(298)
d = rng.normal(size=(64, 3))
d /= np.linalg.norm(d, axis=1)[:, None]
e = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=2.5, velocity=4.0 * d)
assert np.all(np.abs(e - 20.0) <= 8 * EPS * 20.0), np.abs(e - 20.0).max()
v = np.float32(rng.normal(size=(64, 3))).astype(float)
m = np.float32(rng.uniform(0.1, 10.0, 64)).astype(float)
e1 = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=m, velocity=v)
e2 = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=m, velocity=2.0 * v)
e3 = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=3.0 * m, velocity=v)
assert np.array_equal(e2, 4.0 * e1)
assert np.all(np.abs(e3 - 3.0 * e1) <= 4 * EPS * 3.0 * e1)
speed = np.linspace(0.0, 5.0, 51)
curve = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=2.5,
velocity=np.stack([speed, 0 * speed, 0 * speed], axis=1))
fig, ax = plt.subplots(figsize=(7, 3.2))
ax.plot(speed, curve, label='2.5 kg')
ax.set_xlabel('speed (m/s)')
ax.set_ylabel('E (J)')
ax.legend()
plt.show()
Limit: at rest, or in a plane¶
A body at rest, or of no mass, carries nothing. A body moving in a plane gives its third component as zero, which adds nothing: moving in x and z, as a game on the ground does, it carries the same bits as in x and y.
e = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=[5.0, 0.0],
velocity=[[0.0, 0.0, 0.0], [1.0, 2.0, 3.0]])
assert np.all(e == 0.0), e
vx, vz = np.float32(rng.normal(size=(2, 32))).astype(float)
xz = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=0.7,
velocity=np.stack([vx, 0 * vx, vz], axis=1))
xy = trait_run.law('properties/kinetic_energy.trait', 'kinetic_energy', mass=0.7,
velocity=np.stack([vx, vz, 0 * vx], axis=1))
assert np.array_equal(xz, xy)
print('at rest and massless:', e, ' in a plane: the same bits either way')
at rest and massless: [0. 0.] in a plane: the same bits either way