Skip to content

Commit 28fa537

Browse files
committed
SWT damping koda
Dodajam modul in dve .ipynb datoteki s pomočjo katerih sem generiral grafe za članek
1 parent 005da1e commit 28fa537

File tree

3 files changed

+638
-0
lines changed

3 files changed

+638
-0
lines changed

TF_rep_review.ipynb

Lines changed: 170 additions & 0 deletions
Large diffs are not rendered by default.

dampingid.py

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
2+
# coding: utf-8
3+
4+
from scipy import signal
5+
from scipy import special
6+
from scipy import stats
7+
import numpy as np
8+
9+
class wt_damping_id:
10+
def __init__(self, x, t, freq, eta, sig=1.):
11+
"""A class used for computation of (synchrosqueezed) continuous wavelet transform and damping identification
12+
13+
:param x: Signal, 1d array of time series. The same length as t.
14+
:param t: Time, 1d array at which each signal was recorded. The same length as x.
15+
:param freq: Frequencies, 1d array of frequencies for which the CWT or SWT is computed.
16+
:param eta: Frequency modulation parameter, float.
17+
:param sig: Parameter of Gaussian window width, float. If sig==1, Morlet wavelet is used, else Gabor wavelet is used.
18+
"""
19+
self.x = x
20+
self.t = t
21+
self.sig = sig
22+
self.eta = eta
23+
self.freq = freq
24+
self.res_frek = len(freq)
25+
self.res_cas = len(t)
26+
self.casvs = t[-1]-t[0]
27+
28+
def cwt(self):
29+
""" Computes contunous wavelet transform of the signal
30+
:return: CWT, 2d complex array, a CWT over different times and scales.
31+
"""
32+
gaus1=1./((self.sig**2*np.pi)**(0.25))
33+
pot=2.*self.sig**2
34+
def valcon(s):
35+
Psi=s**-0.5*gaus1*np.exp(-(-uar/s)**2./pot-1.j*self.eta*2.*np.pi*(-uar/s))
36+
return signal.fftconvolve(Psi,self.x,mode='same')
37+
uar=np.linspace(-self.casvs/2.,self.casvs/2.,self.res_cas)
38+
sar=self.eta/(self.freq)
39+
Q=np.zeros((self.res_frek,self.res_cas),dtype=complex)
40+
for i in range (0,self.res_frek):
41+
Q[i,:]=valcon(sar[i])
42+
Q /= self.res_cas
43+
self.Q = Q
44+
return Q
45+
46+
def prelimfreq(self):
47+
""" Computes preliminary frequencies without modification
48+
:return: Preliminary frequency for each point on the time-scale plane, 2d complex array
49+
"""
50+
try:
51+
self.Q
52+
except AttributeError:
53+
self.cwt()
54+
55+
res_cas1=1./(self.res_cas-1.)*self.casvs
56+
res_frek1=1./(self.res_frek-1.)*(self.freq[-1]-self.freq[0])*2.*np.pi
57+
gradW=np.gradient(self.Q, res_frek1, res_cas1)[1]
58+
omega=np.zeros(self.Q.shape,dtype=complex)
59+
for i in range (0,self.res_frek):
60+
for ii in range (0,self.res_cas-1):
61+
if abs(self.Q[i,ii])>1E-10:
62+
omega[i,ii]=-1.j*gradW[i,ii]/self.Q[i,ii]
63+
self.omega = omega
64+
return omega
65+
66+
def prelimfreq_scd(self):
67+
""" Computes preliminary frequencies with scale dependant modification
68+
:return: Preliminary frequency for each point on the time-scale plane, 2d complex array
69+
"""
70+
try:
71+
self.Q
72+
except AttributeError:
73+
self.cwt()
74+
75+
res_cas1=1./(self.res_cas-1.)*self.casvs
76+
res_frek1=1./(self.res_frek-1.)*(self.freq[-1]-self.freq[0])*2.*np.pi
77+
gradW=np.gradient(self.Q,res_frek1,res_cas1)[1]
78+
omega=np.zeros(self.Q.shape,dtype=complex)
79+
for i in range (0,self.res_frek):
80+
for ii in range (0,self.res_cas-1):
81+
if abs(self.Q[i,ii])>1E-10:
82+
omega[i,ii]=-1.j*gradW[i,ii]/self.Q[i,ii]
83+
84+
war = self.freq*2*np.pi
85+
frekmatrika = war[:,np.newaxis]*np.ones_like(omega)
86+
hkorekcijski=frekmatrika*self.casvs/(self.res_cas-1.)
87+
omega=omega*hkorekcijski/np.sin(hkorekcijski)
88+
self.omega = omega
89+
return omega
90+
91+
92+
def prelimfreq_shifted(self):
93+
""" Computes preliminary frequencies with shifted coefficient modification
94+
:return: Preliminary frequency for each point on the time-scale plane, 2d complex array
95+
"""
96+
try:
97+
self.Q
98+
except AttributeError:
99+
self.cwt()
100+
101+
res_cas1=1./(self.res_cas-1.)*self.casvs
102+
res_frek1=1./(self.res_frek-1.)*(self.freq[-1]-self.freq[0])*2.*np.pi
103+
gradW=np.gradient(self.Q,res_frek1,res_cas1)[1]
104+
omega=np.zeros(self.Q.shape,dtype=complex)
105+
for i in range (0,self.res_frek):
106+
for ii in range (0,self.res_cas-1):
107+
if abs(self.Q[i,ii])>1E-10:
108+
omega[i,ii]=-1.j*gradW[i,ii]/self.Q[i,ii]
109+
110+
korek=omega[i,ii]*self.casvs/(self.res_cas-1)
111+
omega[i,ii]=omega[i,ii]*korek/np.sin(korek)
112+
113+
self.omega = omega
114+
return omega
115+
116+
def prelimfreq_auto(self):
117+
""" Computes preliminary frequencies with autocorrelated modification
118+
:return: Preliminary frequency for each point on the time-scale plane, 2d complex array
119+
"""
120+
try:
121+
self.Q
122+
except AttributeError:
123+
self.cwt()
124+
125+
res_cas1=1./(self.res_cas-1.)*self.casvs
126+
res_frek1=1./(self.res_frek-1.)*(self.freq[-1]-self.freq[0])*2.*np.pi
127+
gradW=np.gradient(self.Q,res_frek1,res_cas1)[1]
128+
omega=np.zeros(self.Q.shape,dtype=complex)
129+
for i in range (0,self.res_frek):
130+
for ii in range (0,self.res_cas-1):
131+
if abs(self.Q[i,ii])>1E-10:
132+
omega[i,ii]=-1.j*gradW[i,ii]/self.Q[i,ii]
133+
134+
argument=np.abs(omega[i,ii])*self.casvs/(self.res_cas-1)
135+
if argument<=1:
136+
sinus=np.arcsin(argument)
137+
korek=sinus/np.sin(sinus)
138+
omega[i,ii]=omega[i,ii]*korek
139+
140+
self.omega = omega
141+
return omega
142+
143+
144+
def swt(self, omegacor=True):
145+
""" Computes the synchrosqueezed wavelet transform
146+
:param omegacor: If True, the autocorrelated modification is used to compute preliminary frequencies. If false, preliminary frequencies are used without modification
147+
:return: SWT, 2d complex array, a SWT over different times and frequencies.
148+
"""
149+
#Check to see whether Q has already been computed
150+
try:
151+
self.Q
152+
except AttributeError:
153+
self.cwt()
154+
if omegacor:
155+
self.prelimfreq_auto()
156+
else:
157+
self.prelimfreq()
158+
159+
delomega = (self.freq[11]-self.freq[10])*2*np.pi
160+
delomega2 = delomega/2
161+
sar=self.eta/self.freq
162+
sar23 = (self.eta/self.freq)**(2./3.)
163+
absomega=np.abs(self.omega)
164+
165+
temp=sar23[1:]*(sar[1:]-sar[:-1])
166+
T=np.zeros((self.res_frek,self.res_cas),dtype=complex)
167+
min_freq=np.min(self.freq)*2*np.pi
168+
max_freq=np.max(self.freq)*2*np.pi
169+
for ia in range (0,self.res_frek):
170+
omegal=self.freq[ia]
171+
for ib in range (0,self.res_cas):
172+
if min_freq<absomega[ia,ib]<max_freq:
173+
__,_ = min(enumerate(self.freq*2*np.pi), key=lambda x: abs(x[1]-absomega[ia,ib]))
174+
T[__,ib] += self.Q[ia,ib]*temp[ia-1]/delomega
175+
self.T = T
176+
return T
177+
178+
def swt_avg(self, omegacor=True):
179+
""" Computes the synchrosqueezed wavelet transform using the average SWT criterion
180+
:param omegacor: If True, the autocorrelated modification is used to compute preliminary frequencies. If false, preliminary frequencies are used without modification
181+
:return: SWT, 2d complex array, a SWT over different times and frequencies.
182+
"""
183+
if omegacor:
184+
self.prelimfreq_auto()
185+
else:
186+
self.prelimfreq()
187+
188+
sar=self.eta/self.freq
189+
delomega=(self.freq[11]-self.freq[10])*2.*np.pi
190+
delomega2=delomega/2
191+
sar23=sar**(2./3.)
192+
absomega=abs(self.omega)
193+
temp=sar23[1:]*(sar[1:]-sar[:-1])
194+
T=np.zeros((self.res_frek,self.res_cas),dtype=complex)
195+
avgomega=np.mean(absomega,axis=1)
196+
for ia in range (0,self.res_frek):
197+
omegal=self.freq[ia]*2*np.pi
198+
SUMA=np.zeros(self.res_cas)
199+
for k in range (1,self.res_frek):
200+
if abs(avgomega[k]-omegal)<=delomega2:
201+
SUMA=SUMA+self.Q[k,:]*temp[k-1]
202+
T[ia,:]=SUMA/delomega
203+
self.T_avg = T
204+
return self.T_avg
205+
206+
def swt_prop(self,omegacor=True):
207+
""" Computes the synchrosqueezed wavelet transform using the proportional SWT criterion
208+
:param omegacor: If True, the autocorrelated modification is used to compute preliminary frequencies. If false, preliminary frequencies are used without modification
209+
:return: SWT, 2d complex array, a SWT over different times and frequencies.
210+
"""
211+
if omegacor:
212+
self.prelimfreq_auto()
213+
else:
214+
self.prelimfreq()
215+
216+
sar=self.eta/self.freq
217+
delomega=(self.freq[11]-self.freq[10])*2.*np.pi
218+
delomega2=delomega/2
219+
sar23=sar**(2./3.)
220+
absomega=abs(self.omega)
221+
temp=sar23[1:]*(sar[1:]-sar[:-1])
222+
T=np.zeros((self.res_frek,self.res_cas),dtype=complex)
223+
kriterij=np.zeros((self.res_frek,self.res_cas,self.res_frek-1),dtype=np.bool)
224+
for ia in range (0,self.res_frek):
225+
omegal=self.freq[ia]*2*np.pi
226+
for ib in range (0,self.res_cas-1):
227+
for k in range (1,self.res_frek):
228+
if abs(absomega[k,ib]-omegal)<=delomega2:
229+
kriterij[ia,ib,k-1]=1
230+
vsota=np.sum(kriterij,1)
231+
for ia in range (0,self.res_frek):
232+
SUMA=np.zeros(self.res_cas)
233+
for k in range (0,self.res_frek-1):
234+
SUMA=SUMA+self.Q[k,:]*temp[k]*vsota[ia,k]/(self.res_cas-1.)
235+
T[ia,:]=SUMA/delomega
236+
self.T_prop = T
237+
return self.T_prop
238+
239+
def obrez(self, z1=10, z2=10):
240+
""" A function that cuts the edges of the transformed signals to avoid the edge effect
241+
:param z1: percentage of signal that is cutoff at the beginning
242+
:param z2: percentage of signal that is cut off at the end
243+
:return: Cut version of all the transforms that have been computed
244+
"""
245+
meja1=int(self.res_cas*z1/100)
246+
meja2=int(self.res_cas*(1-z2/100))
247+
try:
248+
self.Q
249+
self.Qcut=self.Q[:,meja1:meja2]
250+
except AttributeError:
251+
pass
252+
try:
253+
self.T
254+
self.Tcut=self.T[:,meja1:meja2]
255+
except AttributeError:
256+
pass
257+
try:
258+
self.T_avg
259+
self.T_avg_cut=self.T_avg[:,meja1:meja2]
260+
except AttributeError:
261+
pass
262+
try:
263+
self.T_prop
264+
self.T_prop_cut=self.T_prop[:,meja1:meja2]
265+
except AttributeError:
266+
pass
267+
self.t_cut=self.t[meja1:meja2]
268+
269+
def skel(self, M1, x01, width=20):
270+
""" A function that extracts a skeleton from a 2d array, by searching for a maximum of a absolute value within a band around x01.
271+
:param M1: 2d array from which the
272+
:param x01: center of the band
273+
:param width: One half of the width of the band
274+
:return: Skeleton, 2d array: absolute value on the ridge, zeroes everywhere else.
275+
"""
276+
M=abs(M1)
277+
(xlen,ylen)=M.shape
278+
skel=np.zeros((xlen,ylen))
279+
izhodisce=x01
280+
for y in range (0,ylen):
281+
indmax=np.argmax(M[izhodisce-width:izhodisce+width,y])
282+
skel[indmax+izhodisce-width,y]=M[indmax+izhodisce-width,y]
283+
return skel
284+
285+
def ident(self, trans, x01, width=20):
286+
""" Function that identifies damping.
287+
:param trans: Transformation to be used in the process, string. Either 'cwt', 'swt', 'swt_avg' or 'swt_prop'
288+
:param x01: center of the band where skeleton is searched for
289+
:param width: Width of band
290+
:return: identified frequency, damping ratio
291+
"""
292+
if trans=='cwt':
293+
self.cwt();
294+
self.obrez()
295+
M = self.skel(self.Qcut, x01, width)
296+
elif trans=='swt':
297+
self.swt();
298+
self.obrez()
299+
M = self.skel(self.Tcut, x01, width)
300+
elif trans=='swt_avg':
301+
self.swt_avg();
302+
self.obrez()
303+
M = self.skel(self.T_avg_cut, x01, width)
304+
elif trans=='swt_prop':
305+
self.swt_prop();
306+
self.obrez()
307+
M = self.skel(self.T_prop_cut, x01, width)
308+
else:
309+
raise Exception('Neprepoznna metoda')
310+
311+
frekident=self.freq[np.argmax(np.bincount(np.argmax(abs(M),0)))]
312+
smat = np.zeros(M.shape)
313+
for i in range (0,smat.shape[1]):
314+
smat[:,i]=self.eta/self.freq
315+
pomozna=(2.*abs(M)/((4*np.pi*self.sig**2*smat**2)**(0.25)))
316+
leva=np.log(np.amax(pomozna,0))
317+
leva[leva==-np.inf]=0
318+
leva[leva==0]=np.mean(leva)
319+
desna=-1*frekident*2*np.pi*self.t_cut
320+
DES = np.vstack([desna, np.ones(len(desna))]).T
321+
zeta_iden, lnAmp = np.linalg.lstsq(DES, leva)[0]
322+
return frekident, zeta_iden
323+
324+
325+
if __name__ == "__main__":
326+
t = np.linspace(0,2,2000)
327+
w = 20 #frequency
328+
z = 0.01 #damping ratio
329+
x = np.sin(2*np.pi*w*t)*np.exp(-2*np.pi*w*z*t)+(np.random.rand(len(t))-0.5)
330+
331+
frequencies = np.linspace(15,25,50)
332+
WT = wt_damping_id(x,t,frequencies,5)
333+
freq_id, z_id = WT.ident('swt_prop',25,10)
334+
print('Identified frequency: {0:4.1f}'.format(freq_id))
335+
print('Identified damping: {0:4.2}'.format(z_id))

0 commit comments

Comments
 (0)