Programa de Pós-Graduação em Engenharia Civil (PPGEC)
1. The Rayleigh quotient
2. The damping matrix
3. Modal superposition
4. Application: seimic response
5. Number of modes to be considered
6. Modal analysis for static response
7. Static equivalent loads
Prof. Marcelo M. Rocha, Dr.techn. (ORCID)
Porto Alegre, RS, Brazil
# Importing Python modules required for this notebook
# (this cell must be executed with "shift+enter" before any other Python cell)
import numpy as np
import matplotlib.pyplot as plt
import pickle as pk
from MRPy import MRPy
# Load matrices generated in Class 10 and 11 (those notebooks must be run firstly!)
with open('resources/data/sample_KM.pk', 'rb') as target: # mass and stiffness matrices
K1, M1, K2, M2, K3, M3 = pk.load(target)
with open('resources/data/sample_VM.pk', 'rb') as target: # modal properties
wk1, Phi1, wk2, Phi2, wk3, Phi3 = pk.load(target)
In a previous class we have seen that for a discrete system the potential elastic and the kinetic energy can be given in matrix notation as:
V=12→u⊺K→uT=12˙→u⊺M˙→uThese expressions are valid both for each (finite) element, as for the complete system as well. Now we recall the free vibration response in the k-th vibration mode and the corresponding velocity:
→uk(t)=uk0sin(ωkt+θk)→φk→˙uk(t)=ωkuk0cos(ωkt+θk)→φkDisregarding any energy dissipation (damping), both energy forms, V and T, must have the same maximum amplitude occurring whenever the sine or the cosine function peaks at unity:
VmaxTmax=u2k0(→φ⊺kK→φk)ω2ku2k0(→φ⊺kM→φk)=1This means that:
ω2k=VmaxT∗maxwhere T∗max is called reference kinetic energy, which is the kinetic energy calculated with displacements instead of velocities.
The equation above is called Rayleigh Quotient, and the equality holds only for free vibration at a given
mode k, whatever the vibration amplitude uk0 is.
However, it can be shown that any displacement vector, →u, that minimizes the quotient
above IS a vibration mode.
This means that by making an educated guess about →uk for mode →φk and calculating
the corresponding energies, Vmax and T∗max, will provide an estimation for ωk
that is equal or larger the correct value.
In the following example, we use the deformed shape for a central load calculated with FTool for the plane truss (example 1) in Class 10. For the applied load of 1.0kN, the deformed shape is (the central line of the flexibility matrix):
uk = 1e-3*np.array([[0.1303, 0.2325, 0.2833, 0.2325, 0.1303]]).T # initial guess for eigenvector (m)
The maximum potential elastic energy can be calculated as the work done by external forces, while the maximum reference kinetic energy is calculated with the (lumped) mass matrix:
Vmax = 1000*uk[2,0]/2 # work done by central load 1.0kN
Tref = np.dot(uk.T, np.dot(M1, uk))[0,0]/2 # reference kinetic with mass matrix
# Eigenvalue estimation
fk = np.sqrt(Vmax/Tref)/2/np.pi
f1 = wk1[0]/(2*np.pi)
print('From Rayleigh quotient: {0:4.2f}Hz'.format(fk))
print('Calculated from eigenproblem: {0:4.2f}Hz'.format(f1))
print('Estimation error: {0:4.2f}% '.format(100*(fk - f1)/f1))
From Rayleigh quotient: 4.02Hz Calculated from eigenproblem: 3.94Hz Estimation error: 2.02%
This shows that the Rayleigh quotient may provide a very good estimation of natural frequencies whenever a convincing modal shape is assumed and the corresponding system stiffness is available.
So far the natural frequencies and the corresponding modal shapes have been calculated without considering any energy dissipation. It is however necessary to introduce a damping model, and we shall keep all the mathematical convenience of viscous damping, that implies a reactive force proportional to velocity. In matrix notation the damped equilibrium equation is:
M¨→u+C˙→u+K→u=→F(t)In the next section we introduce the modal superposition method for solving linear systems subjected to forced vibration. The method take advantage of the mode orthogonality with respect to both mass and stiffness matrices. To keep this advantage for damped systems, the damping matrix must also respect the orthogonality condition, what is a quite restrictive requirement. The most practical approach for ensuring this orthogonality is defining the damping matrix as a linear combination of mass and stiffness:
C=α0M+α1KThis means that:
Ck=α0(→φ⊺kM→φk)+α1(→φ⊺kK→φk)=α0Mk+α1Kkwhere Ck, Mk and Kk are called modal damping, modal mass and modal stiffness, respectivelly. Now we observe that the same definitions used for a one degree of freedom system can be applied to these modal properties:
Ck=2ζkMkωk=α0Mk+α1Mkω2kDividing all terms by 2Mkωk gives the modal damping ratio:
ζk=α0(12ωk)+α1(ωk2)what means the modal damping can be estimated directly from the modal frequency, such that once two pairs (ζi,ωi) and (ζj,ωj) are specified, the complete damping matrix is defined from the solution of an equation system for α0 and α1.
As an example, let us take the truss above and specify a damping ratio of 1% for the second and the fourth modes. The calculation is done as shown below:
# Calculate the combination coefficients
wki = wk1[2]; zti = 0.010
wkj = wk1[4]; ztj = 0.010
alpha = np.linalg.solve([[1/(2*wki), wki/2],
[1/(2*wkj), wkj/2]], [zti, ztj])
# Define damping as functions of frequency
ztM = lambda fk: alpha[0]/(4*np.pi*fk) # only M proportional
ztK = lambda fk: alpha[1]*np.pi*fk # only K proportional
ztMK = lambda fk: ztM(fk) + ztK(fk) # mixed M and K model
# Visualize damping ratios for all vibration modes
fk1 = wk1/(2*np.pi)
fk = np.linspace(0.1, 60, 200)
plt.figure(1, figsize=(8,4))
plt.plot(fk, ztMK(fk ), 'b' , lw=2.0)
plt.plot(fk, ztM (fk ), 'g:', lw=1.5)
plt.plot(fk, ztK (fk ), 'g:', lw=1.5)
plt.plot(fk1, ztMK(fk1), 'ro')
plt.xlabel('Natural frequency (Hz)')
plt.ylabel('Damping ratio of critical (nondim)')
plt.axis([0, 60, 0, 0.05])
plt.grid(True)
One can conclude that damping ratios for all further modes are consequence of the specification of pairs (ζi,ωi) and (ζj,ωj), which must be wisely chosen. Frequencies inside the interval will present lower, outside higher damping ratios.
There are other techniques for assembling a damping matrix that presents mathematical convenience, but any solution beyond the simple one presented above will result in complex eigenvalues and eigenvectors and will not be explored in this course.
Mk = np.dot(Phi1.T,np.dot(M1, Phi1))
q1 = Phi1[:,0]
print(q1)
for k in range(5):
Phi1[:,k] = Phi1[:,k] / np.sqrt(Mk[k,k])
q1 = Phi1[:,0]
print(q1)
Mk = np.dot(Phi1.T,np.dot(M1, Phi1))
print(np.diag(Mk))
[0.2901579 0.4993386 0.5770084 0.4993386 0.2901579] [0.00648813 0.01116555 0.0129023 0.01116555 0.00648813] [1. 1. 1. 1. 1.]
We have seen how to solve the undamped matrix equilibrium equation for initial conditions →u0 and →v0. Now we will present the most used method for solving the damped equilibrium for any given load vector →F(t):
M¨→u+C˙→u+K→u=→F(t)We start by recalling the time-space separation assumption:
→u(t)=N∑k=1uk(t)→φk=Φ→uk(t)which has led to the modal properties (ωk,→φk). Replacing this assumption in the matrix equilibrium equation and pre-multiplying by Φ⊺ gives:
(Φ⊺MΦ)¨→uk+(Φ⊺CΦ)˙→uk+(Φ⊺KΦ)→uk=Φ⊺→F(t)The orthogonality of matrix Φ (the orthogonality of modal shapes →ϕk) leads to a decouple of the matrix equation, which becomes a system of N scalar equations:
(→φ⊺kM→φk)¨uk+(→φ⊺kC→φk)˙uk+(→φ⊺kK→φk)uk=→φ⊺k→F(t),k=1,2,…,Nwhere Mk=→ϕ⊺kM→ϕk, Ck=→ϕ⊺kC→ϕk, and Kk=→ϕ⊺kK→ϕk, are the so-called modal mass, modal damping and modal stiffness, respectively. The well known relation Kk=ω2kMk still holds. Observe that the modal damping definitions is only possible if the orthogonality is forced as, for instance, shown in the previous section.
Each dynamic equilibrium scalar equation:
Mk¨uk+Ck˙uk+Kkuk=Fk(t)or
¨uk+2ζkωk˙uk+ω2kuk=Fk(t)/Mkis the equation of a single degree of freedom system subjected to the modal load:
Fk(t)=→ϕ⊺k→F(t)It is very important to realize that the concept of degree of freedom in the equation above does not refer to a the classical definition, rather to a more broad generalized definition. Here, a degree of freedom is the scale to be applied to each modal shape.
After the N scalar equations are solved, for instance by some numerical method like the Duhamel integral, the nodal displacements can be recomposed and the structure can be designed for the maximum strains or stresses resulting from the dynamic load, with the ressonant response duly taken into account.
As an example of application, let us apply a base acceleration in the 3-dof model described in the previous classes. We shall then analyse the amplitude of peak response to this seismic loading.
m(¨u+¨uG)+c˙u+ku=0Hence:
m¨u+c˙u+ku=−maGFirstly we load the acceleration record and build the load vector, which is simply the ground acceleration multiplied by the mass of each model mass:
aG = MRPy.from_file('resources/data/earthquake', form='columns')
FG = np.dot(M3, np.tile(aG[0], (3,1)))
FG = MRPy(-FG, fs=aG.fs)
f2 = FG.plot_time(2, figsize=(8,6), axis_t=[0, aG.Td, -0.2, 0.2])
Be aware that these are NODAL forces. The next step is to move the problem to modal space. To have a orthogonal damping matrix we specify 1% damping for modes 1 and 3:
wki = wk3[0]; zti = 0.01
wkj = wk3[1]; ztj = 0.01
alpha = np.linalg.solve([[1/(2*wki), wki/2],
[1/(2*wkj), wkj/2]], [zti, ztj])
Then we calculate the modal parameters and the modal forces:
fk = wk3/(2*np.pi)
zk = ztMK(fk)
print(zk)
Mk = np.diag(np.dot(Phi3.T, np.dot(M3, Phi3)))
Kk = Mk*(wk3**2)
Fk = MRPy(np.dot(Phi3.T, FG), fs=FG.fs)
f3 = Fk.plot_time(3, figsize=(8,6), axis_t=[0, aG.Td, -0.4, 0.4])
print(Mk)
[0.01 0.01 0.0124698] [0.33 0.33 0.33]
Again, be aware that these are MODAL forces.
The solution in modal space can be calculates using Duhamel, but first
the modal loads must be divided by the modal masses according to
the convention used for the sdof_Duhamel()
method.
# Mass division must be a matrix operation...
ak = MRPy(np.dot(np.diag(1/Mk), Fk), fs=Fk.fs)
# ... and now solving
uk = ak.sdof_Duhamel(fk, zk) # modal space solution
utot = MRPy(np.dot(Phi3, uk), fs=uk.fs) # nodal solution
#f4 = uk.plot_time(4, figsize=(8,6), axis_t=[0, utot.Td/2, -0.002, 0.002])
f5 = utot.plot_time(4, figsize=(8,6), axis_t=[0, utot.Td/2, -0.002, 0.002])
upk = utot.max(axis=1)
print(upk)
[0.00150316 0.00121104 0.00067584]
We can observe that the system response occurs mainly in the first mode.
In the last section we have calculated the seismic response by superposing all three modes obtained from the 3-dof numerical model. However, the main advantage of modal superposition is exactly the possibility of truncating the summation:
→u(t)=M∑k=1uk(t)→φkto a number M<N of relevant modes. In the previous example, if we had kept only the first mode (M=1) the result would be as shown below. The error between considering all three modes and considering only the first mode is plotted.
utot1 = np.zeros(uk.shape) # allocating space for nodal response
qk1 = Phi3[:,0] # retrieve only first vibration mode
for k in range(3):
utot1[k,:] = uk[0]*qk1[k] # nodal response for each d.o.f.
err = MRPy(utot - utot1, uk.fs) # error as a time series
f5 = err.plot_time(5, figsize=(8,6), axis_t=[0, utot.Td/2, -0.0002, 0.0002])
The result above shows that considering only the first vibration mode (in the given example) will lead to a quite accurate result, with neglectable error in comparison to the total response amplitude.
To decide what is the truncation M<N suitable for any structural system, let us recall the dynamic equilibrium equation:
M¨→u+C˙→u+K→u=→F(t)where we assume that the mass matrix is of lumped type (only diagonal elements are not zero) and the external force is defined as:
→F(t)=−M→raG(t)The column vector →r (containing direction cosines) addresses the translational degrees of freedom that will receive the inertial load, according to the direction of ground acceleration. Rotational degrees of freedom will be addressed with zero. For instance, in the 3-dof example the vector →r contains only 1's, for all three structural nodes are subjected to aG(t) (ground acceleration is assumed to be aligned to the three degrees of freedom).
Applying modal decomposition to the matrix equation yields:
Mk¨uk+Ck˙uk+Kkuk=−(→φ⊺kM→r)aG(t)Now we defined a modal seismic load factor, a scalar quantity, as:
Lk=→φ⊺kM→rand divide the whole equation by the modal mass Mk:
¨uk+2ζkωk˙uk+ω2kuk=−(LkMk)aG(t)It can be shown that:
Mtot=N∑k=1L2kMkwhere Mtot is the system total mass (sum of all diagonal elements of M). Finally we define a weightning factor, Wk, such that:
Wk=L2k/MkMtotwhich does not depend on the ground acceleration aG(t), but depends on the addressing vector →r and on the modal shapes →φk. The number of modes to be considered for truncation, M, must be such that the factors Wk add up closely enough to unity.
For instance, let us calculate the weightning factors for the three modes of the previous example.
r3 = np.array([1, 1, 1]) # addressing vector
Lk = np.dot(np.dot(Phi3.T, M3), r3) # modal factors
Mk = np.diag(np.dot(np.dot(Phi3.T, M3), Phi3)) # modal masses
Mtot = np.sum(np.diag(M3)) # total mass
Wk = (Lk**2)/Mk/Mtot # weightning factors
print('Weightning factor mode 1: {0:5.4f}'.format(Wk[0]))
print('Weightning factor mode 2: {0:5.4f}'.format(Wk[1]))
print('Weightning factor mode 3: {0:5.4f}'.format(Wk[2]))
Weightning factor mode 1: 0.9141 Weightning factor mode 2: 0.0749 Weightning factor mode 3: 0.0110
This results explains why the first mode alone is capable of satisfactorily representing the complete system response. However, never forget that this analysis depends on the addressing vector →r, that distributes the load among the degrees of freedom, as well as on the the modal shapes themselves.
It can be shown that the system flexibility matrix (the inverse of the system stiffness matrix) can be reconstituted from vibration modes as:
H=K−1=N∑k=1→φk→φ⊺kω2kMkObserve that each mode contributes to the full matrix as the product →φk→φ⊺k has dimensions N×N. Recall also that the product ω2kMk is the modal stiffness, Kk for mode k.
If the summation in the equation above is truncated to only M terms, the flexibility matrix K−1 will be ill-conditioned and cannot be inverted to give the stiffness matrix, K. It can, however, be used to calculate an approximate static response to any given load vector as:
→u=K−1→FAs an example of application, we will calculate the vertical displacement of the central node in the plane truss subjected to self weight using only the first modal shape. Recall that this result has been previously calculated with Ftool as 20.48mm, while the same result using the condensed stiffness matrix was 20.18mm.
The first vibration mode is plotted below:
with open('resources/data/sample_VM.pk', 'rb') as target: # modal properties
wk1, Phi1, wk2, Phi2, wk3, Phi3 = pk.load(target)
f6 = plt.figure(6, figsize=(8,2))
qk = np.zeros(7)
qk[1:-1] = Phi1[:,0] # include supports for plotting
x = np.arange(0, 14, 2)
#print(Phi1[:,0]/np.max(Phi1[:,0]))
#Phi1[:,0] = np.array([0.5, 0.75, 1, 0.5, 0.75])
plt.plot(x, qk/np.max(np.abs(qk))) # normalize by unity
plt.axis([0, 12, -0.5, 1.5])
plt.text(10, 1, 'fk = {0:3.1f}Hz'.format(fk1[0]));
plt.grid(True)
Now we calculate modal properties and build up the (approximated) stiffness matrix and the load vector:
H1 = np.zeros(K1.shape)
for k in range(1): # choose how many modes to consider
wk = wk1[k] # fundamental frequency
qk = Phi1[:,k] # first mode
Mk = np.sum(qk*qk*np.diag(M1)) # modal mass (simplified calculation)
Kk = wk*wk*Mk # modal stiffness
qk = qk.reshape(5,1) # prepare for matrix multiplication
H1 += np.dot(qk,qk.T)/Kk # approximated flexibility matrix
F = 20000*np.ones((5,1)) # load vector (two nodes grouped)
Finally, the static displacement vector is calculated:
u = np.matmul(H1, F)
print('Maximum displacement at truss center: {0:5.2f}'.format(1000*u[2,0]))
Maximum displacement at truss center: 20.32
We conclude that the displacement error at truss center is only 0.7% with respect to the solution with the condensed K matrix, which is an amazing result for such a simplification level (only one degree of freedom).
The dynamic response of a structure with N degrees of freedom must be used for designing the structural elements according to the maximum stresses developed along time. This is not a simple task, for structural displacements may be quite complex multivariate time series. A simple approach for finding the time instant when the largest stresses happen may be monitoring the total potential elastic energy stored in the structure, which is easily calculated as:
V=12→u⊺K→uIf now we replace the modal decomposition for displacements,
V(t)=12N∑k=1u2k(t)→φ⊺kK→φkand recognize that →φ⊺kK→φk is the modal stiffness, Kk=ω2kMk, we finally get:
V(t)=12N∑k=1ω2kMku2k(t)which is a simple (always positive) sum of scalar time series. By searching for time tmax where V(t) reaches a maximum, we finally get the displaced geometry associated to maximum stresses:
→umax=N∑k=1uk(tmax)→φkThe sum above may be obviously truncated according to the required accuracy. Once the design displacement is available, there is a static equivalent load that may be specified for the structural designer, which represents a virtual static load that causes the structure to be subjected to the same maximum deformation occurring along the dynamic response.
→Feq=K→umax=N∑k=1uk(tmax)K→φkBut again we recall the eigenvalue problem:
K→φk=ω2kM→φkand the equivalent load can be writen as:
→Feq=N∑k=1ω2kuk(tmax)M→φkIt is important to observe that there may be several deformed configurations, or several peak for time series V(t), leading to relevant loading conditions.
As an example of application, let us go back to the seismic response analysis in section 4 and specify an static equivalent load for the structural response. Firstly we take a look at the instantaneous elastic potential energy.
Mk = np.diag(np.dot(np.dot(Phi3.T, M3), Phi3)) # modal masses
Kk = wk3*wk3*Mk # modal stiffnesses
V = MRPy(uk*uk/2, fs=uk.fs).superpose(Kk) # total elastic energy
f7 = V.plot_time(7, figsize=(8,4), axis_t=(0, uk.Td/2, 0, 0.001))
(3,)
Then we look for the moment when V(t) reaches a peak:
imax = np.argmax(V[0])
tmax = imax/uk.fs
umax = uk[:,imax] # uk(tmax)
print('Time instant with maximum potential energy: {0:6.2f}s.'.format(tmax))
Time instant with maximum potential energy: 10.47s.
Finally, the static equivalent load is calculated:
Mqk = np.dot(M3, Phi3)
Feq = np.zeros_like(umax)
#print(umax, umax.shape, Feq)
for k in range(3):
Feq += wk3[k]*wk3[k]*umax[k]*Mqk[:,k]
ueq = np.linalg.solve(K3,Feq)
print(Feq)
print(ueq)
[-0.61147849 -0.48939257 -0.3031919 ] [-0.00161579 -0.00129876 -0.00072798]
a = np.array([1,2,3,4,5,6])[::-1]
print(a)
[6 5 4 3 2 1]
Apresentar uma estimativa da frequência fundamental através do quociente de Rayleigh.
Apresentar a resposta a um impulso unitário (estruturas horizontais: impulso vertical no centro; estruturas verticais: impulso horizontal no topo). Apresente o deslocamento no tempo para o nó que recebeu o impulso e o respectivo periodograma.
Calcular a força estática equivalente para a resposta ao impulso unitário e conferir no Ftool.