Programa de Pós-Graduação em Engenharia Civil (PPGEC)
1. Introduction
2. The finite differences technique
2.1. Formulation
2.2. Example
3. The Duhamel's integral technique
3.1. Formulation
3.2. Example
4. Example: excitation recorded as file
5. Finite differences for nonlinear equations
6. Assignment
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
from MRPy import MRPy
Besides the basic conceptual solutions presented in the previous class, in practical cases where the external force F(t) is arbitrarily defined, the dynamic equilibrium equation must be numerically solved.
In this class we present two of the most important integration techniques:
There is also another useful technique that will be presented after we introduce the Fourier transform and the frequency domain analysis of linear systems, in the next classes. For the moment we can state that time domain techniques are mainly used for transient loads, while frequency domain techniques are maily used for periodic or stationary random loads.
Although the central finite differences technique may be used to solve the dynamic equilibrium equation of linear systems, its most important application is in the solution of nonlinear systems. For linear systems it allows the integration scheme to be explicit, what means that the system state (displacement) in future can be explicitly stated as a function of system state in the past. To understand this concept, let us discretize the time domain as ti=iΔt, where Δt is the so-called time step. This is depicted in the figure below:
Consequently, both the sistem response, u(t), as well as the external load, F(t), can be now expressed at the discrete time instants, ti, as:
ui=u(ti)Fi=F(ti)From this definitions, the system velocity, ˙u(t), and acceleration, ¨u(t), can be approximated through a central finite differences scheme as:
˙ui=12Δt(ui+1−ui−1)¨ui=1Δt2(ui+1−2ui+ui−1)Considering that the dynamic equilibrium equation holds also for a generic time instant ti:
¨ui+2ζωn˙ui+ω2nui=Fimthe discrete kinematic parameters ˙ui and ¨ui can be replaced to give:
1Δt2(ui+1−2ui+ui−1)+2ζωn2Δt(ui+1−ui−1)+ω2nui=FimBy isolating the future system displacement, ui+1, it results:
ui+i=1β1(Fim+β2ui−1−β3ui)with:
β1=1Δt(ζωn+1Δt)β2=1Δt(ζωn−1Δt)β3=1Δt(Δtω2n−2Δt)which is an explicit expression of the system response at time ti+1 as a function of system responses at times ti−1 and ti. The initial position, u0, and initial velocity, v0=˙u0, must be provided for the calculation of system responses at instants t0 and t1. The position at t0 is the provided value u0 itself, while the position at t1 can be calculated with:
u1=u0+v0Δt+F0Δt22The finite differences technique presents a severe restriction for the (arbitrarily chosen) time step, Δt:
Δt≤Tn4ou fS≥4Tn
Whenever this restriction is not respected, the integration scheme will diverge and become unstable, leading to increasingly large response. Furthermore, as one tries to increase Δt to reduce computational time, the technique loose accuracy quite fast. For these reasons, Duhamel's technique presented in the next section is usually preferable for solving linear systems.
MRPy
¶The formulation above is implemented as a method for the MRPy
class, as exemplified
in the following example with a unit step loading.
Firstly, it is necessary to define the mechanical properties of the sdof system. Let us assume that:
m = 1.0 # system mass in kg
fn = 1.0 # natural frequency in Hz
zt = 0.02 # damping as ratio of critical
u0 = 0. # initial displacement (m)
v0 = 0. # initial velocity (m/s)
F0 = 2. # external force magnitude (N)
Once the system properties are specified, the unit step loading can be created as a MRPy
instance by calling the appropriate constructor:
H = F0*MRPy.Heaviside(NX=1, N=128, t0=2.5, fs=1/0.4)
t = H.t_axis()
plt.figure(1, figsize=(10, 4), clear=True)
plt.plot(t, H[0])
plt.xlim(0, H.Td); plt.xlabel('time (s)')
plt.ylim(-1., 3.); plt.ylabel('Force (N)')
plt.grid(True)
H.printAttrib()
fs = 2.5Hz Td = 51.2s NX = 1 N = 128 M = 65
Observe that the number of time steps N
and the total duration Td
will define the
sampling frequency fs
and the time step will be Δt=1/fs.
Smaller values of N
will then worse the integration accuracy for a given Td
.
The solution by finite differences is then available through the sdof_fdiff
method:
u_FD = H.sdof_fdiff(fn, zt, u0, v0)/m
t = u_FD.t_axis()
plt.figure(2, figsize=(10, 4), clear=True)
plt.plot(t, -u_FD[0])
plt.xlim(0., u_FD.Td); plt.xlabel('time (s)')
plt.ylim(-0.5, 0.1); plt.ylabel('displacement (m)')
plt.grid(True)
It can be clearly observed that, after the step onset, the system will oscillate with decreasing amplitude around the static response. This static displacement can be calculated directly from system stiffness:
k = m*(2*np.pi*fn)**2 # system stiffness from frequency formula (w^2 = k/m)
u_st = F0/k # displacement for maximum applied force (u = F/k)
print('Static displacement = {0:0.3f}m'.format(u_st))
Static displacement = 0.051m
Let us see now what happens whenever the time step is too long in comparison to the system natural period of vibration.
Tn = 1/fn # natural period in seconds
dt = Tn/10 # very coarse time step (explore this!!!)
N = int(H.Td/dt) # new length for the Heaviside's excitation
H2 = MRPy.Heaviside(NX=1, N=N, t0=2.5, Td=20)
u2 = H2.sdof_fdiff(fn, zt, u0, v0)/m
t2 = H2.t_axis()
plt.figure(3, figsize=(10, 6), clear=True)
plt.subplot(2,1,1)
plt.plot(t2, H2[0]);
plt.xlim(0, H2.Td); plt.xlabel('time (s)')
plt.ylim(-0.5, 1.5); plt.ylabel('Force (N)')
plt.grid(True)
plt.subplot(2,1,2)
plt.plot(t2, -u2[0]);
plt.xlim( 0.0, u2.Td); plt.xlabel('time (s)')
plt.ylim(-0.05, 0.01); plt.ylabel('displacement (m)')
plt.grid(True)
Since we choose Δt<Tn, the solution has diverged!
To make full use of the Jupyter notebook concept, try now changing the fraction of Tn used to define the time step in the example above. What is the worst acceptable accuracy?
The numerical solution by the Duhamel's integral technique is restricted to linear systems. It relies on the superposition of system responses to a sequence on impulses F(τ)dτ. It has been seen that the general solution of the equilibrium equation of a sdof system subjected to a general load F(t) is given by the convolution of this load with the impulse response:
u(t)=u0(t)+1mωD∫t0exp[−ζωn(t−τ)]sinωD(t−τ)F(τ)dτwhere u0(t) is the system response to the initial conditions. Considering now the trigonometric identity:
sinωD(t−τ)=sinωDtsinωDτ−cosωDtcosωDτand disregarding for a moment the initial conditions leads to:
u(t)=1mωD[A(t)sinωDt−B(t)cosωDtexp(−ζωnt)]with:
A(t)=∫t0exp(−ζωnτ)cosωDτF(τ)dτB(t)=∫t0exp(−ζωnτ)sinωDτF(τ)dτThe reformulations above suggest that a recursive scheme can be used to spare computational time spent in the calculations of trigonometric functions. By using the discretized time τi=iΔτ gives the following discrete evaluations:
ei=exp(−ζωnτi)si=sinωDτici=cosωDτiFi=F(τi)The functions Ai=A(ti) and Bi=B(ti) can be readily calculated as cumulative summations:
Ai=Δτi∑j=0ejcjFjBi=Δτi∑j=0ejsjFjand the solution is finally obtained as:
ui=u0i+1mωD(Aisi−Biciei)with the response to the initial conditions (displacement u0 and velocity v0) from:
u0i=1ei[u0ci+(v0+u0ζωnωD)si]This is a quite fast computational scheme, for Python can build de time vector and
evaluate de functions through built-in numpy
commands, including the cumulative summation.
Furthermore, the Duhamel technique does not become unstable no matter how coarse the
chosen time step is, although its accuracy may not be ensured in this case.
MRPy
¶The formulation above is also implemented as a method for the MRPy
class, as exemplified
in the following.
The same system properties and unit step loading from previous example are used.
The class method is:
H = F0*MRPy.Heaviside(NX=1, N= 128, t0=2.5, fs=1/0.4)
t = H.t_axis()
u_DH = H.sdof_Duhamel(fn, zt, u0, v0)/m
t = u_DH.t_axis()
plt.figure(4, figsize=(10, 4), clear=True)
plt.plot(t, -u_DH[0])
plt.xlim(0, u_DH.Td); plt.xlabel('time (s)')
plt.ylim(-0.5, 0.1); plt.ylabel('displacement (m)')
plt.grid(True)
Tn = 1/fn # natural period in seconds
dt = Tn/3 # very coarse time step on the verge of instability (explore this!!!)
N = int(H.Td/dt) # new length for the Heaviside's excitation
H2 = MRPy.Heaviside(NX=1, N=N, t0=2, Td=10)
u2 = H2.sdof_Duhamel(fn, zt, u0, v0)/m
t2 = H2.t_axis()
plt.figure(5, figsize=(8, 6), clear=True)
plt.subplot(2,1,1)
plt.plot(t2, H2[0]);
plt.xlim(0, H2.Td); plt.xlabel('time (s)')
plt.ylim(-0.5, 1.5); plt.ylabel('Force (N)')
plt.grid(True)
plt.subplot(2,1,2)
plt.plot(t2, -u2[0]);
plt.xlim(0, u2.Td); plt.xlabel('time (s)')
plt.ylim(-0.02, 0.08); plt.ylabel('displacement (m)')
plt.grid(True)
On the contrary of the diverged solution by finite differences, even by choosing Δt<Tn does not cause the solution to diverge.
The MRPy
class provides a constructor that reads a time series from file.
There are some formatting options and others can be included as needed.
In this example we will read a file recorded with the cell phone app iNVH
by Bosch.
The file has a .csv format with four columns, the first being the sampling time and
the next three being the accelerations along the three measurement axes (x, y and z).
Reading the file is a straighforward calling to the from_file
constructor:
data = MRPy.from_file('resources/data/iNVH001', form='invh').zero_mean()
t = data.t_axis()
plt.figure(6, figsize=(8, 8), clear=True)
for kX, acc in enumerate(data):
plt.subplot(3, 1 ,kX+1)
plt.plot(t, acc)
plt.xlim(0, data.Td);
plt.ylim(-15, 15);
plt.ylabel('acc{0} (m/s^2)'.format(kX+1))
plt.grid(True)
plt.xlabel('time (s)');
This data was obtained at the center of a metallic beam subjected to multiple impacts. We are interested in the acceleration along the z axis, so it can be isolated from the complete set with:
az = MRPy(data[2], data.fs).double()
t = az.t_axis()
plt.figure(7, figsize=(8, 4), clear=True)
plt.plot(t, az[0])
plt.xlim(0, az.Td); plt.xlabel('time (s)')
plt.ylim(-10, 10); plt.ylabel('a_z (m/s^2)')
plt.grid(True)
Now this acceleration will be applied as a basis excitation to a sdof system with the same properties as defined in the previous sections. The solutions by finite differences and Duhamel are compared:
u_FD = az.sdof_fdiff (fn, zt, u0, v0)
u_DH = az.sdof_Duhamel(fn, zt, u0, v0)
plt.figure(8, figsize=(10, 4), clear=True)
plt.plot(t, u_FD[0], 'r', t, u_DH[0], 'b')
#plt.xlim(0, az.Td); plt.xlabel('time (s)')
#plt.ylim(-0.015, 0.015); plt.ylabel('displacement (m)')
plt.grid(True)
It seems that both techniques yield almos the same results, so let us take a look at the error:
err = u_FD - u_DH
plt.figure(9, figsize=(8, 4), clear=True)
plt.plot(t, err[0], 'r')
plt.xlim(0, az.Td); plt.xlabel('time (s)')
plt.ylim(-0.001, 0.001); plt.ylabel('displ. error (m)')
plt.grid(True)
This means that, for the provided sampling rate, the integration error is very small.
MRPy.sdof_FDiff()
e MRPy.sdof_Duhamel()
.from scipy.interpolate import interp1d
a = MRPy.from_file('resources/data/earthquake', form='columns')
func = interp1d(a.t_axis(), a[0])
Td = a.Td # duração do sinal é a mesma
dt = 0.03 # passo de tempo proposto
ti = np.linspace(0, Td, int(Td/dt)) # reduz resolução, aumenta dt
fsi = len(ti)/Td # nova taxa de aquisição
ai = MRPy(func(ti), fsi) # aceleração interpolada
ai = ai.double()
f10 = a.plot_time(10, figsize=(8,4)) # aceleração original
f11 = ai.plot_time(11, figsize=(8,4)) # resolução alterada
ai.printAttrib()
fs = 33.3Hz Td = 64.0s NX = 1 N = 2132 M = 1067