wawi 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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