A spinning ball's lift coefficient¶
A spinning ball's lift coefficient from its spin ratio S (lusine.laws.spin_ratio), the fit of Sawicki, Hubbard and Stronge to wind-tunnel and flight measurements of baseballs: C_L = 1.5 S below S = 0.1 and 0.09 + 0.6 S above, the two meeting at 0.15.
Measured to S of about 0.5; a ball of another surface (a smooth boule against a seamed baseball) is the same law within those measurements' scatter, and a game wanting its own passes its own C_L to lusine.laws.magnus. Source: Sawicki, Hubbard and Stronge, How to hit home runs, Am. J. Phys. 71 (2003) 1152, eq. 7. Checked against a reference in numpy, which has its own tests.
- Source
- Sawicki, Hubbard and Stronge, How to hit home runs, Am. J. Phys. 71 (2003) 1152, eq. 7.
- Proof
- reference, limit.
lift_coefficient.trait
namespace lusine.laws;
float lift_coefficient(float spin_ratio)
{
if (spin_ratio < 0.1) return 1.5 * spin_ratio;
return 0.09 + 0.6 * spin_ratio;
}
Sawicki, Hubbard and Stronge's fit (Am. J. Phys. 71, 2003, eq. 7) of a spinning ball's lift coefficient to its spin ratio: 1.5 S below S = 0.1, 0.09 + 0.6 S above, the arms meeting at 0.15.
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
F32 = np.float32
U = float(np.finfo(F32).eps) / 2 # binary32's unit roundoff, 2^-24
rng = np.random.default_rng(2026)
The kernel¶
The law as the library states it.
print(trait_run.source('laws/lift_coefficient.trait'))
// lusine.laws.lift_coefficient: a spinning ball's lift coefficient from its spin ratio S (lusine.laws.spin_ratio), the
// fit of Sawicki, Hubbard and Stronge to wind-tunnel and flight measurements of baseballs: C_L = 1.5 S below S = 0.1
// and 0.09 + 0.6 S above, the two meeting at 0.15. Measured to S of about 0.5; a ball of another surface (a smooth
// boule against a seamed baseball) is the same law within those measurements' scatter, and a game wanting its own
// passes its own C_L to lusine.laws.magnus.
// Source: Sawicki, Hubbard and Stronge, How to hit home runs, Am. J. Phys. 71 (2003) 1152, eq. 7.
// Checked against a reference in numpy, which has its own tests.
namespace lusine.laws;
float lift_coefficient(float spin_ratio)
{
if (spin_ratio < 0.1) return 1.5 * spin_ratio;
return 0.09 + 0.6 * spin_ratio;
}
The program¶
Each section below 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('laws/lift_coefficient.trait', 'lift_coefficient'))
namespace probe;
lusine.version version = { { number = 1 } };
struct item {
host float spin_ratio;
float result_0;
};
lattice<item, 1> items(spacing = 1 m, boundary = wall);
void main() { items.result_0 = lusine.laws.lift_coefficient(items.spin_ratio); }
from lusine.kernels.laws.lift_coefficient import lift_coefficient as reference
Reference: the fit over the measured range¶
The kernel's coefficient within binary32's roundings of the float64 reference's, S from 0 to 0.5.
s = F32(np.linspace(0.0, 0.5, 501)).astype(float)
got = trait_run.law('laws/lift_coefficient.trait', 'lift_coefficient', spin_ratio=s)
want = reference(s)
assert np.max(np.abs(got - want)) <= 4 * U
fig, ax = plt.subplots(figsize=(6, 3))
ax.plot(s, got)
ax.set_xlabel('spin ratio S'); ax.set_ylabel('C_L')
plt.show()
Limit: no spin, and the arms' meeting¶
No spin is no lift; at S = 0.1 the two arms give 0.15.
c = trait_run.law('laws/lift_coefficient.trait', 'lift_coefficient', spin_ratio=np.array([0.0, 0.1]))
assert c[0] == 0.0 and abs(c[1] - 0.15) <= 4 * U
print('0 at rest; 0.15 where the arms meet')
0 at rest; 0.15 where the arms meet