Note
Go to the end to download the full example code.
The Rattleback: one-way spin reversal#
A rattleback (Celt stone) spins smoothly forever in one sense, but spun
the other way it soon wobbles, stalls, and reverses into the stable
sense of spin.
Rattleback is a
deliberately simplified toy model built directly on
EulerTop’s
free-rigid-body equations, for a reduced state
\((n_1, n_2, n_3)\) (\(n_3\) playing the role of spin about the
roughly-vertical axis, \(n_1, n_2\) the two rocking/tipping-mode
rates):
a damped Euler top plus a single term (\(\gamma n_3^2\), added to the \(n_1\) equation only) that breaks the \(n_3 \to -n_3\) symmetry an ordinary (non-reversing) Euler top has, standing in for the real rolling-contact-point physics, plus linear damping \(\mu\) and cubic self-saturation \(\eta\) so the instability it pumps up saturates and decays rather than diverging.
import matplotlib.pyplot as plt
import numpy as np
from physicskit.classical.systems.rotations import Rattleback
from physicskit.classical.visualizers.animations import animate_rattleback
A hard spin started predominantly about n3#
With gamma > 0, spin started mostly in n3 (with only a tiny rocking seed in n1, n2) collapses, overshoots into reversed spin, and decays.
system = Rattleback(n0=[0.01, 0.01, 3.0], I1=1.0, I2=1.5, I3=2.0, gamma=1.0, mu=0.01, eta=0.01)
result = system.integrate((0.0, 400.0), dt=0.1, method="rk4")
Animation: spin direction and n3(t) side by side#
The reversal is directly visible two ways: the indicator arrow reversing its sense of rotation, and the n3(t) trace crossing zero.
anim = animate_rattleback(system, result, interval=30, stride=20)
plt.show()
To save the animation to a file instead of (or in addition to) displaying it interactively, use e.g.:
anim.save("rattleback_animation.gif", writer="pillow", fps=30)
With gamma = 0: no reversal#
Setting gamma to zero removes the symmetry-breaking term entirely, reducing the system to a damped Euler top that just spins down – no reversal, regardless of which way it is spun.
system_no_reversal = Rattleback(n0=[0.01, 0.01, 3.0], I1=1.0, I2=1.5, I3=2.0, gamma=0.0, mu=0.01, eta=0.01)
result_no_reversal = system_no_reversal.integrate((0.0, 400.0), dt=0.02, method="rk4")
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(result.t, result.y[:, 2], label="gamma = 1.0 (reverses)", color="steelblue")
ax.plot(result_no_reversal.t, result_no_reversal.y[:, 2], label="gamma = 0.0 (no reversal)", color="gray")
ax.axhline(0.0, color="k", ls="--", lw=0.7)
ax.set_xlabel("t")
ax.set_ylabel(r"$n_3$ (spin rate)")
ax.set_title("Rattleback: spin reversal requires the symmetry-breaking term")
ax.legend()
fig.tight_layout()
plt.show()
print(f"sign changes with gamma=1.0: {np.sum(np.diff(np.sign(result.y[:, 2])) != 0)}")
print(f"sign changes with gamma=0.0: {np.sum(np.diff(np.sign(result_no_reversal.y[:, 2])) != 0)}")

sign changes with gamma=1.0: 69
sign changes with gamma=0.0: 0
Total running time of the script: (0 minutes 16.023 seconds)