Skip to content

The spin ratio, live

How fast a ball spins against how fast it moves through the air, S = r |ω| / |u|: its surface's speed about its centre over its speed through the air, u = v − w the ball's velocity less the wind's. It is the quantity a spinning sphere's lift and drag coefficients are given in. Below, a boule (3.7 cm in radius) moving east at 7 m/s has its spin about the vertical swept from none to 200 rad/s, 2 rad/s a step, twenty steps a second, and at each the library's ratio (lusine.laws.spin_ratio) is taken on your GPU as you read, for the ball as it is and for the same ball at twice the speed, spinning about another axis, at twice the spin, carried along by a wind, and moving with the wind.

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 ball: the chart draws the spin ratio against the spin, a point a step over one whole sweep, at 7 m/s and at 14 m/s: two lines through nothing, the faster ball's half as steep. Below it, the spin, the ratio at both speeds, carried by a wind, and moving with the wind.
  • The checks, each held at every step and each exact, bit for bit: scales, twice the spin giving twice the ratio and twice the speed half of it; any axis, spinning about the axis east giving what spinning about the vertical does; the air's frame, the ball carried by a wind of (2, −1.5, 0.5) m/s on top of its own motion giving the ratio it has in still air; with the wind, none, a ball moving with the wind, with no speed through the air to set its spin against, giving a ratio of nothing.

Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The ball's radius and speed, the wind 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 ratio is taken for the ball and its variants, and the checks are taken.

// The spin ratio's live page: a boule moving through the air at 7 m/s, its spin swept from none to 200 rad/s, the
// library's spin ratio at each, beside the same ball at twice the speed, spinning about another axis, carried by a
// wind, and moving with it, with the page's checks computed here on the GPU and shown by the panels the program
// declares.

namespace spin_ratio;

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

// The reader's settings: the ball's radius (a boule's), its speed east, a wind, and the sweep's step.
host struct {
  float<m> radius = 0.037 m; float<m/s> speed = 7.0 m/s; float<m/s> wind_east = 2.0 m/s;
  float<m/s> wind_north = -1.5 m/s; float<m/s> wind_up = 0.5 m/s; float<rad/s> stride = 2.0 rad/s;
} settings;

// The sweep: the step it is at, from 0 to 100 and round again, and the ball's spin rate there.
struct { integer step = -1; float<rad/s> spin; } sweep;

// The spin ratio of the ball spinning about the vertical at the sweep's rate, moving east through still air; at twice
// the speed; spinning about the axis east instead; at twice the spin; moving east through the air while a wind carries
// it; and moving with the wind.
struct { float ratio; float faster; float other_axis; float twice_the_spin; float carried; float with_the_wind; } law;

// The checks: twice the spin exactly twice the ratio and twice the speed exactly half of it; the axis of the spin
// making no difference; only the motion through the air counting, the ratio the same carried by a wind as in still air;
// and moving with the wind, no motion through the air, a ratio of nothing.
struct { bool scales = true; bool any_axis = true; bool air_frame = true; bool with_the_wind = 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> east(float<m/s> speed) { return vector<float<m/s>, 3>(speed, 0.0 m/s, 0.0 m/s); }
vector<float<rad/s>, 3> about_up(float<rad/s> spin) { return vector<float<rad/s>, 3>(0.0 rad/s, 0.0 rad/s, spin); }
vector<float<rad/s>, 3> about_east(float<rad/s> spin) { return vector<float<rad/s>, 3>(spin, 0.0 rad/s, 0.0 rad/s); }
vector<float<m/s>, 3> wind(float<m/s> wind_east, float<m/s> wind_north, float<m/s> wind_up)
{
  return vector<float<m/s>, 3>(wind_east, wind_north, wind_up);
}

output table<lusine.widget> panels = {
  lusine.series { { panel = "the ball", chart = "spin ratio", quantity = law.ratio, steps = 101, against = sweep.spin,
                    label = "at 7 m/s" } },
  lusine.series { { panel = "the ball", chart = "spin ratio", quantity = law.faster, steps = 101, against = sweep.spin,
                    label = "at 14 m/s" } },
  lusine.value { { panel = "the ball", quantity = sweep.spin, label = "spin" } },
  lusine.value { { panel = "the ball", quantity = law.ratio, label = "spin ratio" } },
  lusine.value { { panel = "the ball", quantity = law.faster, label = "at twice the speed" } },
  lusine.value { { panel = "the ball", quantity = law.carried, label = "carried by a wind" } },
  lusine.value { { panel = "the ball", quantity = law.with_the_wind, label = "moving with the wind" } },
  lusine.value { { panel = "the checks", quantity = checks.scales, label = "scales" } },
  lusine.value { { panel = "the checks", quantity = checks.any_axis, label = "any axis" } },
  lusine.value { { panel = "the checks", quantity = checks.air_frame, label = "the air's frame" } },
  lusine.value { { panel = "the checks", quantity = checks.with_the_wind, label = "with the wind, none" } },
};

void main()
{
  sweep.step = sweep.step >= 100 ? 0 : sweep.step + 1;
  sweep.spin = float(sweep.step) * settings.stride;
  law.ratio = lusine.laws.spin_ratio(velocity = east(settings.speed), wind = east(speed = 0.0 m/s),
                                     angular_velocity = about_up(sweep.spin), settings.radius);
  law.faster = lusine.laws.spin_ratio(velocity = east(speed = 2.0 * settings.speed), wind = east(speed = 0.0 m/s),
                                      angular_velocity = about_up(sweep.spin), settings.radius);
  law.other_axis = lusine.laws.spin_ratio(velocity = east(settings.speed), wind = east(speed = 0.0 m/s),
                                          angular_velocity = about_east(sweep.spin), settings.radius);
  law.twice_the_spin = lusine.laws.spin_ratio(velocity = east(settings.speed), wind = east(speed = 0.0 m/s),
                                              angular_velocity = about_up(spin = 2.0 * sweep.spin), settings.radius);
  law.carried = lusine.laws.spin_ratio(velocity = east(settings.speed) + wind(settings.wind_east, settings.wind_north,
                                                                              settings.wind_up),
                                       wind = wind(settings.wind_east, settings.wind_north, settings.wind_up),
                                       angular_velocity = about_up(sweep.spin), settings.radius);
  law.with_the_wind = lusine.laws.spin_ratio(velocity = wind(settings.wind_east, settings.wind_north, settings.wind_up),
                                             wind = wind(settings.wind_east, settings.wind_north, settings.wind_up),
                                             angular_velocity = about_up(sweep.spin), settings.radius);
  checks.scales = checks.scales && law.twice_the_spin == 2.0 * law.ratio && law.faster == 0.5 * law.ratio;
  checks.any_axis = checks.any_axis && law.other_axis == law.ratio;
  checks.air_frame = checks.air_frame && law.carried == law.ratio;
  checks.with_the_wind = checks.with_the_wind && law.with_the_wind == 0.0;
  lamps.colour = lamp_colour(holds = checks.scales && checks.any_axis && checks.air_frame && checks.with_the_wind);
}

The law's other proof (the ratio against a reference at random) is on its proof page.