wawi 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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='')