#!/usr/bin/env python
# coding: utf-8
# Configure the plotting machinery
# In[1]:
get_ipython().run_line_magic('pylab', 'inline')
get_ipython().run_line_magic('config', "InlineBackend.figure_format = 'svg'")
import json
s = json.load( open("mplrc.json") )
matplotlib.rcParams.update(s)
matplotlib.rcParams['figure.figsize'] = 9,4
black="#404060" # plots containing "real black" elements look artificial
# Configure the rendering of this notebook with CSS
# In[2]:
from IPython.core.display import HTML
def css_styling():
styles = open("custom.css", "r").read()
return HTML(styles)
# css_styling()
# ## Response Analysis in the Frequency Domain
an Example
# ##### Samples
# In[3]:
figsize(9.0,1.1);
subplot(1,2,1);plot((0,1,3),(0,40,0));xticks((0,1,3)); yticks((0,40))
xlabel('t/s');ylabel('p(t)/kN');grid();
subplot(1,2,2);plot((0,1,3,8),(0,40,0,0));xticks((0,1,3,8)); yticks((0,40))
xlabel('t/s');ylabel('p(t)/kN');grid();
# We want to replicate the solution obtained for a triangular load using the _Duhamel Integral_.
#
# Our load is 3s long, but we need a stretch of zeros to damp out the response and _simulate_ rest initial conditions. We add zeros to the end of the load up to
# a total duration of 8s. The period of our loading is hence, rather arbitrarily,
# $T=8\,{}$s.
# In our exercise, we are free to choose the number of _samples per second_,
# so we chose 512 sps.
#
# How many samples are there? $N = 8\times512=4096$, note that $N$ is a power of 2.
# In[4]:
T = 8
sps = 512
print("The Nyquist frequency is ", sps/2.0,"Hz")
# ##### Load definition
# The _array_ `t` contains the times at which our signal was sampled, the load `p` is computed using the library function `where`, syntactically very similar to `IF` in a spreadsheet
# In[5]:
t = arange(0,T,1./sps)
p = where(t>3, 0, where(t<1, t*40000, 40000*(3-t)/2))
# Am I sure that the _list_ p contains the values of the loading?
#
# Let's try to plot p vs t...
# In[6]:
matplotlib.rcParams['figure.figsize'] = 9,4
plot(t, p, black) ; xlabel("t/s") ; ylabel("p(t)/N")
ylim((-5000,45000))
# It looks OK...
# ##### FFT of the loading
# Now, the fast Fourier transform of the sequence p is computed, and given a name, P.
#
# It is customary to denote Fourier pairs by the same letter, the small letter for the time domain representation and the capital letter for the frequency domain representation.
# In[7]:
P = fft.fft(p)
iP = fft.ifft(P)
# I have computed also the _inverse_ FFT of the FFT of the loading, naming it iP, it is a sequence of complex numbers and here we plot the real and the imaginary part of each component versus time.
# In[8]:
plot(t,real(iP),black,t,1*imag(iP),'y') ; xlabel("t/s") ; ylabel("p(t)/N") ;
# It seems OK...
# Next, we use a convenience function to compute a sequence of frequencies (in Hertz!) associated with the components of P, the FFT of p. The parameters are the number of points and the sampling interval..
#
# Note that the sequence of frequencies has a discontinuity when the Nyquist frequency
# is reached, i.e., the next frequency is the most negative one.
# In[9]:
f = fft.fftfreq(T*sps, 1./sps)
f, f[2046:2051]
# ##### Plots of P, the FFT of p
# The x axis is streching over the interval $-f_\text{Ny}$, $+f_\text{Ny}$
# In[10]:
plot(f,real(P), black, f,imag(P),'b')
xlim(-256,256) ; xticks(range(-256,257,64))
xlabel("f/Hz") ; ylabel("P(f)") ;
# The plot above is not much clear, because the frequency components are significantly different from zero only in a narrow range of frequencies around the origin.
# In the next 3 plots we zoom near the origin of the frequency axis to have a bit more of detail. There are 3 plots, first the absolute value of P vs f, then the real part and finally the imaginary part of P, versus f.
# In[11]:
plot(f,abs(P), black)
xticks(range(-4,5,2))
xlabel("f/Hz") ; ylabel("abs(P(f))")
xlim(-4,4) ; ylim(-0.2E7,3.3E7);
# Not afwully nice, this last plot...the baseline and the missing line
# at the left of the zero are artifacts, due do the particular sequence
# with which the positive and negative frequencies are arranged in the DFT output.
# To obviate these problems we can use the function `fftshift`, that reorders (shifts)
# the elements in an array such that the sequence goes from the most negative
# frequency to the most positive.
# In[12]:
plot(fftshift(f),fftshift(abs(P)), black)
axhline(0,color=black, linewidth=0.25)
xticks(range(-4,5,2))
xlabel("f/Hz") ; ylabel("abs(P(f))")
xlim(-4,4) ; ylim(-0.2E7,3.3E7);
# and now the other two plots I promised,
# In[13]:
plot(fftshift(f),fftshift(real(P)), black)
axhline(0,color=black, linewidth=0.25)
xticks(range(-4,5,2))
xlabel("f/Hz") ; ylabel("real(P(f))")
xlim(-4,4) ; ylim(-3.3E7,3.3E7);
# In[14]:
plot(fftshift(f),fftshift(imag(P)), black)
axhline(0,color=black, linewidth=0.25)
xticks(range(-4,5,2))
xlabel("f/Hz") ; ylabel("imag(P(f))")
xlim(-4,4) ; ylim(-3.3E7,3.3E7);
# ##### The response function
# Until now, we did without the SDOF, now it's time to describe it and derive its _response function_.
#
# All the parameters are the same as in the excel example, we compute k because we need it to normalize the response.
# In[15]:
z = 0.1; fn = 1/0.6 ; m =6E5 ; wn = fn*2*pi ; k = m*wn**2
def H(f):
b = f/fn
return 1./((1-b*b)+1j*(2*z*b))
# As usual, we plot the response function, or rather the absolute value of, against a short span of the frequency axis, centered about the origin, to show the details of the response function itself.
# In[16]:
plot(fftshift(f),fftshift(abs(H(f)))) ; xlabel("f/Hz") ; xlim(-8,8) ; ylabel("H(f)") ;
# ##### Computing the response
# The FFT of the response is computed multiplying, term by term, P by the response or _transfer_ function, then we compute the IFFT of X to obtain x, the _time domain representation_ of the response domain.
# In[17]:
X = [ P_*H(f_) for P_, f_ in zip(P,f)]
x = ifft(X)/k
# Note that the response function is _periodic_ with period $T=8\,{}$s.
# In the end, we remain with the task of plotting the response function, that is the real part of `x`. Just to be certain we plot also the imaginary part of `x`, so we can be sure that it is negligible with respect to the real part
# In[18]:
plot(t,1000*real(x))
xlabel("t/s"); ylabel(r"$\Re(x)/$mm");
axhline(0,color=black, linewidth=0.25)
show()
# In[19]:
plot(t,1E18*imag(x),linewidth=0.33)
axhline(0,color=black, linewidth=0.25)
xlabel("t/s"); ylabel(r"$\Im(x)/$am");
show()
# ##### The zero trail
# The importance of the zero trail to _adjust_ for initial rest condition cannot be
# underestimated. The length required depends, of course, on how much damped our system is,
# the lesser the damping, the longer the time required to damp out the response.
# Lets try to see what happens if we go from $\zeta=0.10$ to $\zeta=0.01$:
# In[20]:
z = 0.01
X = [ P_*H(f_) for P_, f_ in zip(P,f)]
x = ifft(X)/k
plot(t,1000*real(x))
xlabel("t/s"); ylabel(r"$\Re(x)/$mm");
axhline(0,color=black, linewidth=0.25)
show()