Constructing a Pendulum Wave

by Ganden Schaffner

The basis of a pendulum wave is that, in a certain time (the time of the whole "dance" - let's call this $\Gamma$) the pendulum on the longest length string will oscillate $N$ times. The length of each successive shorter pendulum is adjusted such that it executes one additional oscillation in this same time $\Gamma$.

$\Gamma$ will be defined as $60 seconds$, as this number is arbitrary, and 60 seconds is a good, even duration after which the dance will reset and repeat. (The dance will repeat infinitely, assuming that air resistance is negligible. In reality, all pendulums will come to a stop after some time due to air resistance, but 60 seconds should be a short enough time that air resistance will have little effect on the pendulums over this $\Gamma$.)

Other pendulum waves have found $N=51$ to be a good number, used in conjunction with $\Gamma=60 seconds$. Changing $N$ would make the "dance" quicker or slower, and others have found 51 oscillations over 60 seconds (for the longest pendulum) to provide a good viewing speed, so we will use $N=51$ as well.

Therefore, if 12 pendulums are used (another arbitrary number that has worked well in creating other pendulum wave machines), the 1st pendulum (longest) undergoes 51 oscillations in $\Gamma$ seconds, while the 12th pendulum (shortest) undergoes 62 oscillations (51 oscilations + 11 pendulums from the first) in this same $\Gamma=60 seconds$.

Deriving an Equation for the String Length of Each Pendulum

Background Information

  • This derivation assumes that the pendulum only operates under the small angle approximation, where $ \sin(\theta) = \theta$ (in radians). This means that the pendulum should not exceed angles of about ten degrees from its equilibrium position.
  • The position of the bob on a pendulum as a function of time, with the bob starting at its maximum displacement from the equilibrium position, is $x(t) = A\cos(2\pi f t) = A\cos(\frac{2\pi t}{T})$
  • The period of a pendulum can be written $T = 2\pi \sqrt{\frac{l}{g}}$

Deriving $l$

Since $T = \frac{seconds}{oscillation}$, the period of the longest pendulum is $T = \frac{\Gamma}{N}$

Because other pendulums undergo $N$ + (pendulums from the first) oscillations, their period can be written as a function of pendulums from the first: $T(n) = \frac{\Gamma}{N + n}$

Knowing that $T = 2\pi \sqrt{\frac{l}{g}}$, $T(n)$ can be substituted for $T$, yielding $\frac{\Gamma}{N + n} = 2\pi \sqrt{\frac{l(n)}{g}}$

$l$ can then be solved for:

  • $\frac{\Gamma}{2\pi(N + n)} = \sqrt{\frac{l(n)}{g}}$
  • $(\frac{\Gamma}{2\pi(N + n)})^2 = \frac{l(n)}{g}$
  • $l(n) = g(\frac{\Gamma}{2\pi(N + n)})^2$

For our pendulum wave, consisting of twelve pendulums, Python 3 code was written to solve for the required $l$ values (with $n$ ranging from 0 to 11 for the longest to shortest pendulums, respectively. This code and its output can be seen below:


In [1]:
import math
from math import pi

lengthsM = []

danceDuration = 60
mostOscils = 51

# Where n is the index of the pendulum (starting at 0)
length = lambda n: 9.81 * (danceDuration / (2*pi*(mostOscils + n)))**2

for n in range(12):  # 12 pendulums, indexed 0-11
	lengthsM.append(length(n))

# Convert lengths to inches for ease
lengthsIn = []
for length in lengthsM:
	lengthsIn.append(length*39.3701)

lengthsMRounded = []
for length in lengthsM:
    lengthsMRounded.append(round(length, 3))

lengthsInRounded = []
for length in lengthsIn:
    lengthsInRounded.append(round(length, 3))

print("Lengths in meters: {}".format(lengthsMRounded))
print()
print("Lengths in inches: {}".format(lengthsInRounded))


Lengths in meters: [0.344, 0.331, 0.318, 0.307, 0.296, 0.285, 0.275, 0.266, 0.257, 0.248, 0.24, 0.233]

Lengths in inches: [13.541, 13.025, 12.538, 12.078, 11.643, 11.231, 10.84, 10.469, 10.118, 9.783, 9.465, 9.162]

From this point, the pendulum was built. Note that the lengths listed above are from the pivot point of the pendulums to the centers of mass of the hanging golf balls, so the actual strings will be shorter than the numbers given above.

The pendulums were spaced so that there was 0.5in between the golf balls, meaning that strings were hung about 2.2in, or 5.6cm, apart.

Viewing the Device from Above, Using Python

Below, a visualization of the device can be seen (from above). This simulates the position the center of mass of each golf ball, as if the pendulum was started with the balls all 10º from their equilibrium position. A dashed line has been drawn in to show the approximate pattern of the pendulums.


In [2]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
%matplotlib inline

location = np.linspace(0, 0.056*11, 12)

funcTheta = lambda theta_i, n, t: theta_i * math.cos((2 * pi * t) / (60 / (51 + n)))
funcDispX = lambda l, theta: l * math.sin(theta)
funcDispY = lambda l, theta: -1 * l * math.cos(theta)

# Set up the figure, the axis, and the plot we want to animate
fig, ax = plt.subplots()
ax.set_xlim([0, location[11]])
ax.set_ylim([-0.06, 0.06])
line, = ax.plot([], [], linestyle='--', marker='o')
plt.xlabel("Position along the machine, meters")
plt.ylabel("Horizonal displacement from equilibrium, meters")
fig.suptitle("Top View at t = 0 seconds", fontsize=12)
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.labelpad = 100

def init1():
    line.set_data([], [])
    return (line,)

def animate1(t):
    displacementArr = []
    for n in range(12):
        displacementArr.append(funcDispX(lengthsM[n], funcTheta(math.radians(10), n, t)))
    fig.suptitle("Top View at t = {:.3f} seconds".format(round(t, 3)), fontsize=12)
    line.set_data(location, np.array(displacementArr))
    return (line,)

fps = 60
anim = animation.FuncAnimation(fig, animate1, init_func=init1, frames=np.linspace(0, 60, 60*fps//2), blit=True)
anim.save('animTopDown.mp4', writer='ffmpeg', fps=str(fps), extra_args=['-vcodec', 'libx264'])
plt.close()

Visualizing the Device from Head-On, Using Python

Below, a visualization of the device can be seen (from head-on). This simulates the position the center of mass of each golf ball, as if the pendulum was started with the balls all 10º from their equilibrium position.


In [3]:
# Set up the figure, the axis, and the plot we want to animate
fig, ax = plt.subplots()
ax.set_xlim([-0.15, 0.15])
ax.set_ylim([-0.4, 0])
line, = ax.plot([], [], linestyle='', marker='o')
plt.xlabel("Horizonal displacement from equilibrium, meters")
plt.ylabel("Vertical displacement from equilibrium, meters")
fig.suptitle("Top View at t = 0 seconds", fontsize=12)
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.labelpad = 220
ax.yaxis.labelpad = 150

def init2():
    line.set_data([], [])
    return (line,)

def animate2(t):
    xPos = []
    yPos = []
    for n in range(12):
        xPos.append(funcDispX(lengthsM[n], funcTheta(math.radians(10), n, t)))
        yPos.append(funcDispY(lengthsM[n], funcTheta(math.radians(10), n, t)))
    fig.suptitle("Head-On View at t = {:.3f} seconds".format(round(t, 3)), fontsize=12)
    line.set_data(np.array(xPos), np.array(yPos))
    return (line,)

fps = 60
anim = animation.FuncAnimation(fig, animate2, init_func=init2, frames=np.linspace(0, 60, 60*fps//2), blit=True)
anim.save('animHeadOn.mp4', writer='ffmpeg', fps=str(fps), extra_args=['-vcodec', 'libx264'])
plt.close()

Observations

  • Compared to the real-life machine that was built, the visualization above is more accurate. This is because air resistance and friction came in to major effect over the 60s $\Gamma$ of the real-life machine, while both of these sources of error did not exist in the simulation seen above.
  • As expected, each golf ball was in the same place at $t = 0s$ and $t = \Gamma = 60s$.
  • Various patterns and shapes occurred throughout the simulation (and real experiment). They occurred due to the pendulums positions getting out of sync (due to their different lengths and therefore different periods). The pendulums started in sync, but the constant difference of 1 oscillation over 60 seconds slowly put them out of sync with their neighboring pendulums.
  • Matplotlib with animations is janky.