Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit ef7eecf

Browse files
committed
Tools: adding pandalib, signal_analysis and update of eva
1 parent b4e8ed7 commit ef7eecf

File tree

3 files changed

+972
-37
lines changed

3 files changed

+972
-37
lines changed

pyFAST/tools/eva.py

Lines changed: 77 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import numpy as np
2222
from scipy import linalg
2323

24-
def polyeig(*A, sort=False):
24+
def polyeig(*A, sort=False, normQ=None):
2525
"""
2626
Solve the polynomial eigenvalue problem:
2727
(A0 + e A1 +...+ e**p Ap)x = 0
@@ -67,12 +67,13 @@ def polyeig(*A, sort=False):
6767
e = e[I]
6868

6969
# 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))
7172

7273
return X, e
7374

7475

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):
7677
""" performs eigenvalue analysis and return same values as matlab
7778
7879
returns:
@@ -84,27 +85,29 @@ def eig(K, M=None, freq_out=False, sort=True, normQ=None, discardIm=False):
8485
"""
8586
if M is not None:
8687
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)
8797
else:
8898
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:
97104
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]
106109
else:
107-
Lambda = np.diag(D)
110+
Lambda = np.diag(lambdaDiag) # enforcing purely diagonal
108111

109112
# --- Renormalize modes if users wants to
110113
if normQ == 'byMax':
@@ -130,7 +133,7 @@ def eig(K, M=None, freq_out=False, sort=True, normQ=None, discardIm=False):
130133
return Q,Lambda
131134

132135

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):
134137
"""
135138
Perform eigenvalue analysis on a "state" matrix A
136139
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):
180183
freq_0 = omega_0/(2*np.pi) # natural frequency [Hz]
181184

182185
# 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]
188192

189193
# Normalize Q
190194
if normQ=='byMax':
@@ -196,7 +200,7 @@ def eigA(A, nq=None, nq1=None, fullEV=False, normQ=None):
196200

197201

198202

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):
200204
"""
201205
Eigenvalue analysis of a mechanical system
202206
M, K: mass, and stiffness matrices respectively
@@ -208,21 +212,28 @@ def eigMK(M, K, sort=True, normQ=None, discardIm=False, freq_out=True):
208212
Q, freq_0 if freq_out
209213
Q, Lambda otherwise
210214
"""
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)
212216

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):
214219
"""
215220
Eigenvalue analysis of a mechanical system
216221
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
217225
"""
218226
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
224233

225234

235+
n = M.shape[0] # Number of DOFs
236+
226237
if method.lower()=='diag_beta':
227238
## using K, M and damping assuming diagonal beta matrix (Rayleigh Damping)
228239
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):
234245
freq_d = freq_0*np.sqrt(1-zeta**2)
235246
elif method.lower()=='full_matrix':
236247
## 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)
238249
#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)
241252
# Keeping only positive frequencies
242253
bValid = freq_d > 1e-08
243254
freq_d = freq_d[bValid]
244255
zeta = zeta[bValid]
245256
Q = Q[:,bValid]
246257
# 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+
247286
else:
248287
raise NotImplementedError()
288+
249289
# Sorting
250290
if sort:
251291
I = np.argsort(freq_d)

pyFAST/tools/pandalib.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pandas as pd
2+
import numpy as np
3+
4+
def pd_interp1(x_new, xLabel, df):
5+
""" Interpolate a panda dataframe based on a set of new value
6+
This function assumes that the dataframe is a simple 2d-table
7+
"""
8+
from pyFAST.tools.signal_analysis import multiInterp
9+
x_old = df[xLabel].values
10+
data_new=multiInterp(x_new, x_old, df.values.T)
11+
return pd.DataFrame(data=data_new.T, columns=df.columns.values)
12+
#nRow,nCol = df.shape
13+
#nRow = len(xnew)
14+
#data = np.zeros((nRow,nCol))
15+
#xref =df[xLabel].values.astype(float)
16+
#for col,i in zip(df.columns.values,range(nCol)):
17+
# yref = df[col].values
18+
# if yref.dtype!=float:
19+
# raise Exception('Wrong type for yref, consider using astype(float)')
20+
# data[:,i] = np.interp(xnew, xref, yref)
21+
#return pd.DataFrame(data=data, columns = df.columns)
22+
23+
def create_dummy_dataframe(size):
24+
return pd.DataFrame(data={'col1': np.linspace(0,1,size), 'col2': np.random.normal(0,1,size)})

0 commit comments

Comments
 (0)