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/__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='')
|