#!/usr/bin/env python
# coding: utf-8
#
Mass-Spring-Damper System with Direct Force Input
# MCHE 485: Mechanical Vibrations
# Dr. Joshua Vaughan
# joshua.vaughan@louisiana.edu
# http://www.ucs.louisiana.edu/~jev9637/
#
#
# Figure 1: A Direct Force Mass-Spring-Damper System
#
#
# This notebook simluates the free vibration of a simple mass-spring-damper system like the one shown in Figure 1. More specifically, we'll look at how system response to non-zero initial conditions.
#
# The equation of motion for the system is:
#
# $ \quad m \ddot{x} + c \dot{x} + kx = f $
#
# We could also rewrite this equation by dividing by the mass, $m$.
#
# $ \quad \ddot{x} + \frac{c}{m}\dot{x} + \frac{k}{m}x = \frac{f}{m}$
#
# or equivalently:
#
# $ \quad \ddot{x} = - \frac{k}{m}x - \frac{c}{m}\dot{x} + \frac{f}{m}$
#
# For information on how to obtain this equation, you can see the lectures at the [class website](http://www.ucs.louisiana.edu/~jev9637/MCHE485.html).
# To simluate this system using a numerical solver, we need to rewrite this equation of motion, which is a 2nd-order differential equation, as a system of 1st-order differential equations.
#
# To do so, define a state vector as $ \bar{w} = \left[x \ \dot{x}\right]^T $. We can also define an input vector as $\bar{u} = \left[f \right]$. Since we only have one input to this system, the input vector only has one element.
#
# Then, the system of first-order ODEs we have to solve is:
#
# $ \quad \dot{\bar{w}} = g(\bar{w}, \bar{u}, t) $
#
# Writing these out, we have:
#
# $ \quad \dot{\bar{w}} = \left[\dot{x} \right.$
#
# $\phantom{\quad \dot{\bar{w}} = \left[\right.}\left. -\frac{k}{m}x - \frac{c}{m}\dot{x} - \frac{f}{m}\right] $
#
# Now, we can use that system of 1st-order differential equations to simulate the system.
#
# To begin we will import the NumPy library, the matplotlib plotting library, and the ```odeint``` ODE solver from the SciPy library.
# In[1]:
import numpy as np # Grab all of the NumPy functions with nickname np
# In[2]:
get_ipython().run_line_magic('matplotlib', 'inline')
# Import the plotting functions
import matplotlib.pyplot as plt
# In[3]:
# Import the ODE solver
from scipy.integrate import odeint
# We need to define two functions for the differential equation solver to use. The first is just the system of differential equations to solve. I've defined it below by ```eq_of_motion()```. It is just the system of equations we wrote above. The second is the input force as a function of time. I have called it ```f()``` below. Here, it is just a pulse input in force, lasting 0.5 second.
# In[4]:
def eq_of_motion(w, t, p):
"""
Defines the differential equations for the direct-force mass-spring-damper system.
Arguments:
w : vector of the state variables:
t : time
p : vector of the parameters:
"""
x, x_dot = w
m, k, c, StartTime, F_amp = p
# Create sysODE = (x', x_dot')
sysODE = [x_dot,
-k/m * x - c/m * x_dot + f(t, p)/m]
return sysODE
def f(t, p):
"""
defines the disturbance force input to the system
"""
m, k, c, StartTime, F_amp = p
# Select one of the two inputs below
# Be sure to comment out the one you're not using
# Input Option 1:
# Just a step in force beginning at t=DistStart
# f = F_amp * (t >= DistStart)
# Input Option 2:
# A pulse in force beginning at t=StartTime and ending at t=(StartTime + 0.5)
f = F_amp * (t >= StartTime) * (t <= StartTime + 0.5)
return f
# In[5]:
# Define the System Parameters
m = 1.0 # kg
k = (2.0 * np.pi)**2 # N/m (Selected to give an undamped natrual frequency of 1Hz)
wn = np.sqrt(k / m) # Natural Frequency (rad/s)
z = 0.1 # Define a desired damping ratio
c = 2 * z * wn * m # calculate the damping coeff. to create it (N/(m/s))
# In[6]:
# Set up simulation parameters
# ODE solver parameters
abserr = 1.0e-9
relerr = 1.0e-9
max_step = 0.01
stoptime = 10.0
numpoints = 10001
# Create the time samples for the output of the ODE solver
t = np.linspace(0.0, stoptime, numpoints)
# Initial conditions
x_init = 0.0 # initial position
x_dot_init = 0.0 # initial velocity
# Set up the parameters for the input function
StartTime = 0.5 # Time the f(t) input will begin
F_amp = 10.0 # Amplitude of Disturbance force (N)
# Pack the parameters and initial conditions into arrays
p = [m, k, c, StartTime, F_amp]
x0 = [x_init, x_dot_init]
# Now, we will actually call the ode solver, using the ```odeint()``` function from the SciPy library. For more information on ```odeint```, see [the SciPy documentation](http://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html).
# In[7]:
# Call the ODE solver.
resp = odeint(eq_of_motion, x0, t, args=(p,), atol=abserr, rtol=relerr, hmax=max_step)
# The solver returns the time history of each state. To plot an individual response, we just simply pick the corresponding column. Below, we'll plot the position of the mass as a function of time.
# In[8]:
# Make the figure pretty, then plot the results
# "pretty" parameters selected based on pdf output, not screen output
# Many of these setting could also be made default by the .matplotlibrc file
# Set the plot size - 3x2 aspect ratio is best
fig = plt.figure(figsize=(6, 4))
ax = plt.gca()
plt.subplots_adjust(bottom=0.17, left=0.17, top=0.96, right=0.96)
# Change the axis units to serif
plt.setp(ax.get_ymajorticklabels(),family='serif',fontsize=18)
plt.setp(ax.get_xmajorticklabels(),family='serif',fontsize=18)
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# Turn on the plot grid and set appropriate linestyle and color
ax.grid(True,linestyle=':',color='0.75')
ax.set_axisbelow(True)
# Define the X and Y axis labels
plt.xlabel('Time (s)', family='serif', fontsize=22, weight='bold', labelpad=5)
plt.ylabel('Position', family='serif', fontsize=22, weight='bold', labelpad=10)
# Plot the first element of resp for all time. It corresponds to the position.
plt.plot(t, resp[:,0], linewidth=2, linestyle = '-', label=r'Response')
# uncomment below and set limits if needed
# xlim(0,5)
# ylim(0,10)
# # Create the legend, then fix the fontsize
# leg = plt.legend(loc='upper right', fancybox=True)
# ltext = leg.get_texts()
# plt.setp(ltext,family='serif',fontsize=18)
# Adjust the page layout filling the page using the new tight_layout command
plt.tight_layout(pad = 0.5)
# save the figure as a high-res pdf in the current folder
# It's saved at the original 6x4 size
# plt.savefig('MCHE485_DirectForcePulseWithDamping.pdf')
fig.set_size_inches(9, 6) # Resize the figure for better display in the notebook
#
# #### Licenses
# Code is licensed under a 3-clause BSD-style license. See the licenses/LICENSE.md file.
#
# Other content is provided under a [Creative Commons Attribution-NonCommercial 4.0 International License](http://creativecommons.org/licenses/by-nc/4.0/), CC-BY-NC 4.0.
# In[9]:
# This cell will just improve the styling of the notebook
from IPython.core.display import HTML
import urllib.request
response = urllib.request.urlopen("https://cl.ly/1B1y452Z1d35")
HTML(response.read().decode("utf-8"))