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.
Same body, same controller structure, same sixty seconds. One of the two riders can stand up.
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 | No → no, 87° | Uncertain → no, ceiling near 160° |
| standing | Not recommended → no, 147° | Yes → yes, 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.
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
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.
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
Linearising for small angles, with
Setting
The derivation, the seated and standing analyses, the chain treatment and the sources are in docs/PHYSICS.md.
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
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-rotationEvery configuration on one clock:
python scripts/compare_models.py --save media/comparison.gifAs 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
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 | 1× |
Gymnasium SwingEnv (JAX) |
14,544 | 4× |
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.
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.
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, andvmapreturns 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.
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
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.
- The physics of a playground swing, zmatt.net. The post this project started from, and the source of the summary table above.
- Case and Swanson (1990), The pumping of a swing from the seated position, Am. J. Phys. 58(5), 463.
- Case (1996), The pumping of a swing from the standing position, Am. J. Phys. 64(3), 215.
- 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.
- 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.
- 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.


