From simple to chaotic: various pendulum dynamics
My interactive pendulum platform (which I described in a previous post here) supports a variety of systems, each with unique characteristics and underlying physics. This post focuses on the specific JavaScript implementation details for each pendulum, from translating their equations of motion into code to creating their dynamic user interfaces. All pendulum classes extend a common Simulation base class, ensuring a consistent structure.
Simple pendulum
The simple pendulum’s motion is governed by:
\frac{d^2\theta}{dt^2} = -\frac{g}{L} \sin(\theta)
Coding Implementation: * State Variables: this.angle (\theta) and this.angularVelocity (d\theta/dt). * _derivatives(t, y, effective_g, length) method:
// y = [angle, angularVelocity]
const angle_val = y[0];
const angularAcceleration = (-effective_g / length) * Math.sin(angle_val);
return [y[1] /* angularVelocity */, angularAcceleration];
update(deltaTime): Calls therk4solver with the current state and_derivativesfunction.- Controls:
getControlsHTML()generates HTML for sliders (length, initial angle, initial velocity, gravity).bindSpecificControls()usesnoUiSliderto link these to the simulation’s properties (e.g.,this.length,this.initialAngle). Changes to initial conditions trigger a reset of the simulation state if paused/stopped.
Driven damped pendulum
This system adds damping and a periodic driving force:
\frac{d^2\theta}{dt^2} = -\frac{g}{L} \sin(\theta) - b \frac{d\theta}{dt} + F_d \cos(\omega_d t)
Coding Implementation: * State Variables: Includes this.angle, this.angularVelocity, and this.time (to track the driving force phase). * Parameters: Adds damping (b), driveAmplitude (F_d), driveFrequency ($\omega_d$). * _derivatives(t_current, y, ...params) method:
// y = [currentAngle, currentAngularVelocity]
// params = [effective_g, length, damping, driveAmplitude, driveFrequency]
const drivingForceTerm = driveAmplitude * Math.cos(driveFrequency * t_current);
const angularAcceleration =
(-effective_g / length) * Math.sin(currentAngle) -
damping * currentAngularVelocity +
drivingForceTerm;
return [currentAngularVelocity, angularAcceleration];
- Controls: Sliders for damping, drive amplitude, and drive frequency are added. The
updatemethod passes the currentthis.timetork4for the derivative calculation. A trace of the bob’s path is often drawn.
Cycloidal pendulum
Its isochronous motion is described by \psi(t) = \psi_0 \cos\left(\sqrt{\frac{g}{L}} t\right), with bob position:
\begin{aligned} x_B &= O_x + r(\psi + \sin\psi) \\ y_B &= O_y + r(3 + \cos\psi) \end{aligned}
where L=4r.
Coding Implementation: * Direct Solution: Unlike others that use RK4 for \theta, the cycloidal pendulum directly calculates this.phi1 and this.phi2 (for two pendulums) using the analytical solution for \psi.
// Inside update() for CycloidalPendulum
const omega = Math.sqrt(g_scaled / this.L);
this.phi1 = this.amplitude1_rad * Math.cos(omega * this.timeElapsed);
// ... similar for phi2
- Drawing: The
_drawCycloidArcmethod is important for rendering the cycloidal cheeks and the path of the bob. - Controls: Sliders for length (L), mass (m, though not directly in these equations), gravity, and initial amplitudes for two separate pendulums.
Elastic (spring) pendulum
This system has coupled angular and radial motion:
\begin{aligned} \dot{\theta} &= \omega \\ \dot{\omega} &= \frac{-g \sin\theta - 2 \omega v_x}{r_0 + x} \\ \dot{x} &= v_x \\ \dot{v_x} &= (r_0 + x)\omega^2 + g \cos\theta - \frac{k}{m}x \end{aligned}
Coding Implementation: * State Variables: A 4-element vector: [angle (θ), angularVelocity (ω), springExtension (x), radialVelocity (v_x)]. * Parameters: mass (m), restLength (r0), springConstant (k). * _derivatives(t, y_state, ...params) method:
// y_state = [theta, omega, x, v_x]
// params = [effective_g, r0, k, m]
let r = r0 + y_state[2]; // current length
// ... equations for d_theta_dt, d_omega_dt, d_x_dt, d_v_x_dt
return [d_theta_dt, d_omega_dt, d_x_dt, d_v_x_dt];
- Controls: Sliders for initial angle, initial angular velocity, initial spring extension, initial radial velocity, mass, gravity, rest length, and spring constant. The
drawmethod calculates bob position usingcurrentLength = this.restLength + this.springExtension.
Double pendulum
A classic chaotic system whose complex equations are derived via Lagrangian mechanics.
Coding Implementation: * State Variables: A 4-element vector: [a1, a2, a1_v, a2_v] (angles and angular velocities for the two bobs). * Parameters: m1, m2, l1, l2 (masses and lengths of the two pendulum segments). * _derivatives(t, y, g, m1, m2, l1, l2) method: Implements the lengthy coupled equations for a1_a (angular acceleration of bob 1) and a2_a (angular acceleration of bob 2).
// y = [a1, a2, a1_v, a2_v]
// ... complex calculations for num1, num2, num3, den1, etc.
const a1_a = (num1 + num2 + num3) / den1;
// ... similar for a2_a
return [a1_v, a2_v, a1_a, a2_a];
- Controls: Sliders for masses (m1, m2), lengths (l1, l2), initial angles (a1, a2), initial angular velocities (a1_v, a2_v), and gravity. The
drawmethod calculates positions for both bobs and typically draws traces for the second bob.
Each pendulum module follows this pattern: define state and parameters, implement the _derivatives specific to its physics, and provide corresponding UI controls via getControlsHTML and bindSpecificControls. This approach makes the system highly extensible to other mechanical simulations.
For more insights into this topic, you can find the details here.