This page was generated from `source/notebooks/L6/2_coupled_pendula.ipynb`_.
Binder badge

Coupled Pendula

We will continue our course with some physical problems, we are going to tackle. One of the more extensive solutions will consider two coupled pendula. This belongs to the class of coupled oscillators, which are extremely important. They will later yield propagating waves. They are important for phonons, i.e. coupled vibration of atoms in solids, but there are also many other axamples. One can realize the coupled oscillation on different ways. He we will do that not with spring oscillators, by with pendula.

import Cell from ‘@nteract/presentational-components/src/components/cell’;

[13]:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from time import sleep,time
from threading import Thread
from ipycanvas import MultiCanvas, hold_canvas,Canvas

%matplotlib inline
%config InlineBackend.figure_format = 'retina'

# default values for plotting
plt.rcParams.update({'font.size': 12,
                     'axes.titlesize': 18,
                     'axes.labelsize': 16,
                     'axes.labelpad': 14,
                     'lines.linewidth': 1,
                     'lines.markersize': 10,
                     'xtick.labelsize' : 16,
                     'ytick.labelsize' : 16,
                     'xtick.top' : True,
                     'xtick.direction' : 'in',
                     'ytick.right' : True,
                     'ytick.direction' : 'in',})

# center the plots
from IPython.core.display import HTML
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
""")
[13]:

Description of the problem

Sketch

The image below depicts the sitution we would like to cover in our first project. These are two pendula, which have the length \(L_{1}\) and \(L_{2}\). Both are coupled with a spring of spring constant \(k\), which is relaxed, when both pendula are at rest. You may want to include a generalized version where the spring is mounted at a distance \(c\) from the turning points of the pendula.

If you develop the equation of motion, write down as a sum of torques. Use one equation of motion for each pendulum. The result will be two coupled differential equations for the angular coordinates. They are solved by the scipy odeint function without any friction.

Figure 1

Equations of motion

The equations of motion of the two coupled pendula have the following form:

\begin{eqnarray} I_{1}\ddot{\theta_{1}}&=&-m_{1}gL_{1}\sin(\theta_{1})-kc^2[\sin(\theta_{1})-\sin(\theta_{2})]\\ I_{2}\ddot{\theta_{2}}&=&-m_{2}gL_{2}\sin(\theta_{2})+kc^2[\sin(\theta_{1})-\sin(\theta_{2})] \end{eqnarray}

Here, \(\theta_{1}, \theta_{2}\) measure the angle of the two pendula with the length \(L_{1},L_{2}\). \(k\) is the spring constant of the spring coupling both pendula. If you use a variable coupling position of the spring name the length of the coupling from the turning point \(c\).

Solving the problem

Setting up the function

In our previous lecture, we used the odeint function of scipy to solve the driven damped harmonic oscillator. Remeber that we used the array

state[0] -> position
state[1] -> velocity

to exchange position and velocity with the solver via the function that defines the physical problem

def SHO(state, time):
    g0 = state[1]               ->velocity
    g1 = -k/m * state [0]       ->acceleration
    return np.array([g0, g1])

for a coupled system of different equations, we can now extend the state array. In the case of the coupled system of equations it has the following structure

def SHO(state, time):
    g0 = how the velocity of object 1 depends on the velocities of all objects
    g1 = how the acceleration of object 1 depends on the acceleration of all objects
    g2 = how the velocity of object 2 depends on the velocities of all objects
    g3 = how the acceleration of object 2 depends on the acceleration of all objects
    return np.array([g0, g1])

So the state vector just gets longer and the coupling is in the definition of the velocities and accelerations. The results are then the positions and velocities of the objects. Use this type of scheme to define the problem and write a function, which returns the state of the objects as before.

[2]:
def coupled_pendula(state,t):
    g0=state[1]
    g1=(-k*c**2/m/L1**2-g/L1)*np.sin(state[0])+k*c**2*np.sin(state[2])/m/L1**2
    g2=state[3]
    g3=(-k*c**2/m/L2**2-g/L2)*np.sin(state[2])+k*c**2*np.sin(state[0])/m/L2**2
    return np.array([g0,g1,g2,g3])

Define initial parameters

We want to define some parameters of the pendula

  • length of the pendulum 1, \(L_1\)=3

  • length of the pendulum 2, \(L_2\)=3

  • gravitational acceleration, \(g=9.81\)

  • mass at the end of the pendula, \(m=1\)

  • distance where the coupling spring is mounted, \(c=2\)

  • spring constant of the coupling spring, \(k=5\)

[3]:
# Initial parameters
# mass m1, m2, length of pendula L1, L2, position of the coupling, spring constant k, gravitational acceleration
L1=3 # length of pendulum 1
L2=3 # length of pendulum 2
g=9.81 # gravitational acceleration
m=1    # mass of at each of the pendula
c=2  # coupling distance from the mount
k=0.5    # coupling spring constant

As compared to our previous problem of a damped driven pendulum, where we had two initial conditions for the second order differential equation, we have now two second order differential equation. We therefore need 4 initial parameters, which are the initial elongations of both pendula and their corresponding initial angular velocities We will notice, that the solution,i.e. the motion of the pendula, will strongly depend on the initial conditions.

[4]:
# inital angles, initial angular velocities
a=np.pi/12   # initial angle for pendulum 1
b=np.pi/20  # initial angle for pendulum 2
o1=0.0        # initial angular velocity for pendulum 1
o2=0.0        # initial angular velocity for pendulum 2


#define the initial state
state=np.zeros(4)
state=a,o1,b,o2

Solve the equation of motion

We have to define a timeperiod over which we would like to obtain the solution. We use here a period of 400s where we calculate the solution at 10000 points along the 400s.

[5]:
# Solution
time=400  # total time to be simulated (in seconds)
N=10000   # number of timesteps for the simulation
t=np.linspace(0,time,N) # times at which the amplitudes shall be calculated

We are now ready to calculate the solution. Finally, we extract also the angles of the individual pendula, their angular velocities and the position of the point masses at the end of the pendulum. This can be then readily used to create some animation.

[6]:
#solve the differential equations
answer=odeint(coupled_pendula,state,t)

# angles
theta1=answer[:,0]
theta2=answer[:,2]

# angular velocities
omega1=answer[:,1]
omega2=answer[:,3]

#cordinates of the two masses at the end of the pendulum
xdata1=L1*np.sin(theta1)
xdata2=L2*np.sin(theta2)
ydata1=L1*np.cos(theta1)
ydata2=L2*np.cos(theta2)

Plotting

First, get some impression of how the angles change over time.

[7]:
# Plotting angles over time
plt.figure(figsize=(10,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel(r'$\theta_1,\theta_2$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,theta1,label='pendulum 1')
plt.plot(t,theta2,label='pendulum 2')
plt.legend()
plt.tight_layout()
plt.xlim(0,100)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_26_0.png

Animation

The plot of the angles over time is not always giving a good insight. With our knowledge about animations, we may easily animate the motion of the two pendula as well.

[8]:
## create our canvas
canvas = Canvas(width=300, height=200)

For the physics, it has neven been interesting to define at which distance the pendula are mounted at a ceiling for example. For drawing them we have to do that together with some additional parameters, which define for example the position of where to draw in the canvas and the conversion of meters to pixels.

[9]:
distance=2 # distance of the pendula in m
scale=40 # scale meter to pixels, 1 meter = 40 pixels
off_x=canvas.width/2 # horizontal center of the canvas
off_y=0 # top of the canvas

The function below will do the drawing for us. We define a function such that we can create a background animation with a thread. Note that we have inserted sleep(t[1]-t[0]) at the end. The drawing of the 4 objects will be pretty fast so that we can wait a certain amount of time until we display the next frame. That means at the end, that our simulation will run in real time.

[394]:
def draw():
    for i in range(len(xdata1)):
        canvas.line_width = 1
        canvas.fill_style = 'red' # fill color for the particles
        canvas.global_alpha = 1 # make the slightly transparent


        with hold_canvas(canvas):
            canvas.clear() # clear the canvas before drawing
            ##draw the two connections to the ceiling
            canvas.begin_path()
            canvas.move_to(scale*distance/2+off_x, off_y)
            canvas.line_to((xdata1[i]+distance/2)*scale+off_x, ydata1[i]*scale+off_y)
            canvas.move_to(-scale*distance/2+off_x, off_y)
            canvas.line_to((xdata2[i]-distance/2)*scale+off_x, ydata2[i]*scale+off_y)
            canvas.stroke()
            ## draw the two masses
            canvas.fill_arc((xdata1[i]+distance/2)*scale+off_x, ydata1[i]*scale+off_y, 10, 0, 2*np.pi)
            canvas.fill_arc((xdata2[i]-distance/2)*scale+off_x, ydata2[i]*scale+off_y, 10, 0, 2*np.pi)
            ## sleep a timestep after drawing
            sleep(t[1]-t[0])
[398]:
simulation = Thread(target=draw)

Looks pretty slow, but remember the pendula are 3 meters long.

[399]:
display(canvas)
simulation.start()

image1

Normal Modes

We will not cover all the physical details here, but you might remember your mechanis lectures, that two coupled oscillators show distinct modes of motion, which we would call the normal modes. Four two coupled pendula there are two normal modes, where both pendula move with the same frequency. We may force the system into one of its normal modes, by specifying its initial conditions properly.

In-phase motion

The first one, will create an in-phase motion of the two pendula by setting their initial elongation equal, i.e. \(\theta_{1}(t=0)=\theta_{2}(t=0)\). Both pendula then oscillate with their natural frequency and the coupling spring is never elongated.

[31]:
# inital angles, initial angular velocities
a=np.pi/12   # initial angle for pendulum 1
b=np.pi/12   # initial angle for pendulum 2
o1=0.0        # initial angular velocity for pendulum 1
o2=0.0        # initial angular velocity for pendulum 2


#define the initial state
state=np.zeros(4)
state=a,o1,b,o2


[32]:
#solve the differential equations
answer=odeint(coupled_pendula,state,t)

# angles
theta1=answer[:,0]
theta2=answer[:,2]

# angular velocities
omega1=answer[:,1]
omega2=answer[:,3]

#cordinates of the two masses at the end of the pendulum
xdata1=L1*np.sin(theta1)
xdata2=L2*np.sin(theta2)
ydata1=L1*np.cos(theta1)
ydata2=L2*np.cos(theta2)

[33]:
# Plotting angles over time
plt.figure(figsize=(10,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel(r'$\theta_1,\theta_2$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,theta1)
plt.plot(t,theta2)
plt.tight_layout()
plt.xlim(0,10)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_44_0.png

Out-of-phase motion

The second one, will create a motion in which the two pendula are out-of-phase by a phase angle of \(\pi\) , i.e. \(\theta_{1}(t=0)=-\theta_{2}(t=0)\). Both pendula then oscillate with a frequency higher than their natural frequency. This is due to the fact that there is a higher restoring force due to the action of the spring.

[348]:
# inital angles, initial angular velocities
a=np.pi/12   # initial angle for pendulum 1
b=-np.pi/12   # initial angle for pendulum 2
o1=0.0        # initial angular velocity for pendulum 1
o2=0.0        # initial angular velocity for pendulum 2


#define the initial state
state=np.zeros(4)
state=a,o1,b,o2
[349]:
#solve the differential equations
answer=odeint(coupled_pendula,state,t)

# angles
theta1=answer[:,0]
theta2=answer[:,2]

# angular velocities
omega1=answer[:,1]
omega2=answer[:,3]

#cordinates of the two masses at the end of the pendulum
xdata1=L1*np.sin(theta1)
xdata2=L2*np.sin(theta2)
ydata1=L1*np.cos(theta1)
ydata2=L2*np.cos(theta2)
[350]:
# Plotting angles over time
plt.figure(figsize=(10,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel(r'$\theta_1,\theta_2$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,theta1)
plt.plot(t,theta2)
plt.tight_layout()
plt.xlim(0,10)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_49_0.png

Beat case

The last case is not a normal mode but represents a more general case. We start with two different initial angles, i.e. \(\theta_{1}(t=0)=\pi/12\) and \(\theta_{2}(t=0)=0\). This is the so-called beat case, where the pendula exchange energy. The oscillation, which is at the beginning in only in the first pendulum is then transfer to the second one. This transfer of energy is continuously occurring from one pendulum to the other since there’s nowhere for the energy to go. From this point it’s easily to recognize how the wife is generated. In a set of many coupled pendula one pendulum is starting to oscillate and is transferring it’s energy to the next one and then to the next one and then to the next one and this way the energy is propagating along all oscillators.

[374]:
# inital angles, initial angular velocities
a=np.pi/12   # initial angle for pendulum 1
b=0   # initial angle for pendulum 2
o1=0.0        # initial angular velocity for pendulum 1
o2=0.0        # initial angular velocity for pendulum 2


#define the initial state
state=np.zeros(4)
state=a,o1,b,o2
[375]:
#solve the differential equations
answer=odeint(coupled_pendula,state,t)

# angles
theta1=answer[:,0]
theta2=answer[:,2]

# angular velocities
omega1=answer[:,1]
omega2=answer[:,3]

#cordinates of the two masses at the end of the pendulum
xdata1=L1*np.sin(theta1)
xdata2=L2*np.sin(theta2)
ydata1=L1*np.cos(theta1)
ydata2=L2*np.cos(theta2)

[376]:
# Plotting angles over time
plt.figure(figsize=(10,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel(r'$\theta_1,\theta_2$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,theta1)
plt.plot(t,theta2)
plt.tight_layout()
plt.xlim(0,50)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_54_0.png

Computation of energy (here for the beat case)

After we have had a look at the motion of the individual pendula, we may also check, the energies in the system. We have to calculate the potential and kinetic energies of the pendula and we should not forget the potential energy stored in the spring.

Potential energy of the pendula

The potential energy plot below nicely shows the exchange of energy between the two pendula in the beat case.

[377]:
# calculate and plot the potential energies of each pendulum and plot dem independently
E_pot1=m*g*L1*(1-np.cos(theta1))
E_pot2=m*g*L2*(1-np.cos(theta2))

plt.figure(1,figsize=(12,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel('$E_{pot,1}$,$E_{pot,2}$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,E_pot1)
plt.plot(t,E_pot2)
plt.xlim(0,50)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_59_0.png

Potential energy of the spring

[378]:
# calculate and plot the potential energy stored in the spring
d=c*(np.sin(theta1)-np.sin(theta2))
E_spring=k/2*d**2

plt.figure(3,figsize=(12,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel('$E_{\mathrm{spring}}$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,E_spring)
plt.xlim(0,50)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_61_0.png

Kinetic energies

[379]:
# calculate and plot the kinetic energies of each pendulum and plot dem independently
E_kin1=0.5*m*(omega1*L1)**2
E_kin2=0.5*m*(omega2*L2)**2

plt.figure(2,figsize=(12,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel('$E_{kin,1}$,$E_{kin,2}$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,E_kin1)
plt.plot(t,E_kin2)
plt.xlim(0,50)
plt.show()
../../_images/notebooks_L6_2_coupled_pendula_63_0.png

Total energy

As the total energy in the system shall nbe conserved, the sum of all energy contributions should yield a flat line.

[383]:
# calculate the total energy of the system
E_tot = E_pot1 + E_pot2 + E_kin1 + E_kin2 + E_spring

plt.figure(3,figsize=(12,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel('$E_{\mathrm{total}}$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,E_tot)
plt.ylim(0,2)
plt.xlim(0,50)

plt.show()
../../_images/notebooks_L6_2_coupled_pendula_66_0.png

Total energy exchange of the pendula

While the plot above Shows the total energy of both pendula we may now have a look at the total energy in each pendulum. The plots clearly show that the energy is exchanged between the two pendula. The residual ripples on the curve results from the fact that we here exclude the potential energy stored in the spring.

[384]:
# calculate the total energy of the system
E_tot1 = E_pot1 + E_kin1
E_tot2 = E_pot2 + E_kin2

plt.figure(3,figsize=(12,5))
plt.xlabel('time [s]', fontsize=16)
plt.ylabel('$E_{\mathrm{total}}$',fontsize=16)
plt.tick_params(labelsize=14)
plt.plot(t,E_tot1)
plt.plot(t,E_tot2)
plt.xlim(0,50)
plt.show()

../../_images/notebooks_L6_2_coupled_pendula_69_0.png