wawi 0.0.1__py3-none-any.whl → 0.0.5__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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/structural.py
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
import sys
|
2
|
+
import numpy as np
|
3
|
+
from . import wind
|
4
|
+
from .general import rodrot, blkdiag, correct_matrix_size, transform_3dmat
|
5
|
+
from .tools import print_progress as pp
|
6
|
+
from .random import peakfactor
|
7
|
+
|
8
|
+
from scipy.interpolate import interp1d
|
9
|
+
|
10
|
+
#%% General
|
11
|
+
def dry_modalmats(f, m, rayleigh={'stiffness':0, 'mass':0}, xi0=0):
|
12
|
+
"""
|
13
|
+
Construct dry modal matrices.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
f: natural frequencies (Hz)
|
17
|
+
m: modal masses (kg)
|
18
|
+
rayleigh: dictionary with keys ('stiffness' and 'mass') characterizing damping proportional to stiffness and mass
|
19
|
+
xi0: constant modal critical damping ratio value (added on top of Rayleigh damping)
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Mdry: mass matrix
|
23
|
+
Cdry: damping matrix
|
24
|
+
Kdry: stiffness matrix
|
25
|
+
|
26
|
+
Knut Andreas Kvaale, 2017
|
27
|
+
"""
|
28
|
+
w = (f*2*np.pi)
|
29
|
+
k = np.multiply(w**2, m)
|
30
|
+
Kdry = np.diag(k)
|
31
|
+
Mdry = np.diag(m)
|
32
|
+
|
33
|
+
c = k*rayleigh['stiffness'] + m*rayleigh['mass'] + xi0*2*np.sqrt(k*m)
|
34
|
+
Cdry = np.diag(c)
|
35
|
+
|
36
|
+
return Mdry, Cdry, Kdry
|
37
|
+
|
38
|
+
|
39
|
+
def wet_physmat(pontoon_types, angles, mat):
|
40
|
+
"""
|
41
|
+
Construct frequency dependent physical matrix.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
pontoon_types: list with one element per pontoon, indicating the pontoon type (referred to the index of Mh and Ch)
|
45
|
+
angles: list of angles of pontoons (in radians)
|
46
|
+
mat: list of 3D numpy matrices (6 x 6 x Nfreq), with Npontoons entries
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
mat_tot: frequency dependent modal matrix (Nmod x Nmod x Nfreq)
|
50
|
+
|
51
|
+
Knut Andreas Kvaale, 2017
|
52
|
+
"""
|
53
|
+
|
54
|
+
Nponts = len(angles)
|
55
|
+
|
56
|
+
if len(np.shape(mat[0])) == 3:
|
57
|
+
Nfreqs = np.shape(mat[0])[2]
|
58
|
+
else:
|
59
|
+
Nfreqs = 1
|
60
|
+
mat = np.reshape(mat, [len(mat), 6, 6, 1])
|
61
|
+
|
62
|
+
mat_global = np.empty([6*Nponts, 6*Nponts, Nfreqs], dtype=mat[0].dtype)
|
63
|
+
|
64
|
+
T = np.zeros([6, 6])
|
65
|
+
|
66
|
+
for pont in range(0, Nponts):
|
67
|
+
pt = pontoon_types[pont]
|
68
|
+
T0 = rodrot(angles[pont])
|
69
|
+
T[0:3, 0:3], T[3:6, 3:6] = T0, T0
|
70
|
+
|
71
|
+
for k in range(0, Nfreqs): # Loop through discrete freqs
|
72
|
+
mat_global[pont*6:pont*6+6, pont*6:pont*6+6, k] = np.dot(np.dot(T.T, mat[pt][:, :, k]), T)
|
73
|
+
|
74
|
+
if Nfreqs == 1:
|
75
|
+
mat_global = mat_global[:, :, 0]
|
76
|
+
|
77
|
+
return mat_global
|
78
|
+
|
79
|
+
def frf_fun(M, C, K, inverse=False):
|
80
|
+
if inverse:
|
81
|
+
return lambda omega_k: -omega_k**2*M(omega_k) + omega_k*1j*C(omega_k) + K(omega_k)
|
82
|
+
else:
|
83
|
+
return lambda omega_k: np.linalg.inv(-omega_k**2*M(omega_k) + omega_k*1j*C(omega_k) + K(omega_k))
|
84
|
+
|
85
|
+
def frf(M, C, K, w, inverse=False):
|
86
|
+
"""
|
87
|
+
Establish frequency response function from M, C and K matrices (all may be frequency dependent).
|
88
|
+
|
89
|
+
Args:
|
90
|
+
M: mass matrix (Ndofs x Ndofs x Nfreq or Ndofs x Ndofs)
|
91
|
+
C: damping matrix (Ndofs x Ndofs x Nfreq or Ndofs x Ndofs)
|
92
|
+
K: stiffness matrix (Ndofs x Ndofs x Nfreq or Ndofs x Ndofs)
|
93
|
+
w: frequency axis
|
94
|
+
Optional keywords:
|
95
|
+
inverse: state if the inverse of the frf should be returned instead of the frf (standard = False)
|
96
|
+
Returns:
|
97
|
+
H: frequency response function matrix (Ndofs x Ndofs x Nfreq)
|
98
|
+
|
99
|
+
Knut Andreas Kvaale, 2017
|
100
|
+
"""
|
101
|
+
|
102
|
+
n_dofs = np.shape(K)[0]
|
103
|
+
n_freqs = len(w)
|
104
|
+
|
105
|
+
if len(np.shape(M)) == 2:
|
106
|
+
M = np.tile(np.reshape(M, [n_dofs, n_dofs, 1]), [1, 1, n_freqs])
|
107
|
+
if len(np.shape(C)) == 2:
|
108
|
+
C = np.tile(np.reshape(C, [n_dofs, n_dofs, 1]), [1, 1, n_freqs])
|
109
|
+
if len(np.shape(K)) == 2:
|
110
|
+
K = np.tile(np.reshape(K, [n_dofs, n_dofs, 1]), [1, 1, n_freqs])
|
111
|
+
|
112
|
+
if inverse is True:
|
113
|
+
wmat = np.tile(w, [n_dofs, n_dofs, 1])
|
114
|
+
H = -wmat**2*M + wmat*1j*C + K
|
115
|
+
else:
|
116
|
+
H = np.empty([n_dofs, n_dofs, n_freqs], dtype=complex) # Memory allocation
|
117
|
+
|
118
|
+
for k, wk in enumerate(w):
|
119
|
+
Mk = mat3d_sel(M, k)
|
120
|
+
Ck = mat3d_sel(C, k)
|
121
|
+
Kk = mat3d_sel(K, k)
|
122
|
+
H[:, :, k] = np.linalg.inv(-wk**2*Mk + 1j*wk*Ck + Kk)
|
123
|
+
|
124
|
+
return H
|
125
|
+
|
126
|
+
|
127
|
+
def sum_frfs(*args):
|
128
|
+
"""
|
129
|
+
Sum frequency response function matrices, by summing the inverses and reinverting.
|
130
|
+
|
131
|
+
Optional args:
|
132
|
+
first argument: first FRF (Ndofs x Ndofs x Nfreq)
|
133
|
+
second argument: second ...
|
134
|
+
etc..
|
135
|
+
Returns:
|
136
|
+
H: frequency response function matrix (Ndofs x Ndofs x Nfreq)
|
137
|
+
|
138
|
+
Knut Andreas Kvaale, 2017
|
139
|
+
"""
|
140
|
+
|
141
|
+
Hinv = np.zeros(np.shape(args[0]))
|
142
|
+
|
143
|
+
for Hi in args:
|
144
|
+
Hinv = Hinv + np.inv(Hi)
|
145
|
+
|
146
|
+
H = np.inv(Hinv)
|
147
|
+
|
148
|
+
return H
|
149
|
+
|
150
|
+
|
151
|
+
def mat3d_sel(mat, k):
|
152
|
+
|
153
|
+
if len(np.shape(mat)) == 3:
|
154
|
+
matsel = mat[:, :, k]
|
155
|
+
else:
|
156
|
+
matsel = mat
|
157
|
+
|
158
|
+
return matsel
|
159
|
+
|
160
|
+
|
161
|
+
def phys2modal(mat_global, phi_pontoons, inverse=False):
|
162
|
+
"""
|
163
|
+
Transform frequency dependent physical matrix to modal matrix.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
mat_global: global system matrix (6*Nponts x 6*Nponts x Nfreq or 6*Nponts x 6*Nponts)
|
167
|
+
phi_pontoons: modal transformation matrix (DOFs referring to pontoons only)
|
168
|
+
[inverse]: if True, the transform is from modal to physical, i.e., phi * mat * phi^T. (default = False)
|
169
|
+
Returns:
|
170
|
+
mat_modal: frequency dependent modal matrix (Nmod x Nmod x Nfreq)
|
171
|
+
|
172
|
+
Knut Andreas Kvaale, 2017
|
173
|
+
"""
|
174
|
+
|
175
|
+
if inverse is True:
|
176
|
+
phi_pontoons = np.transpose(phi_pontoons) # Transpose phi matrix if inverse transformation
|
177
|
+
|
178
|
+
mat_shape = np.shape(mat_global)
|
179
|
+
Nmodes = np.shape(phi_pontoons)[1]
|
180
|
+
|
181
|
+
if len(mat_shape) == 3: # 3D matrix (frequency dependent)
|
182
|
+
mat_modal = np.empty([Nmodes, Nmodes, mat_shape[2]])
|
183
|
+
|
184
|
+
for k in range(0, mat_shape[2]):
|
185
|
+
mat_modal[:, :, k] = np.dot(np.dot(phi_pontoons.T, mat_global[:, :, k]), phi_pontoons)
|
186
|
+
else: # 2D matrix (no frequency dependency)
|
187
|
+
mat_modal = np.dot(np.dot(phi_pontoons.T, mat_global), phi_pontoons)
|
188
|
+
|
189
|
+
return mat_modal
|
190
|
+
|
191
|
+
#%% Assembly
|
192
|
+
def assemble_hydro_matrices_full(pontoons, omega):
|
193
|
+
node_labels = [pontoon.node for pontoon in pontoons]
|
194
|
+
n_dofs = len(pontoons)*6
|
195
|
+
n_freqs = len(omega)
|
196
|
+
|
197
|
+
Mh = np.zeros([n_dofs, n_dofs, n_freqs])
|
198
|
+
Ch = np.zeros([n_dofs, n_dofs, n_freqs])
|
199
|
+
Kh = np.zeros([n_dofs, n_dofs, n_freqs])
|
200
|
+
|
201
|
+
for ix, pontoon in enumerate(pontoons):
|
202
|
+
if max(omega)>max(pontoon.pontoon_type.original_omega) or min(omega)<min(pontoon.pontoon_type.original_omega):
|
203
|
+
print(f'WARNING: frequency outside range for {pontoon.label} --> extrapolated')
|
204
|
+
|
205
|
+
for k, omega_k in enumerate(omega):
|
206
|
+
Mh[ix*6:ix*6+6, ix*6:ix*6+6, k] = pontoon.get_M(omega_k)
|
207
|
+
Ch[ix*6:ix*6+6, ix*6:ix*6+6, k] = pontoon.get_C(omega_k)
|
208
|
+
Kh[ix*6:ix*6+6, ix*6:ix*6+6, k] = pontoon.get_K(omega_k)
|
209
|
+
|
210
|
+
return Mh, Ch, Kh, node_labels
|
211
|
+
|
212
|
+
|
213
|
+
#%% General, model set up
|
214
|
+
def rayleigh(alpha, beta, omega):
|
215
|
+
ix_zero = np.where(omega==0)
|
216
|
+
|
217
|
+
xi = alpha * (1/(2*omega)) + beta*(omega/2)
|
218
|
+
xi[ix_zero] = np.nan
|
219
|
+
|
220
|
+
return xi
|
221
|
+
|
222
|
+
def rayleigh_damping_fit(xi, omega_1, omega_2):
|
223
|
+
rayleigh_coeff = dict()
|
224
|
+
rayleigh_coeff['mass'] = 2*xi*(omega_1*omega_2)/(omega_1+omega_2)
|
225
|
+
rayleigh_coeff['stiffness'] = 2*xi/(omega_1+omega_2)
|
226
|
+
|
227
|
+
return rayleigh_coeff
|
228
|
+
|
229
|
+
#%% Simulation
|
230
|
+
def freqsim_fun(Sqq, H):
|
231
|
+
def response(omega):
|
232
|
+
return H(omega) @ Sqq(omega) @ H(omega).conj().T
|
233
|
+
|
234
|
+
return response
|
235
|
+
|
236
|
+
|
237
|
+
def freqsim(Sqq, H):
|
238
|
+
n_freqs = np.shape(Sqq)[2]
|
239
|
+
Srr = np.zeros(np.shape(Sqq)).astype('complex')
|
240
|
+
|
241
|
+
for k in range(0, n_freqs):
|
242
|
+
Srr[:,:,k] = H[:,:,k] @ Sqq[:,:,k] @ H[:,:,k].conj().T
|
243
|
+
|
244
|
+
return Srr
|
245
|
+
|
246
|
+
|
247
|
+
def var_from_modal(omega, S, phi, only_diagonal=True):
|
248
|
+
var = phi @ np.real(np.trapz(S, omega, axis=2)) @ phi.T
|
249
|
+
|
250
|
+
if only_diagonal==True:
|
251
|
+
var = np.diag(var)
|
252
|
+
|
253
|
+
return var
|
254
|
+
|
255
|
+
def peakfactor_from_modal(omega, S, phi, T, only_diagonal=True):
|
256
|
+
m0 = phi @ np.real(np.trapz(S, omega, axis=2)) @ phi.T
|
257
|
+
m2 = phi @ np.real(np.trapz(S*omega**2, omega, axis=2)) @ phi.T
|
258
|
+
v0 = 1/(2*np.pi) * np.sqrt(m2/m0)
|
259
|
+
|
260
|
+
kp = peakfactor(T, v0)
|
261
|
+
if only_diagonal==True:
|
262
|
+
kp = np.diag(kp)
|
263
|
+
|
264
|
+
return kp
|
265
|
+
|
266
|
+
def expmax_from_modal(omega, S, phi, T, only_diagonal=True):
|
267
|
+
m0 = phi @ np.real(np.trapz(S, omega, axis=2)) @ phi.T
|
268
|
+
m2 = phi @ np.real(np.trapz(S*omega**2, omega, axis=2)) @ phi.T
|
269
|
+
v0 = 1/(2*np.pi) * np.sqrt(m2/m0)
|
270
|
+
|
271
|
+
expmax = peakfactor(T, v0) * np.sqrt(m0)
|
272
|
+
expmax[m0==0] = 0.0 # avoid nans when 0.0 response
|
273
|
+
|
274
|
+
if only_diagonal==True:
|
275
|
+
expmax = np.diag(expmax)
|
276
|
+
|
277
|
+
return expmax
|
278
|
+
|
wawi/time_domain.py
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from scipy.interpolate import interp1d
|
3
|
+
from scipy.linalg import block_diag, cholesky
|
4
|
+
|
5
|
+
def band_truncate_3d(M3d, n):
|
6
|
+
for k in range(M3d.shape[2]):
|
7
|
+
M3d[:,:,k] = band_truncate(M3d[:,:,k], n=n)
|
8
|
+
|
9
|
+
return M3d
|
10
|
+
|
11
|
+
|
12
|
+
def band_truncate(M, n=1):
|
13
|
+
if n is None:
|
14
|
+
n = M.shape[0]
|
15
|
+
|
16
|
+
Mt = M*0
|
17
|
+
|
18
|
+
for ix in range(M.shape[0]):
|
19
|
+
n1 = ix-n if (ix-n)>=0 else 0
|
20
|
+
n2 = ix+n+1 if (ix+n)<=M.shape[0] else M.shape[0]
|
21
|
+
|
22
|
+
Mt[ix, n1:n2] = M[ix, n1:n2]
|
23
|
+
|
24
|
+
return Mt
|
25
|
+
|
26
|
+
def fft_time(omega, t0=0, n_fft=None):
|
27
|
+
if n_fft is None:
|
28
|
+
n_fft = len(omega)
|
29
|
+
domega = omega[1] - omega[0]
|
30
|
+
t = np.linspace(t0, np.pi*2/domega, n_fft)
|
31
|
+
|
32
|
+
return t
|
33
|
+
|
34
|
+
def spectrum_to_process(S, reg_factor=None, zero_limit=None):
|
35
|
+
B = S*0 # copy S to chol factor
|
36
|
+
norm_pre = np.linalg.norm(S)
|
37
|
+
if reg_factor is not None:
|
38
|
+
for k in range(S.shape[2]):
|
39
|
+
S[:,:,k] = S[:,:,k] + np.linalg.norm(S[:,:,k]) * np.eye(S.shape[0]) * reg_factor
|
40
|
+
|
41
|
+
if (np.linalg.norm(S)-norm_pre)/norm_pre>0.1:
|
42
|
+
raise ValueError('Regularization causes significant increase in norm of S (10%). Please adjust.')
|
43
|
+
|
44
|
+
# Cholesky decomposition
|
45
|
+
for k in range(B.shape[2]):
|
46
|
+
if zero_limit is None or np.max(np.max(np.abs(S[:,:,k])))>zero_limit:
|
47
|
+
B[:,:,k] = cholesky(S[:, :, k], lower=True)
|
48
|
+
return B
|
49
|
+
|
50
|
+
|
51
|
+
def simulate_mdof(S, omega, fs=None, tmax=None, reg_factor=None, zero_limit=1e-12,
|
52
|
+
phase_angles=None, return_phase_angles=False, interpolation_kind='linear',
|
53
|
+
component_scaling=None, print_status=False):
|
54
|
+
|
55
|
+
if omega[0] !=0:
|
56
|
+
omega = np.insert(omega, 0, 0)
|
57
|
+
S = np.dstack([0*S[:,:,0], S])
|
58
|
+
|
59
|
+
if component_scaling is not None:
|
60
|
+
c = np.tile(np.array(component_scaling), int(np.round(S.shape[0]/len(component_scaling))))
|
61
|
+
scale_mat = c[np.newaxis,:].T @ c[np.newaxis, :]
|
62
|
+
for k in range(S.shape[2]):
|
63
|
+
S[:,:,k] = scale_mat * S[:,:,k]
|
64
|
+
|
65
|
+
B = spectrum_to_process(S, reg_factor=reg_factor, zero_limit=zero_limit)
|
66
|
+
B = interp1d(omega, B, axis=2, fill_value=0, kind=interpolation_kind, bounds_error=False)
|
67
|
+
|
68
|
+
n_dofs = S.shape[0]
|
69
|
+
|
70
|
+
if fs is None:
|
71
|
+
omega_max = omega[-1]
|
72
|
+
else:
|
73
|
+
omega_max = fs*(np.pi*2)
|
74
|
+
if omega_max>omega[-1] and print_status:
|
75
|
+
print('fs too high (larger than largest omega value in input) - zero padding automatically enforced')
|
76
|
+
|
77
|
+
if tmax is None:
|
78
|
+
domega = omega[1]-omega[0]
|
79
|
+
else:
|
80
|
+
domega = np.pi*2/tmax
|
81
|
+
|
82
|
+
omega = np.arange(omega[0], omega_max+domega, domega)
|
83
|
+
|
84
|
+
# Summation
|
85
|
+
p = np.zeros([n_dofs, len(omega)])
|
86
|
+
Bi = B(omega)
|
87
|
+
|
88
|
+
if phase_angles is None:
|
89
|
+
alpha = 2*np.pi * np.random.rand(n_dofs, len(omega))
|
90
|
+
else:
|
91
|
+
alpha = phase_angles*1
|
92
|
+
|
93
|
+
for j in range(n_dofs):
|
94
|
+
for m in range(j+1):
|
95
|
+
p[j, :] = p[j, :] + (np.sqrt(2*domega) *
|
96
|
+
np.real(np.fft.fft(Bi[j, m, :] * np.exp(1j*alpha[m, :]))))
|
97
|
+
|
98
|
+
if component_scaling is not None:
|
99
|
+
p = p/np.tile(c[:, np.newaxis], reps=p.shape[1])
|
100
|
+
|
101
|
+
t = fft_time(omega)
|
102
|
+
|
103
|
+
if return_phase_angles:
|
104
|
+
return p, t, alpha
|
105
|
+
else:
|
106
|
+
return p, t
|
107
|
+
|
108
|
+
|
109
|
+
def simulate_mdof_direct(S, omega, reg_factor=None):
|
110
|
+
B = spectrum_to_process(S, reg_factor)
|
111
|
+
|
112
|
+
# Summation
|
113
|
+
domega = omega[1] - omega[0]
|
114
|
+
|
115
|
+
t = fft_time(omega)
|
116
|
+
p = np.zeros([B.shape[0], len(t)])
|
117
|
+
for j in range(B.shape[0]):
|
118
|
+
|
119
|
+
# Eq. 19 computed below for each j
|
120
|
+
for m in range(j+1):
|
121
|
+
for k, omega_k in enumerate(omega):
|
122
|
+
phi = 2*np.pi * np.random.rand()
|
123
|
+
p[j, :] = p[j, :] + np.sqrt(2*domega) * np.real(B[j, m, k] * np.exp(1j*(omega_k*t + phi)))
|
124
|
+
|
125
|
+
return p, t
|
126
|
+
|
wawi/tools.py
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
def print_progress(t, tmax, length=20, sym='=', postfix='', startstop_sym=' '):
|
4
|
+
progress = t/tmax
|
5
|
+
n_syms = np.floor(progress*length).astype(int)
|
6
|
+
string = "\r[%s%-"+ str(length*len(sym)) +"s%s] %3.0f%%" + postfix
|
7
|
+
print(string % (startstop_sym,sym*int(n_syms), startstop_sym, progress*100), end='')
|