Double pendulum · laguna-s-2.1
Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.
transcript · 15 messages
Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.
thinking
The user wants a chaotic double pendulum simulation on an HTML canvas. Let me write a self-contained index.html file that:
1. Uses correct equations of motion for a double pendulum
2. Draws the arms
3. Draws a colorful fading trail of the tip
4. All inline - CSS in style tag, JS in script tag
5. No external resources
Let me write the correct physics. The double pendulum equations of motion are:
For a double pendulum with two point masses m1 and m2 at the ends of two massless rods of length L1 and L2, with angles θ1 and θ2 from vertical:
The Lagrangian approach gives:
θ1'' = (-g(2m1+m2)sin(θ1) - m2 g sin(θ1-2θ2) - 2 sin(θ1-θ2) m2 (θ2'^2 L2 + θ1'^2 L1 cos(θ1-θ2))) / (L1(2m1+m2-m2 cos(2θ1-2θ2)))
θ2'' = (2 sin(θ1-θ2)(θ1'^2 L1(m1+m2) + g(m1+m2)cos(θ1) + θ2'^2 L2 m2 cos(θ1-θ2))) / (L2(2m1+m2-m2 cos(2θ1-2θ2)))
Let me use the standard form with the denominator:
D = (2m1 + m2 - m2 cos(2θ1 - 2θ2))
Actually, let me be careful. The standard equations:
For the first pendulum:
θ1'' = [ -g(2m1 + m2) sin(θ1) - m2·g·sin(θ1 - 2θ2) - 2·sin(θ1 - θ2)·m2·(ω2²·L2 + ω1²·L1·cos(θ1 - θ2)) ] / [ L1·(2m1 + m2 - m2·cos(2θ1 - 2θ2)) ]
For the second pendulum:
θ2'' = [ 2·sin(θ1 - θ2)·(ω1²·L1·(m1 + m2) + g·(m1 + m2)·cos(θ1) + ω2²·L2·m2·cos(θ1 - θ2)) ] / [ L2·(2m1 + m2 - m2·cos(2θ1 - 2θ2)) ]
where D = 2m1 + m2 - m2·cos(2θ1 - 2θ2)
This is the classic correct form. Let me use numerical integration (RK4 for accuracy).
Let me write the code:
- RK4 integration of the state [θ1, ω1, θ2, ω2]
- Draw arms as lines
- Keep a trail of tip positions as an array of {x, y, color} with a max length, fading older ones
- Colorful trail using hue based on age
Let me write this all out.
I'll make the trail use a fading alpha based on position in the array, and cycle colors through HSL.
Let me write the full file./tmp/ws/index.html (7846 bytes)Successfully wrote 7846 bytes to /tmp/ws/index.html
thinking
Let me verify the file is there and check the physics once more. Actually, I notice my RK4 has some leftover buggy lines (`nh1` calculation and a confusing comment) that are computed but unused. Let me clean that up to avoid confusion, even though `nh1` is unused. The actual integration uses `nth1, nw1, nth2, nw2` which is correct.
Wait, let me double-check the RK4. The issue is that in RK4 for a second-order ODE, I need to compute k's properly. The standard RK4 for the system:
The state evolves as:
- dθ/dt = ω
- dω/dt = f(θ, ω) (the angular acceleration, which depends on θ1, ω1, θ2, ω2)
So the derivatives function returns dω/dt given θ1, ω1, θ2, ω2.
For RK4:
- k1 for θ = ω (the current value)
- k1 for ω = f(θ1, ω1, θ2, ω2)
- k2 for θ = ω + 0.5*dt*k1_ω
- k2 for ω = f(θ1 + 0.5*dt*ω, ω1 + 0.5*dt*k1_ω, ...)
Hmm, this is getting complicated because the acceleration depends on all thetas and omegas. Let me think more carefully.
Actually, the standard approach treats it as a system of 4 first-order ODEs:
- dθ1/dt = ω1
- dω1/dt = α1(θ1, ω1, θ2, ω2)
- dθ2/dt = ω2
- dω2/dt = α2(θ1, ω1, θ2, ω2)
RK4 for this system uses intermediate evaluations where ALL four state variables are advanced by half steps.
Let me rewrite the derivative evaluation as a vector function and apply standard RK4.
The way I wrote it with `derivatives(th1, w1, th2, w2)` returning `{dw1, dw2}`, the RK4 should be:
k1:
- dth1 = w1, dw1 = a.dw1
- dth2 = w2, dw2 = a.dw2
k2 (evaluate at th1 + dt/2*dth1, w1 + dt/2*dw1, th2 + dt/2*dth2, w2 + dt/2*dw2):
- b = derivatives(th1 + dt/2*w1, w1 + dt/2*k1w1, th2 + dt/2*w2, w2 + dt/2*k1w2)
- dth1 = w1 + dt/2*k1w1, dw1 = b.dw1
- dth2 = w2 + dt/2*k1w2, dw2 = b.dw2
I had several bugs:
1. In computing `b`, I used `th1 + 0.5 * dt * w1` for theta1 — that's correct (dth1 = w1). And `w1 + 0.5*dt*k1w1` for omega1 — correct since dw1 = k1w1 = a.dw1.
2. But then for the final combination, I used `k1w1, k2w1, k3w1, k4w1` as the acceleration k-values. But I need the theta k-values too, which are the omega values at intermediate points.
Actually, the cleanest RK4:
State vector Y = [θ1, ω1, θ2, ω2]
dY/dt = F(Y) where F = [ω1, α1, ω2, α2]
k1 = F(Y)
k2 = F(Y + dt/2 * k1)
k3 = F(Y + dt/2 * k2)
k4 = F(Y + dt * k3)
Y_new = Y + dt/6 * (k1 + 2k2 + 2k3 + k4)
This is the cleanest way. Let me rewrite using this vector form. My current code is messy and buggy. Let me rewrite it properly to ensure the equations are correctly integrated, since the user emphasized "correct equations of motion."
Let me rewrite the whole thing cleanly. The `derivatives` function should return all four components [dθ1, dω1, dθ2, dω2].
Let me rewrite the file.Let me clean up the RK4 integration — the current version has leftover buggy/unused lines and the half-step combinations weren't computed correctly for a 4-variable system. I'll rewrite it as a clean vector RK4.
{"path":"/tmp/ws/index.html","offset":35,"limit":60}const L2 = 120; // length of second rod const m1 = 2.0; // mass of first bob const m2 = 1.0; // mass of second bob const drag = 0.9995; // tiny air-resistance-ish damping to keep it pretty // State: [theta1, omega1, theta2, omega2] // theta measured from the downward vertical (hanging) position. let theta1 = Math.PI / 2 + 0.3; // start near horizontal let omega1 = 0.0; let theta2 = Math.PI / 2 - 0.3; let omega2 = 0.0; // Equations of motion for the double pendulum (point masses, massless rods). // Derived from the Euler-Lagrange equations of the two-angle Lagrangian. function derivatives(th1, w1, th2, w2) { const delta = th1 - th2; const sinDelta = Math.sin(delta); const cosDelta = Math.cos(delta); const denom = (2 * m1 + m2 - m2 * Math.cos(2 * th1 - 2 * th2)); const dw1 = (-g * (2 * m1 + m2) * Math.sin(th1) - m2 * g * Math.sin(th1 - 2 * th2) - 2 * sinDelta * m2 * (w2 * w2 * L2 + w1 * w1 * L1 * cosDelta)) / (L1 * denom); const dw2 = (2 * sinDelta * ( w1 * w1 * L1 * (m1 + m2) + g * (m1 + m2) * Math.cos(th1) + w2 * w2 * L2 * m2 * cosDelta)) / (L2 * denom); return { dw1, dw2 }; } // Classic 4th-order Runge-Kutta integrator for the angular state. function rk4(th1, w1, th2, w2, dt) { const a = derivatives(th1, w1, th2, w2); const k1w1 = a.dw1, k1w2 = a.dw2; const b = derivatives(th1 + 0.5 * dt * w1, w1 + 0.5 * dt * k1w1, th2 + 0.5 * dt * w2, w2 + 0.5 * dt * k1w2); const k2w1 = b.dw1, k2w2 = b.dw2; const c = derivatives(th1 + 0.5 * dt * w2, w1 + 0.5 * dt * k2w1, th2 + 0.5 * dt * w2, w2 + 0.5 * dt * k2w2); const k3w1 = c.dw1, k3w2 = c.dw2; const d = derivatives(th1 + dt * w2, w1 + dt * k3w1, th2 + dt * w2, w2 + dt * k3w2); const k4w1 = d.dw1, k4w2 = d.dw2; // Use the midpoint (Heun/Ralston-style) weighted average for angular positions // and RK4 weighting for the angular accelerations. const nh1 = th1 + (dt / 6) * (w1 + 2 * (w1 + 0.5 * dt * k1w1) + 2 * (w1 + 0.5 * dt * k2w1) + (w1 + dt * k3w1)); // simpler correct RK4 below: const nth1 = th1 + (dt / 6) * (w1 + 2 * k1w1 + 2 * k2w1 + k3w1); const nw1 = w1 + (dt / 6) * (k1w1 + 2 * k2w1 + 2 * k3w1 + k4w1); const nth2 = th2 + (dt / 6) * (w2 + 2 * k1w2 + 2 * k2w2 + k3w2); [134 more lines in file. Use offset=95 to continue.]
thinking
Now let me rewrite the integration section cleanly. I'll replace the derivatives and rk4 functions with a proper vector RK4. The correct RK4 for the system Y = [θ1, ω1, θ2, ω2], dY/dt = F(Y) = [ω1, α1, ω2, α2]: k1 = F(Y) k2 = F(Y + dt/2 * k1) k3 = F(Y + dt/2 * k2) k4 = F(Y + dt * k3) Y_new = Y + dt/6 * (k1 + 2*k2 + 2*k3 + k4) Let me replace from the derivatives function through the rk4 return.
{"path":"/tmp/ws/index.html","edits":"[{\"oldText\":\" // Equations of motion for the double pendulum (point masses, massless rods).\n // Validation failed for tool "edit": - edits.0: must be object Received arguments: { "path": "/tmp/ws/index.html", "edits": "[{\"oldText\":\" // Equations of motion for the double pendulum (point masses, massless rods).\n // Derived from the Euler-Lagrange equations of the two-angle Lagrangian.\n function derivatives(th1, w1, th2, w2) {\n const delta = th1 - th2;\n const sinDelta = Math.sin(delta);\n const cosDelta = Math.cos(delta);\n const denom = (2 * m1 + m2 - m2 * Math.cos(2 * th1 - 2 * th2));\n\n const dw1 =\n (-g * (2 * m1 + m2) * Math.sin(th1)\n - m2 * g * Math.sin(th1 - 2 * th2)\n - 2 * sinDelta * m2 * (w2 * w2 * L2 + w1 * w1 * L1 * cosDelta))\n / (L1 * denom);\n\n const dw2 =\n (2 * sinDelta * (\n w1 * w1 * L1 * (m1 + m2)\n + g * (m1 + m2) * Math.cos(th1)\n + w2 * w2 * L2 * m2 * cosDelta))\n / (L2 * denom);\n\n return { dw1, dw2 };\n }\n\n // Classic 4th-order Runge-Kutta integrator for the angular state.\n function rk4(th1, w1, th2, w2, dt) {\n const a = derivatives(th1, w1, th2, w2);\n const k1w1 = a.dw1, k1w2 = a.dw2;\n\n const b = derivatives(th1 + 0.5 * dt * w1, w1 + 0.5 * dt * k1w1,\n th2 + 0.5 * dt * w2, w2 + 0.5 * dt * k1w2);\n const k2w1 = b.dw1, k2w2 = b.dw2;\n\n const c = derivatives(th1 + 0.5 * dt * w2, w1 + 0.5 * dt * k2w1,\n th2 + 0.5 * dt * w2, w2 + 0.5 * dt * k2w2);\n const k3w1 = c.dw1, k3w2 = c.dw2;\n\n const d = derivatives(th1 + dt * w2, w1 + dt * k3w1,\n th2 + dt * w2, w2 + dt * k3w2);\n const k4w1 = d.dw1, k4w2 = d.dw2;\n\n // Use the midpoint (Heun/Ralston-style) weighted average for angular positions\n // and RK4 weighting for the angular accelerations.\n const nh1 = th1 + (dt / 6) * (w1 + 2 * (w1 + 0.5 * dt * k1w1) + 2 * (w1 + 0.5 * dt * k2w1) + (w1 + dt * k3w1));\n // simpler correct RK4 below:\n const nth1 = th1 + (dt / 6) * (w1 + 2 * k1w1 + 2 * k2w1 + k3w1);\n const nw1 = w1 + (dt / 6) * (k1w1 + 2 * k2w1 + 2 * k3w1 + k4w1);\n const nth2 = th2 + (dt / 6) * (w2 + 2 * k1w2 + 2 * k2w2 + k3w2);\n const nw2 = w2 + (dt / 6) * (k1w2 + 2 * k2w2 + 2 * k3w2 + k4w2);\n\n return { nth1, nw1, nth2, nw2 };\n }" }
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Double pendulum with colorful fading trail animates correctly; simple but functional, no extra UI or interactivity visible.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.