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