In this notebook we will find the vibrational modes of a simple model of a building. We will assume that the mass of the floors are much more than the mass of the walls and that the lateral stiffness of the walls can be modeled by a simple linear spring. We will investigate how the building may vibrate under initial conditions that could be caused by a gust of wind and during ground vibration.
from IPython.display import YouTubeVideo
YouTubeVideo('g0cz-oDfUg0', width=600)
YouTubeVideo('hSwjkG3nv1c', width=600)
YouTubeVideo('kzVvd4Dk6sw', width=600)
import numpy as np
import matplotlib.pyplot as plt
from resonance.linear_systems import FourStoryBuildingSystem
This gives a bit nicer printing of large NumPy arrays.
np.set_printoptions(precision=5, linewidth=100, suppress=True)
%matplotlib inline
sys = FourStoryBuildingSystem()
sys.constants
sys.coordinates
sys.plot_configuration();
traj = sys.free_response(30, sample_rate=10)
traj[list(sys.coordinates.keys())].plot(subplots=True);
sys.animate_configuration(fps=10)
M, C, K = sys.canonical_coefficients()
M
C
K
The system can be normalized by the mass matrix and transformed into a symmetric eigenvalue problem by introducing the new coordinate vector:
$$\mathbf{q}=\mathbf{L}^T\mathbf{x}$$$\mathbf{L}$ is the Cholesky decomposition of the symmetric mass matrix, i.e. $\mathbf{M}=\mathbf{L}\mathbf{L}^T$.
The equation of motion becomes:
$$\ddot{\mathbf{q}} + \tilde{\mathbf{K}} \mathbf{q} = 0$$Compute $\tilde{\mathbf{K}}$.
L = np.linalg.cholesky(M)
L
M**0.5
import numpy.linalg as la
from numpy.linalg import inv
K_tilde = inv(L) @ K @ inv(L.T)
K_tilde
Notice that $\tilde{\mathbf{K}}$ is symmetric, so we are guaranteed to get real eigenvalues and orthogonal eigenvectors when solving this system.
Find the eigenvalues and eigenvectors. Create the spectral matrix $\mathbf{\Lambda}$ and the matrix $P$ which contains the orthonormal eigenvectors of $\tilde{\mathbf{K}}$.
$$ \mathbf{P} = \left[ \mathbf{v}_1, \ldots, \mathbf{v}_4 \right] $$evals, evecs = np.linalg.eig(K_tilde)
evals
evecs
Lambda = np.diag(evals)
Lambda
P = evecs
Prove that the eigenvectors in $\mathbf{P}$ are orthonormal.
np.dot(P[:, 0], P[:, 1])
np.linalg.norm(P[:, 0])
P[:, 0].T @ P[:, 1]
P[:, 0].T @ P[:, 0]
An orthonormal matrix has the property that its transpose multiplied by itself is the identity matrix.
P.T @ P
Find the natural freqencies of the system in both radians per second and Hertz, store them in an array in the order of the eigenvalues with names ws and fs.
ws = np.sqrt(evals)
ws
fs = ws / 2 / np.pi
fs
Transform the eigenvectors back into the coordinate system associated with $\mathbf{x}$.
$$ \mathbf{S} = \left[ \mathbf{u}_1, \ldots, \mathbf{u}_4 \right] $$S = np.linalg.inv(L.T) @ P
S
sys.coordinates
The eigenmodes (mode shapes) are contained in each column of $\mathbf{S}$. Create a plot for each mode shape with these specifications:
S[:, 0]
np.hstack((0, S[:, 0]))
u1 = S[:, 0]
u1
u1[::-1]
S[:, 2]
fig, axes = plt.subplots(1, 4)
for i in range(4):
axes[i].plot(np.hstack((0, S[:, i])), [0, 3, 6, 9, 12], marker='o')
axes[i].set_title('{:1.2f} Hz'.format(fs[i]))
plt.tight_layout()
fs[0]
S[:, 0]
sys.coordinates['x1'] = S[0, 2]
sys.coordinates['x2'] = S[1, 2]
sys.coordinates['x3'] = S[2, 2]
sys.coordinates['x4'] = S[3, 2]
traj = sys.free_response(30, sample_rate=10)
traj[list(sys.coordinates.keys())].plot(subplots=True)
sys.animate_configuration(fps=10)
The trajectory of building's coordinates can be found with:
$$ \mathbf{x}(t) = \sum_{i=1}^n c_i \sin(\omega_i t + \phi_i) \mathbf{u}_i $$where
$$ \phi_i = \arctan \frac{\omega_i \mathbf{v}_i^T \mathbf{q}_0}{\mathbf{v}_i^T \dot{\mathbf{q}}_0} $$and
$$ c_i = \frac{\mathbf{v}^T_i \mathbf{q}_0}{\sin\phi_i} $$$c_i$ are the modal participation factors and reflect what proportion of each mode is excited given specific initial conditions. If the initial conditions are the eigenmode, $\mathbf{u}_i$, the all but the $i$th $c_i$ will be zero.
Show that if $\mathbf{q}_0 = \mathbf{v}_i$ then $c_i = 1$ all other modal participation factors are 0. Also, report all of the phase angles, $\phi_i$, in degrees.
for i in range(4):
x0 = S[:, i]
xd0 = np.zeros(4)
print(x0)
q0 = L.T @ x0
qd0 = L.T @ xd0
phis = np.arctan2(ws * P.T @ q0, P.T @ xd0)
print(np.rad2deg(phis))
cs = P.T @ q0 / np.sin(phis)
print(cs)
print('=' * 40)
Create a function called simulate() that returns the trajectories of the coordinates given an array of monotonically increasing time values and the initial conditions of the system.
It should look like:
def simulate(t, x0, xd0):
"""Returns the state trajectory.
Parameters
==========
t : ndarray, shape(m,)
Monotonic values of time.
x0 : ndarray, shape(n,)
The initial conditions of each coordinate.
xd0 : ndarray, shape(n,)
The initial conditions of each speed.
Returns
=======
x : ndarray, shape(m, n)
The trajectories of each state.
"""
# your code here
return x
def simulate(t, x0, xd0):
q0 = L.T @ x0
qd0 = L.T @ xd0
phis = np.arctan2(ws * P.T @ q0, P.T @ xd0)
cs = P.T @ q0 / np.sin(phis)
x = np.zeros((len(x0), len(t)))
for ci, wi, phii, ui in zip(cs, ws, phis, S.T):
x += ci * np.sin(wi * t + phii) * np.tile(ui, (len(t), 1)).T
return x
Using the plotting function below, show that the results found here are the same as the simulations from the FourStoryBuildingSystem given the same initial conditions.
def plot_trajectories(t, x):
fig, axes = plt.subplots(4, 1)
for i, ax in enumerate(axes.flatten()):
ax.plot(t, x[i])
ax.set_ylabel(r'$x_{}$ [m]'.format(i + 1))
ax.set_xlabel('Time [s]')
plt.tight_layout()
t = np.linspace(0, 50, num=50 * 60)
x0 = np.array([0.001, 0.010, 0.020, 0.025])
xd0 = np.zeros(4)
x = simulate(t, x0, xd0)
plot_trajectories(t, x)
This shows the plot of a single mode:
x = simulate(t, S[:, 0], np.zeros(4))
plot_trajectories(t, x)