wawi 0.0.1__py3-none-any.whl → 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wawi/__init__.py +8 -4
- wawi/fe.py +134 -0
- wawi/general.py +468 -0
- wawi/identification.py +66 -0
- wawi/io.py +719 -0
- wawi/modal.py +608 -0
- wawi/plot.py +569 -0
- wawi/prob.py +9 -0
- wawi/random.py +38 -0
- wawi/signal.py +45 -0
- wawi/structural.py +278 -0
- wawi/time_domain.py +126 -0
- wawi/tools.py +7 -0
- wawi/wave.py +491 -0
- wawi/wind.py +1109 -0
- wawi/wind_code.py +14 -0
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/METADATA +7 -6
- wawi-0.0.5.dist-info/RECORD +21 -0
- wawi-0.0.1.dist-info/RECORD +0 -6
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/LICENSE +0 -0
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/WHEEL +0 -0
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/top_level.txt +0 -0
wawi/modal.py
ADDED
@@ -0,0 +1,608 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from .tools import print_progress as pp
|
3
|
+
from scipy.interpolate import interp1d
|
4
|
+
|
5
|
+
def maxreal(phi):
|
6
|
+
"""
|
7
|
+
Rotate complex vectors (stacked column-wise) such that the absolute values of the real parts are maximized.
|
8
|
+
|
9
|
+
Arguments
|
10
|
+
---------------------------
|
11
|
+
phi : double
|
12
|
+
complex-valued modal transformation matrix (column-wise stacked mode shapes)
|
13
|
+
|
14
|
+
Returns
|
15
|
+
---------------------------
|
16
|
+
phi_max_real : boolean
|
17
|
+
complex-valued modal transformation matrix, with vectors rotated to have maximum real parts
|
18
|
+
"""
|
19
|
+
|
20
|
+
angles = np.expand_dims(np.arange(0,np.pi/2, 0.01), axis=0)
|
21
|
+
phi_max_real = np.zeros(np.shape(phi)).astype('complex')
|
22
|
+
for mode in range(0,np.shape(phi)[1]):
|
23
|
+
rot_mode = np.dot(np.expand_dims(phi[:, mode], axis=1), np.exp(angles*1j))
|
24
|
+
max_angle_ix = np.argmax(np.sum(np.real(rot_mode)**2,axis=0), axis=0)
|
25
|
+
|
26
|
+
phi_max_real[:, mode] = phi[:, mode] * np.exp(angles[0, max_angle_ix]*1j)*np.sign(sum(np.real(phi[:, mode])))
|
27
|
+
|
28
|
+
return phi_max_real
|
29
|
+
|
30
|
+
def get_mode_sort(lambd, order_fun_dict=None, return_as_dict=True, remove_conjugates=['dynamic'], sort=['dynamic']):
|
31
|
+
order_fun_dict_std = {
|
32
|
+
'dynamic': lambda l: np.logical_and(np.imag(l)!=0, np.real(l)<0),
|
33
|
+
'undamped':lambda l: np.real(l)==0,
|
34
|
+
'unstable': lambda l: np.logical_and(np.imag(l)!=0, np.real(l)>0),
|
35
|
+
'overdamped': lambda l: np.logical_and(np.imag(l)==0, np.real(l)<0)
|
36
|
+
}
|
37
|
+
|
38
|
+
if order_fun_dict is None:
|
39
|
+
order_fun_dict = order_fun_dict_std
|
40
|
+
elif type(order_fun_dict) is list: #specify certain terms from standard dict defined
|
41
|
+
order_fun_dict = {key:order_fun_dict_std[key] for key in order_fun_dict}
|
42
|
+
|
43
|
+
ix_dict = dict()
|
44
|
+
|
45
|
+
for key, fun in order_fun_dict.items():
|
46
|
+
ix = np.where(fun(lambd))[0]
|
47
|
+
|
48
|
+
if key in sort:
|
49
|
+
ix2 = np.argsort(np.abs(lambd[ix]))
|
50
|
+
ix = ix[ix2]
|
51
|
+
|
52
|
+
if remove_conjugates:
|
53
|
+
ix = ix[::2]
|
54
|
+
|
55
|
+
ix_dict[key] = ix
|
56
|
+
|
57
|
+
|
58
|
+
if return_as_dict:
|
59
|
+
return ix_dict
|
60
|
+
else:
|
61
|
+
return np.hstack(list(ix_dict.values()))
|
62
|
+
|
63
|
+
def sort_modes(lambd, phi=None, order_fun_dict=None, return_as_dict=True, remove_conjugates=['dynamic'], sort=['dynamic']):
|
64
|
+
ix_dict = get_mode_sort(lambd, order_fun_dict=order_fun_dict, return_as_dict=True, remove_conjugates=['dynamic'], sort=['dynamic'])
|
65
|
+
lambd_dict = dict()
|
66
|
+
|
67
|
+
if phi is not None:
|
68
|
+
phi_dict = dict()
|
69
|
+
|
70
|
+
for key in ix_dict:
|
71
|
+
ix = ix_dict[key]
|
72
|
+
lambd_dict[key] = lambd[ix]
|
73
|
+
if phi is not None:
|
74
|
+
phi_dict[key] = phi[:, ix]
|
75
|
+
|
76
|
+
if not return_as_dict:
|
77
|
+
lambd_list = [lambdi for lambdi in list(lambd_dict.values()) if lambdi.size!=0]
|
78
|
+
lambd_sorted = np.hstack([lambd_list])
|
79
|
+
|
80
|
+
if phi is not None:
|
81
|
+
phi_list = [phii for phii in list(phi_dict.values()) if phii.size!=0]
|
82
|
+
phi_sorted = np.hstack(phi_list)
|
83
|
+
else:
|
84
|
+
phi_sorted = None
|
85
|
+
return lambd_sorted, phi_sorted
|
86
|
+
else:
|
87
|
+
return lambd_dict, phi_dict
|
88
|
+
|
89
|
+
|
90
|
+
def statespace(K, C, M):
|
91
|
+
ndofs = np.shape(K)[0]
|
92
|
+
A = np.zeros([2*ndofs, 2*ndofs])
|
93
|
+
A[0:ndofs, ndofs:2*ndofs] = np.eye(ndofs)
|
94
|
+
A[ndofs:2*ndofs, 0:ndofs] = -np.linalg.inv(M) @ K
|
95
|
+
A[ndofs:2*ndofs, ndofs:2*ndofs] = -np.linalg.inv(M) @ C
|
96
|
+
|
97
|
+
return A
|
98
|
+
|
99
|
+
|
100
|
+
def iteig(K, C, M, omega=None, omega_ref=0, input_functions=True, itmax=None, tol=None, keep_full=False,
|
101
|
+
mac_min=0.9, w_initial=None, normalize=False, print_progress=False, print_warnings=True,
|
102
|
+
track_by_psi=True, remove_velocity=True, divergence_protection=True):
|
103
|
+
|
104
|
+
mean_w = False
|
105
|
+
|
106
|
+
if itmax is None:
|
107
|
+
itmax = 15
|
108
|
+
|
109
|
+
if tol is None:
|
110
|
+
tol = 1e-4
|
111
|
+
|
112
|
+
|
113
|
+
if not input_functions:
|
114
|
+
K = interp1d(omega, K, kind='quadratic', fill_value='extrapolate')
|
115
|
+
C = interp1d(omega, C, kind='quadratic', fill_value='extrapolate')
|
116
|
+
M = interp1d(omega, M, kind='quadratic', fill_value='extrapolate')
|
117
|
+
|
118
|
+
ndofs = K(0).shape[0]
|
119
|
+
lambd = np.zeros([2*ndofs], dtype=complex)
|
120
|
+
q = np.zeros([2*ndofs, 2*ndofs], dtype=complex)
|
121
|
+
|
122
|
+
# Reference phi
|
123
|
+
A = statespace(K(omega_ref), C(omega_ref), M(omega_ref))
|
124
|
+
lambd_ref, q_ref = np.linalg.eig(A)
|
125
|
+
not_converged = []
|
126
|
+
omega_ref = np.abs(np.imag(lambd_ref))
|
127
|
+
|
128
|
+
if w_initial is None:
|
129
|
+
w_initial = np.sort(np.abs(np.imag(lambd_ref)))
|
130
|
+
|
131
|
+
for m in range(2*ndofs):
|
132
|
+
w = w_initial[m]
|
133
|
+
|
134
|
+
wprev = np.inf
|
135
|
+
wprev2 = -np.inf
|
136
|
+
|
137
|
+
phi = q[:,0]*0
|
138
|
+
phiprev = 0*phi
|
139
|
+
phiprev2 = 0*phi
|
140
|
+
|
141
|
+
q_ref_m = q_ref[:, m:m+1]
|
142
|
+
|
143
|
+
for i in range(0, itmax):
|
144
|
+
A = statespace(K(w), C(w), M(w))
|
145
|
+
lambdai, qi = np.linalg.eig(A)
|
146
|
+
|
147
|
+
if track_by_psi:
|
148
|
+
macs = xmacmat(qi, phi2=q_ref_m, conjugates=False)
|
149
|
+
m_ix = np.argmax(macs, axis=0)[0]
|
150
|
+
|
151
|
+
phi = qi[:, m_ix:m_ix+1]
|
152
|
+
lambdai = lambdai[m_ix]
|
153
|
+
else:
|
154
|
+
phi = qi[:, m:m+1]
|
155
|
+
lambdai = lambdai[m]
|
156
|
+
|
157
|
+
w = abs(np.imag(lambdai))
|
158
|
+
if mean_w:
|
159
|
+
w = (wprev+w)/2
|
160
|
+
|
161
|
+
if w==0:
|
162
|
+
mean_w = False
|
163
|
+
break
|
164
|
+
elif np.abs(w - wprev2)/w <= tol and np.abs(w - wprev)/w <= tol and mac(phi, phiprev)>=mac_min and mac(phi, phiprev2)>=mac_min: # Converged!
|
165
|
+
mean_w = False
|
166
|
+
break
|
167
|
+
elif np.abs(w - wprev2)/w <= tol and np.abs(w - wprev)/w > tol and mac(phi, phiprev)<mac_min and mac(phi, phiprev2)>=mac_min:
|
168
|
+
if divergence_protection:
|
169
|
+
mean_w = True
|
170
|
+
extra_message = 'Divergence protection active - attempting to continue with average values'
|
171
|
+
else:
|
172
|
+
extra_message = ''
|
173
|
+
if print_warnings:
|
174
|
+
print(f'Warning: Suspecting oscillatory divergence on iteration of mode {int(np.ceil(m/2))}. Please conduct checks.')
|
175
|
+
print(f'Re(lambd) = {np.real(lambdai):.2f}, Im(lambd) = {np.imag(lambdai):.2f}%')
|
176
|
+
print(extra_message)
|
177
|
+
|
178
|
+
wprev2 = wprev*1
|
179
|
+
wprev = w*1
|
180
|
+
|
181
|
+
phiprev2 = phiprev*1
|
182
|
+
phiprev = phi*1
|
183
|
+
|
184
|
+
if i is itmax-1:
|
185
|
+
if print_warnings:
|
186
|
+
print(f'Warning: Maximum number of iterations ({i+1}) performed on mode {int(np.ceil(m/2))}. Storing last value.')
|
187
|
+
print(f'Last three damped natural frequencies: {wprev2:.2f}, {wprev:.2f}, {w:.2f} rad/s')
|
188
|
+
print(f'Corresponding automacs (3,1) and (3,2): {xmacmat(phi, phiprev2):.1f}, {xmacmat(phi, phiprev):.1f}')
|
189
|
+
not_converged.append(int(m/2))
|
190
|
+
|
191
|
+
lambd[m] = lambdai
|
192
|
+
q[:, m] = phi[:,0]
|
193
|
+
|
194
|
+
if print_progress:
|
195
|
+
pp(m+2,2*ndofs, sym='>', postfix=' finished with iterative modal analysis.')
|
196
|
+
|
197
|
+
if print_progress:
|
198
|
+
print(' ')
|
199
|
+
|
200
|
+
if remove_velocity:
|
201
|
+
q = q[:ndofs, :]
|
202
|
+
|
203
|
+
if not keep_full:
|
204
|
+
lambd = lambd[::2]
|
205
|
+
q = q[:, ::2]
|
206
|
+
|
207
|
+
if normalize==True:
|
208
|
+
for mode in range(0, np.shape(q)[1]):
|
209
|
+
q[:, mode] = q[:, mode]/max(abs(q[:, mode]))
|
210
|
+
|
211
|
+
# Sort
|
212
|
+
ix = np.argsort(np.abs(lambd))
|
213
|
+
lambd = lambd[ix]
|
214
|
+
q = q[:,ix]
|
215
|
+
|
216
|
+
return lambd, q, not_converged
|
217
|
+
|
218
|
+
|
219
|
+
def iteig_naive(K, C, M, itmax=None, tol=1e-4):
|
220
|
+
|
221
|
+
if itmax is None:
|
222
|
+
itmax = 15
|
223
|
+
|
224
|
+
if tol is None:
|
225
|
+
tol = 1e-4
|
226
|
+
|
227
|
+
ndofs = K(0).shape[0]
|
228
|
+
lambd = np.zeros([2*ndofs], dtype=complex)
|
229
|
+
q = np.zeros([2*ndofs, 2*ndofs], dtype=complex)
|
230
|
+
|
231
|
+
for m in range(0, 2*ndofs, 2):
|
232
|
+
w = 0.0
|
233
|
+
wprev = np.inf
|
234
|
+
|
235
|
+
for i in range(0, itmax):
|
236
|
+
A = statespace(K(w), C(w), M(w))
|
237
|
+
lambdai, qi = np.linalg.eig(A)
|
238
|
+
ix = np.argsort(np.abs(lambdai))
|
239
|
+
lambdai = lambdai[ix][m]
|
240
|
+
qi = qi[:, ix][:, m]
|
241
|
+
|
242
|
+
w = abs(-np.imag(lambdai))
|
243
|
+
|
244
|
+
if np.abs(w - wprev) <= tol:
|
245
|
+
break
|
246
|
+
|
247
|
+
wprev = w*1
|
248
|
+
|
249
|
+
lambd[m] = lambdai
|
250
|
+
q[:, m] = qi
|
251
|
+
|
252
|
+
return lambd, q
|
253
|
+
|
254
|
+
|
255
|
+
def iteig_freq(K, C, M, omega=None, itmax=15, reference_omega=0, input_functions=True,
|
256
|
+
tol=1e-4, keep_full=False, mac_min=0.98, w_initial=None,
|
257
|
+
normalize=False, print_progress=False, print_warnings=True, divergence_protection=True):
|
258
|
+
|
259
|
+
if not input_functions:
|
260
|
+
K = interp1d(omega, K, kind='quadratic', fill_value='extrapolate')
|
261
|
+
C = interp1d(omega, C, kind='quadratic', fill_value='extrapolate')
|
262
|
+
M = interp1d(omega, M, kind='quadratic', fill_value='extrapolate')
|
263
|
+
|
264
|
+
if divergence_protection:
|
265
|
+
extra_message = '[Divergence protection active]'
|
266
|
+
else:
|
267
|
+
extra_message = ''
|
268
|
+
|
269
|
+
ndofs = K(0).shape[0]
|
270
|
+
|
271
|
+
lambd = np.zeros([2*ndofs], dtype=complex)
|
272
|
+
q = np.zeros([2*ndofs, 2*ndofs], dtype=complex)
|
273
|
+
|
274
|
+
not_converged = []
|
275
|
+
|
276
|
+
if not w_initial:
|
277
|
+
w_initial = np.zeros([ndofs])
|
278
|
+
|
279
|
+
for m in range(0, 2*ndofs):
|
280
|
+
w = w_initial[int(m/2)]
|
281
|
+
|
282
|
+
wprev = np.inf
|
283
|
+
wprev2 = -np.inf
|
284
|
+
|
285
|
+
phi = q[:,0]*0
|
286
|
+
phiprev = 0*phi
|
287
|
+
phiprev2 = 0*phi
|
288
|
+
|
289
|
+
for i in range(0, itmax):
|
290
|
+
A = statespace(K(w), C(w), M(w))
|
291
|
+
|
292
|
+
lambdai, qi = np.linalg.eig(A)
|
293
|
+
sortix = np.argsort(np.imag(lambdai))
|
294
|
+
|
295
|
+
qi = qi[:, sortix]
|
296
|
+
lambdai = lambdai[sortix]
|
297
|
+
|
298
|
+
w = abs(np.imag(lambdai[m]))
|
299
|
+
phi = qi[:, m]
|
300
|
+
|
301
|
+
if np.abs(w - wprev2) <= tol and np.abs(w - wprev) <= tol and mac(phi, phiprev)>=mac_min and mac(phi, phiprev2)>=mac_min: # Converged!
|
302
|
+
break
|
303
|
+
elif np.abs(w - wprev2) <= tol and np.abs(w - wprev) > tol and mac(phi, phiprev)<mac_min and mac(phi, phiprev2)>=mac_min:
|
304
|
+
if divergence_protection:
|
305
|
+
w = (w+wprev)/2
|
306
|
+
if print_warnings:
|
307
|
+
print(f'Oscillatory divergence, mode {np.ceil(m/2):.0f} omega = {w:.2f}, xi = {100*-np.real(lambdai[m])/np.abs(lambdai[m]):.1f} % {extra_message}')
|
308
|
+
|
309
|
+
wprev2 = wprev*1
|
310
|
+
wprev = w*1
|
311
|
+
|
312
|
+
phiprev2 = phiprev*1
|
313
|
+
phiprev = phi*1
|
314
|
+
|
315
|
+
if i is itmax-1:
|
316
|
+
if print_warnings:
|
317
|
+
print('** Maximum number of iterations (%i) performed on mode %i. Storing last value.' % (i+1, np.ceil(m/2)))
|
318
|
+
# print('Last three damped natural frequencies: %f, %f, %f rad/s' % (wprev2, wprev, w))
|
319
|
+
# print('Corresponding automacs (3,1) and (3,2): %f, %f' % (xmacmat(phi, phiprev2), xmacmat(phi, phiprev)))
|
320
|
+
not_converged.append(int(m/2))
|
321
|
+
|
322
|
+
lambd[m] = lambdai[m]
|
323
|
+
q[:, m] = qi[:, m]
|
324
|
+
|
325
|
+
if print_progress:
|
326
|
+
pp(m+2,2*ndofs, sym='>', postfix=' finished with iterative modal analysis.')
|
327
|
+
|
328
|
+
if print_progress:
|
329
|
+
print(' ')
|
330
|
+
|
331
|
+
if not keep_full:
|
332
|
+
q = q[0:ndofs, :]
|
333
|
+
lambd = lambd[0::2]
|
334
|
+
q = q[:, 0::2]
|
335
|
+
else:
|
336
|
+
lambd[1::2] = np.conj(lambd[0::2])
|
337
|
+
q[:, 1::2] = np.conj(q[:,0::2])
|
338
|
+
|
339
|
+
if normalize==True:
|
340
|
+
for mode in range(0, np.shape(q)[1]):
|
341
|
+
q[:, mode] = q[:, mode]/max(abs(q[:, mode]))
|
342
|
+
|
343
|
+
# Sort
|
344
|
+
ix = np.argsort(np.abs(lambd))
|
345
|
+
lambd = lambd[ix]
|
346
|
+
q = q[:,ix]
|
347
|
+
|
348
|
+
return lambd, q, not_converged
|
349
|
+
|
350
|
+
|
351
|
+
|
352
|
+
def xmacmat(phi1, phi2=None, conjugates=True):
|
353
|
+
"""
|
354
|
+
Modal assurance criterion numbers, cross-matrix between two modal transformation matrices (modes stacked as columns).
|
355
|
+
|
356
|
+
Arguments
|
357
|
+
---------------------------
|
358
|
+
phi1 : double
|
359
|
+
reference modes
|
360
|
+
phi2 : double, optional
|
361
|
+
modes to compare with, if not given (i.e., equal default value None), phi1 vs phi1 is assumed
|
362
|
+
conjugates : True, optional
|
363
|
+
check the complex conjugates of all modes as well (should normally be True)
|
364
|
+
|
365
|
+
Returns
|
366
|
+
---------------------------
|
367
|
+
macs : double
|
368
|
+
matrix of MAC numbers
|
369
|
+
"""
|
370
|
+
# If no phi2 is given, assign value of phi1
|
371
|
+
if phi2 is None:
|
372
|
+
phi2 = 1.0*phi1
|
373
|
+
|
374
|
+
if len(np.shape(phi1))==1:
|
375
|
+
phi1 = np.expand_dims(phi1, axis=0).T
|
376
|
+
|
377
|
+
if len(np.shape(phi2))==1:
|
378
|
+
phi2 = np.expand_dims(phi2, axis=0).T
|
379
|
+
|
380
|
+
# norms1 = np.dot(np.expand_dims(np.sum(phi1.T * phi1.T.conj(), axis=1), axis=0), np.expand_dims(np.sum(phi2.T * phi2.T.conj(),axis=1), axis=1))
|
381
|
+
norms = np.real(np.sum(phi1.T * np.conj(phi1.T), axis=1))[:,np.newaxis] @ np.real(np.sum(phi2.T * np.conj(phi2.T),axis=1))[np.newaxis,:]
|
382
|
+
|
383
|
+
|
384
|
+
if conjugates:
|
385
|
+
macs1 = np.divide(abs(np.dot(phi1.T, phi2))**2, norms)
|
386
|
+
macs2 = np.divide(abs(np.dot(phi1.T, phi2.conj()))**2, norms)
|
387
|
+
macs = np.maximum(macs1, macs2)
|
388
|
+
else:
|
389
|
+
macs = np.divide(abs(np.dot(phi1.T, phi2))**2, norms)
|
390
|
+
|
391
|
+
macs = np.real(macs)
|
392
|
+
|
393
|
+
if np.size(macs) == 1:
|
394
|
+
macs = macs[0,0]
|
395
|
+
|
396
|
+
return macs
|
397
|
+
|
398
|
+
def mac(phi1, phi2):
|
399
|
+
|
400
|
+
mac_value = np.real(np.abs(np.dot(phi1.T,phi2))**2 / np.abs((np.dot(phi1.T, phi1) * np.dot(phi2.T, phi2))))
|
401
|
+
return mac_value
|
402
|
+
|
403
|
+
|
404
|
+
|
405
|
+
def mcf(phi):
|
406
|
+
|
407
|
+
# Ensure on matrix format
|
408
|
+
if phi.ndim == 1:
|
409
|
+
phi = phi[:,np.newaxis]
|
410
|
+
|
411
|
+
|
412
|
+
n_modes = np.shape(phi)[1]
|
413
|
+
|
414
|
+
X = np.real(phi)
|
415
|
+
Y = np.imag(phi)
|
416
|
+
|
417
|
+
modal_complexity_factor = [None]*n_modes
|
418
|
+
for mode in range(0,n_modes):
|
419
|
+
modal_complexity_factor[mode] = np.abs(np.dot(X[:,mode], Y[:,mode]))**2 / (np.abs(np.dot(X[:,mode], X[:,mode])) * np.abs(np.dot(Y[:,mode], Y[:,mode])))
|
420
|
+
|
421
|
+
modal_complexity_factor = np.array(modal_complexity_factor)
|
422
|
+
return modal_complexity_factor
|
423
|
+
|
424
|
+
|
425
|
+
def mpc(phi):
|
426
|
+
# Based on the current paper:
|
427
|
+
# Pappa, R. S., Elliott, K. B., & Schenk, A. (1993).
|
428
|
+
# Consistent-mode indicator for the eigensystem realization algorithm.
|
429
|
+
# Journal of Guidance, Control, and Dynamics, 16(5), 852–858.
|
430
|
+
|
431
|
+
# Ensure on matrix format
|
432
|
+
if phi.ndim == 1:
|
433
|
+
phi = phi[:,np.newaxis]
|
434
|
+
|
435
|
+
n_modes = np.shape(phi)[1]
|
436
|
+
mpc_val = [None]*n_modes
|
437
|
+
|
438
|
+
for mode in range(0,n_modes):
|
439
|
+
phin = phi[:, mode]
|
440
|
+
Sxx = np.dot(np.real(phin), np.real(phin))
|
441
|
+
Syy = np.dot(np.imag(phin), np.imag(phin))
|
442
|
+
Sxy = np.dot(np.real(phin), np.imag(phin))
|
443
|
+
|
444
|
+
eta = (Syy-Sxx)/(2*Sxy)
|
445
|
+
|
446
|
+
lambda1 = (Sxx+Syy)/2 + Sxy*np.sqrt(eta**2+1)
|
447
|
+
lambda2 = (Sxx+Syy)/2 - Sxy*np.sqrt(eta**2+1)
|
448
|
+
|
449
|
+
mpc_val[mode] = ((lambda1-lambda2)/(lambda1+lambda2))**2
|
450
|
+
|
451
|
+
mpc_val = np.array(mpc_val)
|
452
|
+
return mpc_val
|
453
|
+
|
454
|
+
|
455
|
+
def scale_phi(phi, scaling):
|
456
|
+
phi_scaled = phi*1
|
457
|
+
for mode in range(phi.shape[1]):
|
458
|
+
phi_scaled[:,mode] = phi[:,mode] * scaling[mode]
|
459
|
+
|
460
|
+
return phi_scaled
|
461
|
+
|
462
|
+
def normalize_phi(phi, include_dofs=[0,1,2,3,4,5,6], n_dofs=6):
|
463
|
+
phi_n = phi*0
|
464
|
+
|
465
|
+
phi_for_scaling = np.vstack([phi[dof::n_dofs, :] for dof in include_dofs])
|
466
|
+
mode_scaling = np.max(np.abs(phi_for_scaling), axis=0)
|
467
|
+
ix_max = np.argmax(np.abs(phi_for_scaling), axis=0)
|
468
|
+
signs = np.sign(phi_for_scaling[ix_max, range(0, len(ix_max))])
|
469
|
+
signs[signs==0] = 1
|
470
|
+
mode_scaling[mode_scaling==0] = 1
|
471
|
+
|
472
|
+
phi_n = phi/np.tile(mode_scaling[np.newaxis,:]/signs[np.newaxis,:], [phi.shape[0], 1])
|
473
|
+
|
474
|
+
return phi_n, mode_scaling
|
475
|
+
|
476
|
+
|
477
|
+
def restructure_as_ref(phi_ref, phi, min_mac=0.0, ensure_unique=True,
|
478
|
+
return_all=True, accept_conjugates=True):
|
479
|
+
"""
|
480
|
+
Restructure modes based on reference modal transformation matrix phi_ref.
|
481
|
+
|
482
|
+
Arguments
|
483
|
+
---------------------------
|
484
|
+
phi_ref : double
|
485
|
+
reference modes
|
486
|
+
phi : double
|
487
|
+
modes to compare with
|
488
|
+
min_mac : double, 0.0
|
489
|
+
minimum MAC value for mode to be placed in index arrays
|
490
|
+
|
491
|
+
Returns
|
492
|
+
---------------------------
|
493
|
+
ixs : integers
|
494
|
+
array of indices, describing which elements to copy to reference
|
495
|
+
numbering (indices indicate position in original numbering,
|
496
|
+
as in phi - position relates to reference numbering)
|
497
|
+
ref_ixs : integers
|
498
|
+
array of indices, describing which elements to assign values to
|
499
|
+
(using reference numbering) - equal to 0:n_modes if min_mac=0.0 (all are populated).
|
500
|
+
"""
|
501
|
+
xmacs = xmacmat(phi_ref, phi2=phi, conjugates=accept_conjugates) # n_modes_ref-by-n_modes
|
502
|
+
ixs = np.argmax(xmacs, axis=1) # indices of what mode in phi_ref each mode in phi2 are closest to, i.e, phi_rs = phi[:, ix]
|
503
|
+
ref_ixs = np.arange(len(ixs))
|
504
|
+
xmacs_out = xmacs*1
|
505
|
+
|
506
|
+
if ensure_unique:
|
507
|
+
for row in range(xmacs.shape[0]):
|
508
|
+
xmac_row = xmacs[row, :]
|
509
|
+
xmacs[row, xmac_row<np.max(xmac_row)] = 0
|
510
|
+
|
511
|
+
for col in range(xmacs.shape[1]):
|
512
|
+
xmac_col = xmacs[:, col]
|
513
|
+
xmacs[xmac_col<np.max(xmac_col), col] = 0
|
514
|
+
|
515
|
+
ok = np.diag(xmacs[:, ixs])>min_mac
|
516
|
+
|
517
|
+
ixs = ixs[ok]
|
518
|
+
ref_ixs = ref_ixs[ok]
|
519
|
+
rejected_ixs = np.where(~ok)[0]
|
520
|
+
xmacs_out = xmacs_out[ref_ixs, ixs]
|
521
|
+
|
522
|
+
if return_all:
|
523
|
+
return ixs, ref_ixs, rejected_ixs, xmacs_out
|
524
|
+
else:
|
525
|
+
return ixs
|
526
|
+
|
527
|
+
|
528
|
+
def robust_phi_restructure(phi_ref, phi, accept_conjugates=False):
|
529
|
+
"""
|
530
|
+
Restructure modes based on reference modal transformation matrix phi_ref.
|
531
|
+
|
532
|
+
Arguments
|
533
|
+
---------------------------
|
534
|
+
phi_ref : double
|
535
|
+
reference modes
|
536
|
+
phi : double
|
537
|
+
modes to compare with
|
538
|
+
|
539
|
+
Returns
|
540
|
+
---------------------------
|
541
|
+
ixs : integers
|
542
|
+
array of indices, describing which elements to copy to reference numbering (indices indicate position in original numbering, as in phi - position relates to reference numbering)
|
543
|
+
discarded_ixs : integers
|
544
|
+
rest of modes (if not the best match to any mode in reference mode)
|
545
|
+
"""
|
546
|
+
|
547
|
+
xmacs = xmacmat(phi_ref, phi2=phi, conjugates=accept_conjugates) # n_modes_ref-by-n_modes
|
548
|
+
ixs = np.argmax(xmacs, axis=0)
|
549
|
+
|
550
|
+
# Make unique based on selected columns in ixs
|
551
|
+
for ix in np.unique(ixs):
|
552
|
+
all_this_ix_ix = np.where(ixs==ix)[0]
|
553
|
+
ix_max = np.argmax(xmacs[:, ixs==ix], axis=0)
|
554
|
+
|
555
|
+
n = len(all_this_ix_ix)
|
556
|
+
discards = np.array([i for i in range(n) if i != ix_max])
|
557
|
+
|
558
|
+
if len(discards)>=0:
|
559
|
+
ixs[all_this_ix_ix[discards]] = np.nan
|
560
|
+
|
561
|
+
all_ixs = np.arange(0, phi_ref.shape[1])
|
562
|
+
discarded_ixs = np.sort([i for i in all_ixs if i not in ixs])
|
563
|
+
|
564
|
+
return ixs, discarded_ixs
|
565
|
+
|
566
|
+
|
567
|
+
|
568
|
+
def freq_eig(K, C, M, omega, omega_ref=None, phi_ref=None, min_mac=0.0, input_functions=True, keep_full=False):
|
569
|
+
if not input_functions:
|
570
|
+
K = interp1d(omega, K, kind='quadratic', fill_value='extrapolate')
|
571
|
+
C = interp1d(omega, C, kind='quadratic', fill_value='extrapolate')
|
572
|
+
M = interp1d(omega, M, kind='quadratic', fill_value='extrapolate')
|
573
|
+
|
574
|
+
if phi_ref is None:
|
575
|
+
if omega_ref is None:
|
576
|
+
prev_compare = True
|
577
|
+
omega_ref = omega[0]
|
578
|
+
else:
|
579
|
+
prev_compare = False
|
580
|
+
|
581
|
+
A_ref = statespace(K(omega_ref), C(omega_ref), M(omega_ref))
|
582
|
+
lambda_ref, phi_ref = np.linalg.eig(A_ref)
|
583
|
+
sortix = np.argsort(abs(lambda_ref))
|
584
|
+
phi_ref = phi_ref[:, sortix]
|
585
|
+
else:
|
586
|
+
prev_compare = False
|
587
|
+
|
588
|
+
phi = [None]*len(omega)
|
589
|
+
lambd = [None]*len(omega)
|
590
|
+
|
591
|
+
for k, omega_k in enumerate(omega):
|
592
|
+
phi[k] = np.nan*phi_ref
|
593
|
+
lambd[k] = np.nan*np.ones(phi_ref.shape[1]).astype('complex')
|
594
|
+
|
595
|
+
A = statespace(K(omega_k), C(omega_k), M(omega_k))
|
596
|
+
lambd_k, phi_k = np.linalg.eig(A)
|
597
|
+
mode_ix, ref_ix, __, __ = restructure_as_ref(phi_ref, phi_k, min_mac=min_mac, ensure_unique=False)
|
598
|
+
lambd[k][ref_ix] = lambd_k[mode_ix]
|
599
|
+
phi[k][:, ref_ix] = phi_k[:, mode_ix]
|
600
|
+
|
601
|
+
if prev_compare:
|
602
|
+
phi_ref = phi[k]
|
603
|
+
|
604
|
+
if not keep_full:
|
605
|
+
lambd = [lambd_k[::2] for lambd_k in lambd]
|
606
|
+
phi = [phi_k[::2,::2] for phi_k in phi]
|
607
|
+
|
608
|
+
return lambd, phi
|