# 8 Modeling a Drone Swinging in a Trifilar Pendulum¶

A trifilar pendulum is a common tool for determining the inertia of a rigid body. In the video below a small quadcopter drone is hung from a trifilar pendulum and set into an oscillation about the vertical axis. The frequency (or period) of this oscillation correlates with the inertia of the drone about the axis of rotation. In this notebook you will learn how to create a mathematical model of this system.

In [1]:
from IPython.display import YouTubeVideo
YouTubeVideo('HrG7xhrLbWo')

Out[1]:
In [2]:
import numpy as np
import sympy as sm
sm.init_printing()

In [3]:
%matplotlib inline


# System parameters¶

We introduced a number of variable to describe the geometry in the problem:

• $m$: total mass of the drone
• $l$: length of the very stiff strings
• $I$: rotational inertia of the drone about a vertical axis and with respect to the center of mass
• $g$: acceleration due to gravity
• $r$: the radial distance from the center of mass to the location of the string attachments
In [4]:
m, I, g, l, r = sm.symbols('m, I, g, l, r', real=True, positive=True)


The goal is to write the equations of motion in terms of the generalized coordinate. In our case are going to use the rotational angle of the drone, $\theta$. This variable should be a function of time.

In [5]:
t = sm.symbols('t')

In [6]:
theta = sm.Function('theta')(t)
theta

Out[6]:
$\displaystyle \theta{\left(t \right)}$

With $\theta$ being a function of time the first and second derivatives can be easily taken with .diff().

In [7]:
theta.diff(t)

Out[7]:
$\displaystyle \frac{d}{d t} \theta{\left(t \right)}$
In [8]:
theta.diff(t, 2)

Out[8]:
$\displaystyle \frac{d^{2}}{d t^{2}} \theta{\left(t \right)}$

# Getting the geometry and velocity right¶

The height of the drone from its equilibrium resting position is written in terms of $\theta$ below.

In [9]:
h = l - sm.sqrt(l**2 - (2 * r * sm.sin(theta / 2))**2)
h

Out[9]:
$\displaystyle l - \sqrt{l^{2} - 4 r^{2} \sin^{2}{\left(\frac{\theta{\left(t \right)}}{2} \right)}}$

The time derivative of $h$ is also needed to compute the linear kinetic energy.

In [10]:
v = h.diff(t)
v

Out[10]:
$\displaystyle \frac{2 r^{2} \sin{\left(\frac{\theta{\left(t \right)}}{2} \right)} \cos{\left(\frac{\theta{\left(t \right)}}{2} \right)} \frac{d}{d t} \theta{\left(t \right)}}{\sqrt{l^{2} - 4 r^{2} \sin^{2}{\left(\frac{\theta{\left(t \right)}}{2} \right)}}}$

This can be simplified a bit:

In [11]:
v = sm.trigsimp(v)
v

Out[11]:
$\displaystyle \frac{r^{2} \sin{\left(\theta{\left(t \right)} \right)} \frac{d}{d t} \theta{\left(t \right)}}{\sqrt{l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}}}$

# Forming the Lagrangian¶

Now the total kinetic energy can be formed by summing the linear and rotational kinetic energies:

In [12]:
T = m * v**2 / 2 + I * theta.diff()**2 / 2
T

Out[12]:
$\displaystyle \frac{I \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{2} + \frac{m r^{4} \sin^{2}{\left(\theta{\left(t \right)} \right)} \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{2 \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)}$

The total potential energy is:

In [13]:
U = m * g * h
U

Out[13]:
$\displaystyle g m \left(l - \sqrt{l^{2} - 4 r^{2} \sin^{2}{\left(\frac{\theta{\left(t \right)}}{2} \right)}}\right)$

The Lagrangian:

In [14]:
L = T - U
L

Out[14]:
$\displaystyle \frac{I \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{2} - g m \left(l - \sqrt{l^{2} - 4 r^{2} \sin^{2}{\left(\frac{\theta{\left(t \right)}}{2} \right)}}\right) + \frac{m r^{4} \sin^{2}{\left(\theta{\left(t \right)} \right)} \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{2 \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)}$

# Forming and evaluating Lagrange's Equation¶

Now Lagrange's equation of the second kind can be formed with respect to the single generalized coordinate.

$$\frac{d}{dt}\left(\frac{\partial L}{\partial \dot{\theta}}\right) - \frac{\partial L}{\partial \theta} = 0$$
In [15]:
zero = L.diff(theta.diff(t)).diff(t) - L.diff(theta)
zero

Out[15]:
$\displaystyle I \frac{d^{2}}{d t^{2}} \theta{\left(t \right)} + \frac{2 g m r^{2} \sin{\left(\frac{\theta{\left(t \right)}}{2} \right)} \cos{\left(\frac{\theta{\left(t \right)}}{2} \right)}}{\sqrt{l^{2} - 4 r^{2} \sin^{2}{\left(\frac{\theta{\left(t \right)}}{2} \right)}}} + \frac{m r^{6} \sin^{3}{\left(\theta{\left(t \right)} \right)} \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{\left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)^{2}} + \frac{m r^{4} \sin^{2}{\left(\theta{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \theta{\left(t \right)}}{l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}} + \frac{m r^{4} \sin{\left(\theta{\left(t \right)} \right)} \cos{\left(\theta{\left(t \right)} \right)} \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}}$

Some trigonometric simplification can clean it up a bit:

In [16]:
zero = sm.trigsimp(zero)
zero

Out[16]:
$\displaystyle I \frac{d^{2}}{d t^{2}} \theta{\left(t \right)} + \frac{g m r^{2} \sin{\left(\theta{\left(t \right)} \right)}}{\sqrt{l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}}} + \frac{m r^{6} \sin^{3}{\left(\theta{\left(t \right)} \right)} \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{\left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)^{2}} + \frac{m r^{4} \sin^{2}{\left(\theta{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \theta{\left(t \right)}}{l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}} + \frac{m r^{4} \sin{\left(2 \theta{\left(t \right)} \right)} \left(\frac{d}{d t} \theta{\left(t \right)}\right)^{2}}{2 \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)}$

# Putting the equations of motion into first order form¶

The equation of motion is linear with respect to $\ddot{\theta}$ and the coefficient can be extracted with:

In [17]:
acc_coeff = zero.coeff(theta.diff(t, 2))
acc_coeff

Out[17]:
$\displaystyle I + \frac{m r^{4} \sin^{2}{\left(\theta{\left(t \right)} \right)}}{l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}}$

This system can be put into first order form by introducing a new variable $\omega = \dot{\theta}$ (this relationship is also the first of the two ODEs). The second is then:

In [18]:
omega = sm.Function('omega')(t)
omega_dot = sm.simplify(-(zero.subs({theta.diff(): omega}) - acc_coeff * omega.diff()) / acc_coeff)
sm.Eq(omega.diff(), omega_dot)

Out[18]:
$\displaystyle \frac{d}{d t} \omega{\left(t \right)} = - \frac{m r^{2} \left(2 g \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)^{4} \sin{\left(\theta{\left(t \right)} \right)} + 2 r^{4} \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)^{\frac{5}{2}} \omega^{2}{\left(t \right)} \sin^{3}{\left(\theta{\left(t \right)} \right)} + r^{2} \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)^{\frac{7}{2}} \omega^{2}{\left(t \right)} \sin{\left(2 \theta{\left(t \right)} \right)}\right)}{2 \left(I \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right) + m r^{4} \sin^{2}{\left(\theta{\left(t \right)} \right)}\right) \left(l^{2} + 2 r^{2} \cos{\left(\theta{\left(t \right)} \right)} - 2 r^{2}\right)^{\frac{7}{2}}}$

# Simulating the nonlinear equations of motion¶

In [19]:
from resonance.nonlinear_systems import SingleDoFNonLinearSystem

In [20]:
nl_sys = SingleDoFNonLinearSystem()

In [21]:
nl_sys.constants['m'] = 1  # kg
nl_sys.constants['r'] = 0.3  # m
nl_sys.constants['l'] = 0.75  # m
nl_sys.constants['g'] = 9.81  # m/s**2
nl_sys.constants['I'] = 0.3**2  # kg m**2

nl_sys.coordinates['theta'] = np.deg2rad(15) # rad
nl_sys.speeds['omega'] = 0.0  # rad/s


The 2nd order differential equations for a non-linear system have to be specificied in explicit first order form for most numerical integration routines (which is what is used behind the scenes in resonance). This means we need to introduce a new variable, a generalized speed, that defines the first of the two first order equations. We also have to move everything to the right hand side of the equations except for terms with derivatives.:

$$\dot{\theta} = \omega \\ \dot{\omega} = f(\omega, \theta, m, r, l, g, I, t)$$

You have to create a function that computes the time derivatives of the coordinates and speeds (the states) given the current values of the coordinates and speeds. The function will have arguments that match your variable names defined above and it must output the time derivatives that correspond to the order of sys.states. See below how to translate this mathematical expression into a "right hand side" function.

Option 1) Copy code form SymPy output and modify by hand:

In [22]:
str(omega_dot)

Out[22]:
'-m*r**2*(2*g*(l**2 + 2*r**2*cos(theta(t)) - 2*r**2)**4*sin(theta(t)) + 2*r**4*(l**2 + 2*r**2*cos(theta(t)) - 2*r**2)**(5/2)*omega(t)**2*sin(theta(t))**3 + r**2*(l**2 + 2*r**2*cos(theta(t)) - 2*r**2)**(7/2)*omega(t)**2*sin(2*theta(t)))/(2*(I*(l**2 + 2*r**2*cos(theta(t)) - 2*r**2) + m*r**4*sin(theta(t))**2)*(l**2 + 2*r**2*cos(theta(t)) - 2*r**2)**(7/2))'
In [23]:
def eval_rhs(theta, omega, I, m, r, l, g):
theta_dot = omega
omega_dot = (-m*r**2*(2*g*(l**2 + 2*r**2*np.cos(theta) -
2*r**2)**4*np.sin(theta) +
2*r**4*(l**2 + 2*r**2*np.cos(theta) -
2*r**2)**(5/2)*omega**2*np.sin(theta)**3 +
r**2*(l**2 + 2*r**2*np.cos(theta) -
2*r**2)**(7/2)*omega**2*np.sin(2*theta))/
(2*(I*(l**2 + 2*r**2*np.cos(theta) - 2*r**2) +
m*r**4*np.sin(theta)**2)*(l**2 + 2*r**2*np.cos(theta) -
2*r**2)**(7/2)))
return theta_dot, omega_dot

nl_sys.diff_eq_func = eval_rhs


Try out the function with some arbitrary values:

In [24]:
eval_rhs(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7)

Out[24]:
$\displaystyle \left( 0.2, \ -0.03979972088000974\right)$

Option 2) Use lambdify to create the function with no manual copying.

In [25]:
eval_omega_dot = sm.lambdify((theta, omega, I, m, r, l, g), omega_dot)


You can see that the function works the same as the above function:

In [26]:
eval_omega_dot(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7)

Out[26]:
$\displaystyle -0.03979972088000975$
In [27]:
def eval_rhs(theta, omega, I, m, r, l, g):
theta_dot = omega
omega_dot = eval_omega_dot(theta, omega, I, m, r, l, g)
return theta_dot, omega_dot

nl_sys.diff_eq_func = eval_rhs

In [28]:
eval_rhs(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7)

Out[28]:
$\displaystyle \left( 0.2, \ -0.03979972088000975\right)$
In [29]:
nl_traj = nl_sys.free_response(20.0)

In [30]:
nl_traj.plot(subplots=True);


# Linearizing the Equations of Motion¶

Assume $\theta$ is small, therefore:

• $\sin\theta = \theta$
• $\cos(\theta) = 1$
• $\theta \cdot \theta = 0$
• $\dot{\theta} \cdot \dot{\theta} = 0$
• $\sin^2\theta = \theta^2 = 0$
In [31]:
zero = zero.subs({theta.diff(): omega})
zero_lin = zero.subs({sm.sin(theta): theta,
sm.cos(theta): 1,
sm.sin(theta / 2): theta / 2,
sm.cos(theta / 2): 1,
theta**2: 0,
omega**2: 0,
}).subs(theta**2, 0)  # have to get this last one because order of subs matters
sm.Eq(zero_lin, 0)

Out[31]:
$\displaystyle I \frac{d}{d t} \omega{\left(t \right)} + \frac{g m r^{2} \theta{\left(t \right)}}{l} = 0$

# Simulating the linear equations of motion¶

In [32]:
from resonance.linear_systems import SingleDoFLinearSystem

In [33]:
sys = SingleDoFLinearSystem()

In [34]:
sys.constants['m'] = 1  # kg
sys.constants['r'] = 0.3  # m
sys.constants['l'] = 0.7  # m
sys.constants['g'] = 9.81  # m/s**2
sys.constants['I'] = 0.3**2  # kg m**2

In [35]:
sys.coordinates['theta'] = np.deg2rad(15)  # rad
sys.speeds['omega'] = 0.0  # rad/s

In [36]:
def coeffs(m, r, l, g, I):
k = g * m * r**2 / l
return I, 0, k

In [37]:
sys.canonical_coeffs_func = coeffs

In [38]:
traj = sys.free_response(5.0)

In [39]:
traj.plot(subplots=True);


# Compare the nonlinear and linear motion¶

In [40]:
initial_angle = 35  # degrees

nl_sys.coordinates['theta'] = np.deg2rad(initial_angle)
sys.coordinates['theta'] = np.deg2rad(initial_angle)

nl_traj = nl_sys.free_response(10)
traj = sys.free_response(10)

axes = nl_traj.plot(subplots=True, color='blue')
traj.plot(subplots=True, ax=axes, color='red');