21
21
import numpy as np
22
22
from scipy import linalg
23
23
24
- def polyeig (* A , sort = False ):
24
+ def polyeig (* A , sort = False , normQ = None ):
25
25
"""
26
26
Solve the polynomial eigenvalue problem:
27
27
(A0 + e A1 +...+ e**p Ap)x = 0
@@ -67,12 +67,13 @@ def polyeig(*A, sort=False):
67
67
e = e [I ]
68
68
69
69
# Scaling each mode by max
70
- X /= np .tile (np .max (np .abs (X ),axis = 0 ), (n ,1 ))
70
+ if normQ == 'byMax' :
71
+ X /= np .tile (np .max (np .abs (X ),axis = 0 ), (n ,1 ))
71
72
72
73
return X , e
73
74
74
75
75
- def eig (K , M = None , freq_out = False , sort = True , normQ = None , discardIm = False ):
76
+ def eig (K , M = None , freq_out = False , sort = True , normQ = None , discardIm = False , massScaling = True ):
76
77
""" performs eigenvalue analysis and return same values as matlab
77
78
78
79
returns:
@@ -84,27 +85,29 @@ def eig(K, M=None, freq_out=False, sort=True, normQ=None, discardIm=False):
84
85
"""
85
86
if M is not None :
86
87
D ,Q = linalg .eig (K ,M )
88
+ # --- rescaling using mass matrix to be consistent with Matlab
89
+ # TODO, this can be made smarter
90
+ # TODO this should be a normQ
91
+ if massScaling :
92
+ for j in range (M .shape [1 ]):
93
+ q_j = Q [:,j ]
94
+ modalmass_j = np .dot (q_j .T ,M ).dot (q_j )
95
+ Q [:,j ]= Q [:,j ]/ np .sqrt (modalmass_j )
96
+ Lambda = np .dot (Q .T ,K ).dot (Q )
87
97
else :
88
98
D ,Q = linalg .eig (K )
89
- # --- rescaling TODO, this can be made smarter
90
- if M is not None :
91
- for j in range (M .shape [1 ]):
92
- q_j = Q [:,j ]
93
- modalmass_j = np .dot (q_j .T ,M ).dot (q_j )
94
- Q [:,j ]= Q [:,j ]/ np .sqrt (modalmass_j )
95
- Lambda = np .dot (Q .T ,K ).dot (Q )
96
- lambdaDiag = np .diag (Lambda ) # Note lambda might have off diganoal values due to numerics
99
+ Lambda = np .diag (D )
100
+
101
+ # --- Sort
102
+ lambdaDiag = np .diag (Lambda )
103
+ if sort :
97
104
I = np .argsort (lambdaDiag )
98
- # Sorting eigen values
99
- if sort :
100
- Q = Q [:,I ]
101
- lambdaDiag = lambdaDiag [I ]
102
- if freq_out :
103
- Lambda = np .sqrt (lambdaDiag )/ (2 * np .pi ) # frequencies [Hz]
104
- else :
105
- Lambda = np .diag (lambdaDiag ) # enforcing purely diagonal
105
+ Q = Q [:,I ]
106
+ lambdaDiag = lambdaDiag [I ]
107
+ if freq_out :
108
+ Lambda = np .sqrt (lambdaDiag )/ (2 * np .pi ) # frequencies [Hz]
106
109
else :
107
- Lambda = np .diag (D )
110
+ Lambda = np .diag (lambdaDiag ) # enforcing purely diagonal
108
111
109
112
# --- Renormalize modes if users wants to
110
113
if normQ == 'byMax' :
@@ -130,7 +133,7 @@ def eig(K, M=None, freq_out=False, sort=True, normQ=None, discardIm=False):
130
133
return Q ,Lambda
131
134
132
135
133
- def eigA (A , nq = None , nq1 = None , fullEV = False , normQ = None ):
136
+ def eigA (A , nq = None , nq1 = None , fullEV = False , normQ = None , sort = True ):
134
137
"""
135
138
Perform eigenvalue analysis on a "state" matrix A
136
139
where states are assumed to be ordered as {q, q_dot, q1}
@@ -180,11 +183,12 @@ def eigA(A, nq=None, nq1=None, fullEV=False, normQ=None):
180
183
freq_0 = omega_0 / (2 * np .pi ) # natural frequency [Hz]
181
184
182
185
# Sorting
183
- I = np .argsort (freq_0 )
184
- freq_d = freq_d [I ]
185
- freq_0 = freq_0 [I ]
186
- zeta = zeta [I ]
187
- Q = Q [:,I ]
186
+ if sort :
187
+ I = np .argsort (freq_0 )
188
+ freq_d = freq_d [I ]
189
+ freq_0 = freq_0 [I ]
190
+ zeta = zeta [I ]
191
+ Q = Q [:,I ]
188
192
189
193
# Normalize Q
190
194
if normQ == 'byMax' :
@@ -196,7 +200,7 @@ def eigA(A, nq=None, nq1=None, fullEV=False, normQ=None):
196
200
197
201
198
202
199
- def eigMK (M , K , sort = True , normQ = None , discardIm = False , freq_out = True ):
203
+ def eigMK (M , K , sort = True , normQ = None , discardIm = False , freq_out = True , massScaling = True ):
200
204
"""
201
205
Eigenvalue analysis of a mechanical system
202
206
M, K: mass, and stiffness matrices respectively
@@ -208,21 +212,28 @@ def eigMK(M, K, sort=True, normQ=None, discardIm=False, freq_out=True):
208
212
Q, freq_0 if freq_out
209
213
Q, Lambda otherwise
210
214
"""
211
- return eig (K , M , sort = sort , normQ = normQ , discardIm = discardIm , freq_out = freq_out )
215
+ return eig (K , M , sort = sort , normQ = normQ , discardIm = discardIm , freq_out = freq_out , massScaling = massScaling )
212
216
213
- def eigMCK (M , C , K , method = 'full_matrix' , sort = True ):
217
+
218
+ def eigMCK (M , C , K , method = 'full_matrix' , sort = True , normQ = None ):
214
219
"""
215
220
Eigenvalue analysis of a mechanical system
216
221
M, C, K: mass, damping, and stiffness matrices respectively
222
+
223
+ NOTE: full_matrix, state_space and state_space_gen should return the same
224
+ when damping is present
217
225
"""
218
226
if np .linalg .norm (C )< 1e-14 :
219
- # No damping
220
- Q , freq_0 = eigMK (M , K , sort = sort )
221
- freq_d = freq_0
222
- zeta = freq_0 * 0
223
- return freq_d , zeta , Q , freq_0
227
+ if method .lower () not in ['state_space' , 'state_space_gen' ]:
228
+ # No damping
229
+ Q , freq_0 = eigMK (M , K , sort = sort , freq_out = True , normQ = normQ )
230
+ freq_d = freq_0
231
+ zeta = freq_0 * 0
232
+ return freq_d , zeta , Q , freq_0
224
233
225
234
235
+ n = M .shape [0 ] # Number of DOFs
236
+
226
237
if method .lower ()== 'diag_beta' :
227
238
## using K, M and damping assuming diagonal beta matrix (Rayleigh Damping)
228
239
Q , Lambda = eig (K ,M , sort = False ) # provide scaled EV, important, no sorting here!
@@ -234,18 +245,47 @@ def eigMCK(M, C, K, method='full_matrix', sort=True):
234
245
freq_d = freq_0 * np .sqrt (1 - zeta ** 2 )
235
246
elif method .lower ()== 'full_matrix' :
236
247
## Method 2 - Damping based on K, M and full D matrix
237
- Q ,e = polyeig (K ,C ,M )
248
+ Q ,v = polyeig (K ,C ,M , sort = sort , normQ = normQ )
238
249
#omega0 = np.abs(e)
239
- zeta = - np .real (e ) / np .abs (e )
240
- freq_d = np .imag (e ) / (2 * np .pi )
250
+ zeta = - np .real (v ) / np .abs (v )
251
+ freq_d = np .imag (v ) / (2 * np .pi )
241
252
# Keeping only positive frequencies
242
253
bValid = freq_d > 1e-08
243
254
freq_d = freq_d [bValid ]
244
255
zeta = zeta [bValid ]
245
256
Q = Q [:,bValid ]
246
257
# logdec2 = 2*pi*dampratio_sorted./sqrt(1-dampratio_sorted.^2);
258
+
259
+ elif method .lower ()== 'state_space' :
260
+ # See welib.system.statespace.StateMatrix
261
+ Minv = np .linalg .inv (M )
262
+ I = np .eye (n )
263
+ Z = np .zeros ((n , n ))
264
+ A = np .block ([[np .zeros ((n , n )), np .eye (n )],
265
+ [ - Minv @K , - Minv @C ]])
266
+ return eigA (A , normQ = normQ , sort = sort )
267
+
268
+ elif method .lower ()== 'state_space_gen' :
269
+ I = np .eye (n )
270
+ Z = np .zeros ((n , n ))
271
+ A = np .block ([[Z , I ],
272
+ [- K , - C ]])
273
+ B = np .block ([[I , Z ],
274
+ [Z , M ]])
275
+ # solve the generalized eigenvalue problem
276
+ D , Q = linalg .eig (A , B )
277
+ # Keeping every other states (assuming pairs..)
278
+ v = D [::2 ]
279
+ Q = Q [:n , ::2 ]
280
+
281
+ # calculate natural frequencies and damping
282
+ omega_0 = np .abs (v ) # natural cyclic frequency [rad/s]
283
+ freq_d = np .imag (v )/ (2 * np .pi ) # damped frequency [Hz]
284
+ zeta = - np .real (v )/ omega_0 # damping ratio
285
+
247
286
else :
248
287
raise NotImplementedError ()
288
+
249
289
# Sorting
250
290
if sort :
251
291
I = np .argsort (freq_d )
0 commit comments