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/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