Double Pendulum — how it works
Two rods, two bobs, one pivot, and the most famous demonstration of deterministic chaos.
The state is four numbers

θ₁ and θ₂ are the angles each rod makes with straight down; ω₁ and ω₂ are how fast those angles are changing. Everything else — where the bobs are, how much energy the system has, what it will do next — follows from those four and the fixed hardware (rod lengths L₁, L₂, masses m₁, m₂, gravity g).
The forces, as the notebook drew them

Gravity pulls each bob straight down. Each rod's tension resolves that pull into the direction its bob is actually free to move. The difficulty is the coupling: the lower rod pulls back on the upper bob, so neither rod can be solved on its own — write down one and the other appears in it.
Doing that exactly gives two coupled second-order equations for ω̇₁ and ω̇₂ that share one denominator:
den = 2m₁ + m₂ − m₂·cos(2θ₁ − 2θ₂)
No small-angle approximation. sin θ ≈ θ would make the system linear, solvable in closed form,
and completely wrong: it is precisely the nonlinearity that produces the chaos this plugin exists to
show. The exact form is carried over from the notebook verbatim (PendulumMath.Derivatives).
Integration: RK4, and why the drift number is there
There is no closed-form solution, so the state is stepped forward numerically with classical Runge–Kutta 4. Every run reports its energy drift, because that is the one honest check on whether the picture can be believed at all: total mechanical energy is exactly constant in the real system, so any drift is the integrator's error and nothing else.
🚨 Drift is measured against
(m₁+m₂)·g·(L₁+L₂), never against the run's own initial energy. Total energy is defined only up to a constant, and for the most natural release of all — both rods horizontal, at rest — it is exactly zero. Dividing by it turns a flawless integrator into an infinite error. The first draft of these tests did precisely that and reported a drift of 2.5×10⁵ for a run whose true drift was 1.1×10⁻¹¹.
Motion without scripts
Controls.Html does not execute injected script, so the animation is data: the trajectory is
integrated once, sampled to ~240 frames, and handed to the browser as SVG SMIL keyframes.
Deterministic, identical for every viewer, and nothing is mutated to show it — the honest answer to
the notebook's Task.Run + PeriodicTimer mutation of a live scope.
Languages
Every explanation on the Live and Play pages exists in English and German, in PendulumTexts —
one pure table per language. The page picks its language from ?lang=de first, then the viewer's
profile locale, then English; a 🌐 line above the animation switches. Release NAMES stay English: they are
also the ?release= URL parameter.
Numbers are always formatted invariant-culture, in both languages — a German page rendering 1,8
beside SVG coordinates that use 1.8 reads as two different documents, and a decimal comma inside a
markdown table cell is indistinguishable from a thousands separator. A test pins it.
One control
There used to be four routes — a gallery, a playground, a walkthrough and its chrome-free twin — and moving between them meant leaving the pendulum you were watching. There is now one screen:
┌──────────────────────────────────────────────┐
│ the pendulum running, trail growing │ ← behind the bob, not ahead of it
├──────────────────────────────────────────────┤
│ rods 1/1 masses 1/1 g 9.8 90°/90° 50s │
│ [ Run ] [ 🎲 Surprise ] │
├──────────────────────────────────────────────┤
│ ‹The physics› ‹Examples› ‹Try yourself› │
└──────────────────────────────────────────────┘
| Area | What it is |
|---|---|
| Live (default) | The whole control. ?release=Chaotic opens on a named release; ?size=900 enlarges; ?lang=de picks the language. |
| Play | The same control. The old playground route, kept alive so no link, bookmark or embed 404s. |
| Tests | The physics suite, executed on the mesh. |
Three things about it are deliberate:
- You land on a RANDOM release. No
?release=, and the page rolls one — a different pendulum every time you open it, already running, with nothing to press. A roll is not a free-for-all: the upper rod is released between 40° and 180°, because a pendulum let go a few degrees off the bottom just ticks back and forth and teaches nothing about chaos. - 🚨 The trail ACCUMULATES. It used to be painted in full before the bob set off, which gave the
ending away — you were shown the answer and then watched it be arrived at. It now draws itself
under the bob, via an animated
stroke-dashoffsetkeyed to cumulative arc length (PendulumMath.TrailReveal). The arc-length keying is the load-bearing part: a pendulum crosses the bottom of its swing several times faster than the top, so a constant-rate reveal would run ahead of the bob through the fast parts and lag through the slow ones. Trail and bob are sampled from the same frames over the same duration, so they are in lockstep by construction rather than by tuning — and a test pins it. - It re-runs on the BUTTON, not the keystroke. That is what makes the setup explorable: you can set three numbers wrong on the way to setting the fourth right without the page re-integrating 20 000 RK4 steps for every half-typed value. It is also what pays for the finer step: the settings integrate at 200 slices per simulated second, sixteen times more accurate than the node default, because energy drift is the one number this page asks the viewer to trust.
The pendulum carries no name of its own: PendulumContent.NameOf recognises it by its setup, so
a release loaded from the picker is called Chaotic and gets Chaotic's note, and the moment a number
is nudged off it, it quietly stops being Chaotic.
Reading the phase portrait
θ₁ against ω₁, one dot per step, dark to bright over time. This is the view that turns "it looks messy" into something checkable:
- regular motion closes into loops — the state returns to where it was, so the curve retraces itself;
- chaotic motion fills a band and never retraces.
Run Gentle swing and then Chaotic and compare the two portraits. That difference is the whole subject.
The five releases
| Release | What it shows |
|---|---|
| Gentle swing | Under ~20° the system is not chaotic: two coupled harmonic oscillators, energy sloshing on a regular beat. The control case. |
| Chaotic | Both rods horizontal, from rest — the canonical demonstration. A billionth of a radian rewrites the trace within seconds. |
| Over the top | Released almost inverted; the lower bob loops the loop, never the same way twice. The hardest case for the integrator, so watch the drift here. |
| Heavy lower bob | m₂ = 4·m₁ — a heavy tail dominates the coupling and drags the upper rod instead of following it. |
| Long lower rod | L₂ = 1.8·L₁ — reach and period both grow with √L, so slower motion over a much larger area. |
Authoring a node
Content is the setup plus the release point plus the integration settings:
{ "nodeType": "DoublePendulum/Pendulum",
"content": { "$type": "PendulumContent", "preset": "Chaotic" } }
Naming a preset fills in the rest; explicit numbers are the other way to say it. Either way the node
pins that release and the page opens on it — and a node that authors nothing rolls one instead,
which is what DoublePendulum/Live does. A pinned node is rendered from its OWN content rather than
through the settings model, so an authored omega1/omega2 survives: the form offers where you let
go, not how hard you throw, and routing a spun release through it would silently zero the spin.
There is no body field any more: the note under a release is the Examples tab's, keyed off the
preset, so a per-node one would describe whichever release the page happened to roll. PendulumContent.Validated()
guards lengths, masses and the step so an admissible setup can never divide by zero, and the
playground clamps every typed field on top of that (PendulumSettings.ToContent) — the generated
number field does not enforce [Range], so a pasted NaN arrives exactly as typed.
Original notebook: Double Pendulum, Systemorph Cloud. The force and angle diagrams are the originals. Rebuilt for MeshWeaver in 2026.