This notebook builds on the previous one by introducing both a nonlinear pendulum and a nonlinear damping effect through Coulomb friction. Students will be able to work with both a linear and nonlinear version of the same system (a clock pendulum) in order to compare the free response in both cases.
After the completion of this assignment students will be able to:
A common source of damping and energy dissipation is friction, which is a specific type of damping. With viscous damping, we had a torque that was linearly proportional to the angular velocity:
$$T_f = c l^2 \dot{\theta}$$This simple source of energy dissipation is a reasonable mathematical model for many phenomena, but it isn't often a good model for dry friction between to hard materials. One very useful model of friction is Coulomb friction. Coulomb friction behaves more like:
$$T_f = \begin{cases} -\frac{2}{3}\mu R F_N & \dot{\theta} > 0 \\ 0 & \dot{\theta} = 0 \\ \frac{2}{3}\mu R F_N & \dot{\theta} < 0 \end{cases}$$where $\mu$ is a coefficient of sliding friction, $R$ is the outer radius of the joint contact (assuming disc/disc contact, see http://www.iitg.ernet.in/kd/Lecture%20Notes/ME101-Lecture15-KD_DivI.pdf for an explanation), and $F_N$ is the normal force in the pivot joint. Here the damping torque is constant, always impeding the motion of the system.
This can also be more simply written using the signum function:
$$ T_f = -\frac{2}{3}\mu R F_n \text{sgn}\left( \dot{\theta} \right)$$To start import some of the common packages we will need:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
resonance
has both a version of the clock pendulum with the viscous damping (a model of air drag) and Coulomb friction (a model of joint friction). The first is a linear system due to the torque being linear in the angular velocity and the second is a nonlinear system because the torque is nonlinear with respect to the velocity. Import them both:
from resonance.nonlinear_systems import ClockPendulumSystem as NonLinPendulum
from resonance.linear_systems import ClockPendulumSystem as LinPendulum
non_lin_sys = NonLinPendulum()
help(NonLinPendulum)
non_lin_sys.constants
lin_sys = LinPendulum()
help(LinPendulum)
lin_sys.constants
Exercise
Simulate each system for 5 seconds with an initial angle of 1.0 degrees and plot the trajectory of the angle from each response on a single plot to see if there are any differences.
initial_angle = np.deg2rad(1.0)
non_lin_sys.coordinates['angle'] = initial_angle
lin_sys.coordinates['angle'] = initial_angle
duration = 5.0
non_lin_traj = non_lin_sys.free_response(duration)
lin_traj = lin_sys.free_response(duration)
# write your answer here
fig, ax = plt.subplots(1, 1)
ax.plot(non_lin_traj.index, non_lin_traj.angle)
ax.plot(lin_traj.index, lin_traj.angle)
ax.legend(['Nonlinear', 'Linear']);
Exercise
Create a function called compare_lin_to_nonlin
that accepts a single angle value in degrees as the only argument and creates a plot just like the one you made above. Don't forget axis labels and a legend so that we can tell the two lines apart. Include the initial angle in the title of the plot. It should look something like:
def compare_lin_to_nonlin(initial_angle):
# write your code here (hint copy most of it from above and modify)
fig, ax = plt.subplots(1, 1)
ax.plot(non_lin_traj.index, np.rad2deg(non_lin_traj.angle))
ax.plot(lin_traj.index, np.rad2deg(lin_traj.angle))
# write the legend, labels, title, etc here
ax.grid()
def compare_lin_to_nonlin(initial_angle):
initial_angle = np.deg2rad(initial_angle)
non_lin_sys.coordinates['angle'] = initial_angle
lin_sys.coordinates['angle'] = initial_angle
non_lin_traj = non_lin_sys.free_response(duration)
lin_traj = lin_sys.free_response(duration)
fig, ax = plt.subplots(1, 1)
ax.plot(non_lin_traj.index, np.rad2deg(non_lin_traj.angle))
ax.plot(lin_traj.index, np.rad2deg(lin_traj.angle))
ax.legend(['Nonlinear', 'Linear'])
ax.set_xlabel('Time [s]')
ax.set_ylabel('Angle [deg]')
ax.set_title('Initial angle: {:1.0f} deg'.format(np.rad2deg(initial_angle)))
ax.grid()
# write your answer here
Exercise
Try out some angles from 0 to 180 degrees and view the graphs to see if there is anything interesting.
compare_lin_to_nonlin(1)
compare_lin_to_nonlin(25)
compare_lin_to_nonlin(87)
compare_lin_to_nonlin(130)
# write your answer here
Exercise
Make a plot of initial angle versus period for the nonlinear pendulum. Report on your what you learn from this plot. The code should look something like:
periods = []
angles = np.deg2rad(np.linspace(0, 90))
for angle in angles:
# fill in the loop here
fig, ax = plt.subplots(1, 1)
ax.plot(np.rad2deg(angles), periods)
ax.set_ylabel('Period [s]')
ax.set_xlabel('Initial Angle [deg]')
from resonance.functions import estimate_period
periods = []
angles = np.deg2rad(np.linspace(0, 90))
for angle in angles:
non_lin_sys.coordinates['angle'] = angle
traj = non_lin_sys.free_response(5.0)
periods.append(estimate_period(traj.index, traj.angle))
fig, ax = plt.subplots(1, 1)
ax.plot(np.rad2deg(angles), periods)
ax.set_ylabel('Period [s]')
ax.set_xlabel('Initial Angle [deg]')
# write your answer here
Below we add a small amount of viscous damping to the linear pendulum and a small amount of Coulomb friction to the nonlinear pendulum. If you compare the trajectories of the angle at an initial angle of 5 degrees you see some differences.
lin_sys.constants['viscous_damping'] = 0.1 # Ns/m
non_lin_sys.constants['coeff_of_friction'] = 0.1 # Nm
compare_lin_to_nonlin(5.0)
non_lin_traj = non_lin_sys.free_response(5.0, sample_rate=500)
from scipy.optimize import curve_fit
Exercise
Use your curve fitting function for $\theta(t) = A e^{\lambda t} \cos(\omega t + \phi)$ and see how well it fits the nonlinear trajectory.
def exp_decayed_oscillation(times, amplitude, decay_constant, frequency, phase_shift):
# write your function here and return the answer
def exp_decayed_oscillation(times, amplitude, decay_constant, frequency, phase_shift):
return amplitude * np.exp(decay_constant * times) * np.cos(frequency * times + phase_shift)
# write your answer here
Since there is something funny going on at the end of the simulation, only fit to the first 3 seconds of data.
popt, pcov = curve_fit(exp_decayed_oscillation,
non_lin_traj[:3.0].index, non_lin_traj[:3.0].angle,
p0=(5.0, -0.001, 2 * np.pi, 0.0))
fig, ax = plt.subplots(1, 1)
ax.plot(non_lin_traj.index, non_lin_traj.angle, '.')
best_fit_angle = exp_decayed_oscillation(non_lin_traj.index, popt[0], popt[1], popt[2], popt[3])
ax.plot(non_lin_traj.index, best_fit_angle)
ax.legend(['Data', 'Fit'])
ax.set_xlabel('Time [s]')
ax.set_ylabel('Angle [rad]')
Exercise
Now try a function that looks like:
$$ \theta(t) = (m t + A) \cos(\omega t + \phi) $$This is a linear decaying function instead of an exponentially decaying function.
def lin_decayed_oscillation(times, m, A, frequency, phi):
# write function here and return result
def lin_decayed_oscillation(times, m, A, frequency, phi):
return (m * times + A) * np.cos(frequency * times + phi)
# write your answer here
popt, pcov = curve_fit(lin_decayed_oscillation,
non_lin_traj[:3.0].index, non_lin_traj[:3.0].angle,
p0=(-0.1, 0.07, 2 * np.pi, 0.0))
fig, ax = plt.subplots(1, 1)
ax.plot(non_lin_traj.index, non_lin_traj.angle, '.')
best_fit_angle = lin_decayed_oscillation(non_lin_traj.index, popt[0], popt[1], popt[2], popt[3])
ax.plot(non_lin_traj.index, best_fit_angle)
ax.legend(['Data', 'Fit'])
ax.set_xlabel('Time [s]')
ax.set_ylabel('Angle [rad]')
Notice that there is a point in time where the pendulum stops oscillating. At this point the pendulum does not have enough kinetic energy to overcome the friction. We can look more closely at this by simulating at a higher sample rate.
Exercise
Add a measurement to the system that computes the friction torque based on the defintion at the beginning of the notebook. Assume that $T_N=1$ Newton-meter, $\mu=0.2$, and $\theta_0=2.0$ deg. Simulate the system and plot the angle, angular velocity, and friction torque in 3 subplots that share the same X axis (time).
def calculate_friction(coeff_of_friction, angle_vel):
return -coeff_of_friction * np.sign(angle_vel)
non_lin_sys.add_measurement('friction', calculate_friction)
non_lin_sys.constants['coeff_of_friction'] = 0.2 # Nm
non_lin_sys.coordinates['angle'] = np.deg2rad(2.0)
non_lin_traj = non_lin_sys.free_response(2.0, sample_rate=5000)
fig, axes = plt.subplots(3, 1, sharex=True)
axes[0].plot(non_lin_traj.index,
np.rad2deg(non_lin_traj.angle))
axes[0].set_ylabel('Angle [deg]')
axes[0].grid()
axes[1].plot(non_lin_traj.index,
np.rad2deg(non_lin_traj.angle_vel))
axes[1].set_ylabel('Angular Velocity [deg/s]')
axes[1].grid()
axes[2].plot(non_lin_traj.index,
non_lin_traj.friction)
axes[2].set_ylabel('Friction Torque [Nm]')
axes[2].set_xlabel('Time [s]')
axes[2].grid()
plt.tight_layout()
non_lin_traj.tail()
np.rad2deg(non_lin_traj.angle).tail()
Finally, visualize the animation with different friction coefficients to see the behavior.
non_lin_sys.coordinates['angle'] = np.deg2rad(5.0)
non_lin_traj = non_lin_sys.free_response(4.0, sample_rate=100)
ani = non_lin_sys.animate_configuration(interval=1000/100) # interval should be in milliseconds
from IPython.display import HTML, display
html = ani.to_jshtml()
display(HTML(html))