Skip to content

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

swing-rl

Can a playground swing be pumped all the way over the top bar?

A rider on a swing cannot push against anything external and cannot apply a torque at the pivot. The only available control is the position of its own centre of mass. This repository contains the physics for that problem and a set of Gymnasium environments built on it.

Standing, squatting and rising twice per swing: full 360° in 26 s

the standing rider going over the top

Seated, leaning and swinging the legs once per swing: 49° after a minute

the seated rider stalling

Same body, same controller structure, same sixty seconds. One of the two riders can stand up.


Whether a swing can loop

The zmatt.net post on swing physics gives a summary table for this question. An independent model reproduces it cell for cell:

technique flexible chains rigid rod
seated Nono, 87° Uncertainno, ceiling near 160°
standing Not recommendedno, 147° Yesyes, 360° in 26 s

Seated on a rigid rod is the cell that post leaves open. It resolves as no: 49° after one minute, 140° after three, approaching a ceiling in the low 160s, which is the figure that post quotes from its own simulation.

Where the energy goes

The rider's mechanical work is the rise in mechanical energy plus every joule dissipated by pivot friction, aerodynamic drag and chain snaps.

configuration full 360° rider's work average power peak
rigid rod, standing yes, 26 s 2890 J 97 W 177 W
rigid rod, seated no, 76° 1307 J 15 W 37 W
flexible chain, standing no, 147° 11128 J 124 W 196 W

A rigid-rod loop costs 97 W sustained with 177 W peaks, which is ordinary hard exercise for an adult.

The standing rider on chains performs 11.1 kJ, four times the work of a successful rigid-rod run, and stops at 147°. The chain removes energy at each snap faster than the rider supplies it. A rigid rod need only lift the centre of mass to the top, costing $2mgL$; a chain must also be moving fast enough there to stay taut, $v^2 \ge gL$, so a chain loop costs

$$ 2mgL + \tfrac{1}{2}mgL = 1.25 \times 2mgL, $$

25% more, independent of mass. That is the loop-the-loop condition, and it is what separates the two columns of the table above.

Seated pumping draws 15 W with its commands already saturated at their limits. Its ceiling is the authority of the mechanism, not the power of the rider. The technique a real playground swing comfortably supports is therefore the one that cannot reach the top.


The two pumping channels

Gravity supplies the only external torque, and the rider controls only the position of its own mass. Moving that mass in the right phase makes the timing of gravity's torque add energy on every cycle. Two channels do this.

Parametric. Moving the centre of mass along the suspension, by squatting and standing, modulates the pendulum's length and pumps at twice the swing frequency. It is the stronger channel, and from exact rest it does nothing: there is no oscillation to modulate.

Driven. Moving it across the suspension, by leaning and swinging the legs, pumps at once the swing frequency. It is weaker, and it works from a dead stop.

Both fall out of one Euler-Lagrange equation in the suspension angle $\theta$, with $\rho$ the centre-of-mass distance from the pivot, $\alpha$ its angular offset from the suspension, $\psi = \theta + \alpha$, and $J$ the rider's inertia about its own centre of mass:

$$ (m\rho^2 + J)\ddot\theta = \underbrace{-m\rho^2\ddot\alpha}_{\text{driving}} \underbrace{-2m\rho\dot\rho\dot\psi}_{\text{parametric}} -J\ddot\beta - \dot J(\dot\theta + \dot\beta) -mg\rho\sin\psi + \tau_{\text{damp}}. $$

Linearising for small angles, with $\rho = L + a$ and $J = 0$, separates the two channels and recovers the form used throughout the American Journal of Physics swing literature:

$$ \ddot\theta + \underbrace{\frac{2\dot a}{L}}_{\text{pumps at } 2\omega_0}\dot\theta + \frac{g}{L}\theta = \underbrace{-\left(\ddot\alpha + \frac{g}{L}\alpha\right)}_{\text{pumps at } \omega_0}. $$

Setting $\alpha \equiv 0$ and $J = 0$ collapses this to the textbook variable-length pendulum, $\ddot\theta = -(2\dot\rho/\rho)\dot\theta - (g/\rho)\sin\theta$.

The derivation, the seated and standing analyses, the chain treatment and the sources are in docs/PHYSICS.md.


Environments

Nine Gymnasium ids, three bodies by three suspensions.

rigid rod flexible chain chain with mass
lumped rider Swing-v0, SwingFullRotation-v0 SwingChain-v0 SwingMultilink-v0
articulated, seated SwingArticulatedSeated-v0 SwingChainSeated-v0 SwingMultilinkSeated-v0
articulated, standing SwingArticulatedStanding-v0 SwingChainStanding-v0

Bodies. A rider lumped into a single point mass, or an articulated body branching at the hips into torso, thighs and shins with the three-segment masses used in the literature. Seated and standing riders have different freedoms. Seated, the feet dangle, so the leg swing and the knee are commanded independently. Standing, the feet rest on the seat, which determines the leg angles from the hip height and leaves only squat depth and torso lean.

Suspensions. A rigid rod, which both pushes and pulls. A massless chain, which pulls only, with exact switching between a taut phase and a ballistic one and an inelastic snap when it comes tight. Or a chain carrying mass, built from $N$ rigid links, which sags under gravity and whips with its own inertia.

a chain with mass, sagging and whipping


Installation

git clone https://github.com/pierrelux/swing-rl && cd swing-rl
python -m venv .venv && .venv/bin/pip install -e ".[jax]"

The baseline pumper, animated:

python scripts/demo_heuristic.py --model articulated_standing --full-rotation

Every configuration on one clock:

python scripts/compare_models.py --save media/comparison.gif

As an ordinary Gymnasium environment:

import gymnasium as gym
import swing_rl.envs # registers the ids

env = gym.make("SwingArticulatedStanding-v0")
obs, info = env.reset(seed=0)
obs, reward, terminated, truncated, info = env.step(env.action_space.sample())

Reward. A terminal bonus for passing over the top, potential-based shaping on normalised mechanical energy, and small effort and time costs. Potential-based shaping leaves the optimal policy unchanged (Ng, Harada and Russell, 1999) and makes an otherwise very sparse goal learnable.

Success. The seat must pass the top bar and the suspension must be near full extension. On a rod the second condition costs nothing, since the radius is always $L$. On a chain it decides the question: a fully folded chain carries the seat past the bar at a radius of a few centimetres, which is the swing collapsing around its own pivot rather than looping over it.


Backends

Two backends read the same model description. swing_rl.physics (numpy) is the reference, validated against the hand-derived equation of motion. swing_rl.jaxsim (JAX) runs the rollouts, and a test asserts the two agree, so each new configuration is cross-validated without new test code.

Measured by scripts/benchmark.py on CPU. JAX on Apple Silicon has no usable GPU backend, so these figures come from XLA fusion and batching:

backend env-steps/s vs numpy
numpy reference solver 3,295
Gymnasium SwingEnv (JAX) 14,544
JAX scan, one env, whole episode 113,080 34×
JAX vmap+scan, 256 envs 1,204,166 365×

A single step is small enough that per-call dispatch dominates it, so the Gymnasium path reaches 4× however well it is written. Training should use Simulator.rollout_batch. Rollouts are differentiable, which the port supplied for free.


Layout

src/swing_rl/
 physics/ numpy reference: no Gymnasium, no matplotlib, no reward
 params.py every parameter, with units and sign conventions
 models.py ModelSpec: what a swing is, independent of who simulates it
 multibody.py generalised-coordinate solver
 pendulum.py closed-form 1-DOF equation of motion, for cross-checking
 jaxsim/ JAX runtime: same ModelSpec, jit / vmap / scan
 tree.py functional kinematics and equations of motion
 simulate.py stepping, rollouts, the rider's servo
 hybrid.py the flexible chain: taut and ballistic phases
 envs/ Gymnasium wrapper: observation, reward, bookkeeping
 viz/ matplotlib animation; reads the simulator, never writes to it
 control/ hand-written baseline pumpers
scripts/ demo_heuristic.py, compare_models.py, benchmark.py, train_cem.py
docs/PHYSICS.md the model, the sources, and what is verified

A new configuration is a new ModelSpec, not a change to a solver. The agreement tests are parameterised over the model registry, so it is cross-validated against numpy on the next test run.


Validation

python -m pytest runs 82 tests. Among them:

  • The generalised solver reproduces the hand-derived $\ddot\theta$ to $10^{-10}$ over 200 random states, and its energy and angular momentum to $10^{-12}$.
  • A chaotic double pendulum's energy error converges at fourth order in the step size. An error in the mass matrix produces a floor that refuses to converge.
  • With gravity off, arbitrary internal motion leaves the momentum conjugate to $\theta$ unchanged to $10^{-9}$: the rider cannot torque the pivot.
  • The JAX runtime matches numpy to $10^{-11}$ relative across every registered model, and vmap returns the same trajectories as sequential rollout.
  • A chain that never slackens reproduces the rod to machine precision.
  • The chain snap is an $M$-orthogonal projection, so it only removes energy, and the chain never stretches by more than $4 \times 10^{-16}$ m.
  • Damping is calibrated rather than fitted: coasting from 40°, the amplitude halves in about 19 cycles.

A chain driven into buckling is chaotic, and no step size makes it reproducible, because the divergence is Lyapunov rather than truncation. The test asserts the chaos, and fails if that regime ever becomes reproducible.


Learning

The controllers here are hand-written baselines, tuned by sweeping the model for whatever pumps hardest. They are the thing to beat.

A linear policy cannot pump parametrically. Squatting and standing has to happen at $2\omega_0$, and no linear function of the observation carries content at that frequency, so a linear policy can learn to lean and nothing else.

Starting from exact rest is an exploration problem rather than a control problem. At rest the parametric channel is inert and a random policy gains no measurable energy, leaving the shaping signal flat. scripts/train_cem.py takes a --start-amplitude curriculum that begins each episode mid-swing; the final policy is still evaluated from rest.


Sources

  1. The physics of a playground swing, zmatt.net. The post this project started from, and the source of the summary table above.
  2. Case and Swanson (1990), The pumping of a swing from the seated position, Am. J. Phys. 58(5), 463.
  3. Case (1996), The pumping of a swing from the standing position, Am. J. Phys. 64(3), 215.
  4. Wirkus, Rand and Ruina (1998), How to Pump a Swing, College Math. J. 29(4), 266. The variable-length pendulum and the bang-bang strategy.
  5. Post, de Groot, Daffertshofer and Beek (2007), Pumping a Playground Swing, Motor Control 11(2), 136. Motion capture of eighteen participants, finding driven oscillation dominant and parametric pumping subordinate.
  6. Hirata, Kitahara, Yamamoto, Gohara and Richardson (2023), Initial phase and frequency modulations of pumping a playground swing, Phys. Rev. E 107, 044203.

Full citations and what each contributes: docs/PHYSICS.md.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages