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