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)