wawi 0.0.8__py3-none-any.whl → 0.0.9__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.
Potentially problematic release.
This version of wawi might be problematic. Click here for more details.
- examples/3 Software interfacing/Abaqus model export/bergsoysund-export.py +72 -0
- examples/3 Software interfacing/Abaqus model export/test.py +67 -0
- tests/test_IABSE_step11a.py +217 -0
- tests/test_IABSE_step11c.py +281 -0
- tests/test_IABSE_step2a.py +263 -0
- tests/test_wind.py +71 -0
- wawi/__init__.py +1 -1
- wawi/ext/__init__.py +0 -0
- wawi/ext/abq.py +259 -0
- wawi/ext/abq_legacy.py +462 -0
- wawi/ext/ansys.py +0 -0
- wawi/ext/orcaflex.py +0 -0
- wawi/ext/sofistik.py +0 -0
- wawi/model/__init__.py +11 -0
- wawi/model/_aero.py +363 -0
- wawi/model/_dry.py +141 -0
- wawi/model/_hydro.py +882 -0
- wawi/model/_model.py +1324 -0
- wawi/model/_screening.py +124 -0
- {wawi-0.0.8.dist-info → wawi-0.0.9.dist-info}/METADATA +1 -1
- wawi-0.0.9.dist-info/RECORD +39 -0
- wawi-0.0.9.dist-info/top_level.txt +3 -0
- wawi-0.0.8.dist-info/RECORD +0 -21
- wawi-0.0.8.dist-info/top_level.txt +0 -1
- {wawi-0.0.8.dist-info → wawi-0.0.9.dist-info}/LICENSE +0 -0
- {wawi-0.0.8.dist-info → wawi-0.0.9.dist-info}/WHEEL +0 -0
wawi/model/_hydro.py
ADDED
@@ -0,0 +1,882 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import json
|
3
|
+
from inspect import isfunction
|
4
|
+
from scipy.interpolate import interp1d
|
5
|
+
|
6
|
+
from wawi.general import blkdiag, interp1d_angular
|
7
|
+
from wawi.wave import maxincrement, dispersion_relation_scalar, xsim
|
8
|
+
import wawi.wave
|
9
|
+
import pyvista as pv
|
10
|
+
from scipy.linalg import block_diag
|
11
|
+
|
12
|
+
'''
|
13
|
+
HYDRO SUBMODULE
|
14
|
+
'''
|
15
|
+
class Hydro:
|
16
|
+
def __init__(self, pontoons=None, phi_key='hydro', environment=None, phases_lead=False, seastate=None):
|
17
|
+
self.pontoons = pontoons
|
18
|
+
self.phi_key = phi_key
|
19
|
+
self._phases_lead = False
|
20
|
+
self._phi = None
|
21
|
+
self.seastate = seastate
|
22
|
+
|
23
|
+
if environment is None:
|
24
|
+
self.environment = Environment()
|
25
|
+
|
26
|
+
self.assign_environment()
|
27
|
+
|
28
|
+
@property
|
29
|
+
def phases_lead(self):
|
30
|
+
return self._phases_lead
|
31
|
+
|
32
|
+
@phases_lead.setter
|
33
|
+
def phases_lead(self, val):
|
34
|
+
self._phases_lead = val
|
35
|
+
self.assign_to_pontoon_types(phases_lead=val)
|
36
|
+
|
37
|
+
@property
|
38
|
+
def phi(self):
|
39
|
+
if self._phi is not None:
|
40
|
+
return self._phi
|
41
|
+
else:
|
42
|
+
return np.eye(self.ndofs)
|
43
|
+
|
44
|
+
@phi.setter
|
45
|
+
def phi(self, phi):
|
46
|
+
self._phi = phi
|
47
|
+
|
48
|
+
|
49
|
+
@property
|
50
|
+
def pontoon_types(self):
|
51
|
+
ptypes = [p.pontoon_type for p in self.pontoons]
|
52
|
+
unique_types = []
|
53
|
+
for pt in ptypes:
|
54
|
+
if pt not in unique_types:
|
55
|
+
unique_types.append(pt)
|
56
|
+
return unique_types
|
57
|
+
|
58
|
+
|
59
|
+
def reset_linearized_drag(self):
|
60
|
+
for p in self.pontoons:
|
61
|
+
p.Cquad_lin = np.diag(np.zeros(6))
|
62
|
+
|
63
|
+
def assign_direct_cquad(self, cquad_dict=None):
|
64
|
+
for p in self.pontoons:
|
65
|
+
p.Cquad = np.zeros(6)
|
66
|
+
|
67
|
+
if cquad_dict is None:
|
68
|
+
cquad_dict = {}
|
69
|
+
|
70
|
+
for pontoon_label in cquad_dict:
|
71
|
+
self.get_pontoon(pontoon_label).Cquad = cquad_dict[pontoon_label]
|
72
|
+
|
73
|
+
def assign_environment(self):
|
74
|
+
for pontoon in self.pontoons:
|
75
|
+
pontoon.environment = self.environment
|
76
|
+
|
77
|
+
def assign_to_pontoons(self, **kwargs):
|
78
|
+
for p in self.pontoons:
|
79
|
+
for k, v in kwargs.items():
|
80
|
+
setattr(p, k, v)
|
81
|
+
|
82
|
+
def assign_to_pontoon_types(self, **kwargs):
|
83
|
+
for pt in self.pontoon_types:
|
84
|
+
for k, v in kwargs.items():
|
85
|
+
setattr(pt, k, v)
|
86
|
+
|
87
|
+
def get_all_cquad(self, local=False):
|
88
|
+
|
89
|
+
if local:
|
90
|
+
cquad = [p.cquad_local for p in self.pontoons]
|
91
|
+
else:
|
92
|
+
cquad = [p.cquad for p in self.pontoons]
|
93
|
+
|
94
|
+
return block_diag(*cquad)
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
def get_all_Cd(self):
|
99
|
+
Cd = np.zeros(6*len(self.pontoons))
|
100
|
+
for ix, p in enumerate(self.pontoons):
|
101
|
+
Cd[ix*6:ix*6+6] = p.Cd
|
102
|
+
|
103
|
+
return Cd
|
104
|
+
|
105
|
+
def get_all_area(self):
|
106
|
+
area = np.zeros(6*len(self.pontoons))
|
107
|
+
for ix, p in enumerate(self.pontoons):
|
108
|
+
area[ix*6:ix*6+6] = p.area
|
109
|
+
|
110
|
+
return area
|
111
|
+
|
112
|
+
|
113
|
+
def get_from_all(self, par):
|
114
|
+
vals = []
|
115
|
+
for p in self.pontoons:
|
116
|
+
vals.append(getattr(p, par))
|
117
|
+
|
118
|
+
return vals
|
119
|
+
|
120
|
+
def get_pontoon(self, label):
|
121
|
+
matching_pontoons = [p for p in self.pontoons if p.label == label]
|
122
|
+
if len(matching_pontoons)>1:
|
123
|
+
raise ValueError('Multiple matching pontoons')
|
124
|
+
elif len(matching_pontoons)==0:
|
125
|
+
return None
|
126
|
+
else:
|
127
|
+
return matching_pontoons[0]
|
128
|
+
|
129
|
+
|
130
|
+
@property
|
131
|
+
def nodelabels(self):
|
132
|
+
return np.array([pont.node.label for pont in self.pontoons])
|
133
|
+
|
134
|
+
@property
|
135
|
+
def ndofs(self):
|
136
|
+
return len(self.pontoons)*6
|
137
|
+
|
138
|
+
'''
|
139
|
+
ENVIRONMENT CLASS
|
140
|
+
'''
|
141
|
+
class Environment:
|
142
|
+
def __init__(self, g=9.80655, rho=1.025e3, depth=np.inf, waterlevel=0):
|
143
|
+
self.g = g
|
144
|
+
self.rho = rho
|
145
|
+
self.depth = depth
|
146
|
+
self.waterlevel = waterlevel
|
147
|
+
|
148
|
+
|
149
|
+
'''
|
150
|
+
PONTOON CLASS
|
151
|
+
'''
|
152
|
+
|
153
|
+
class Pontoon:
|
154
|
+
def __init__(self, node, pontoon_type, rotation=0, label='pontoon',
|
155
|
+
current_affects_Q=None, seastate=None, environment=None, current_affects_k=None):
|
156
|
+
|
157
|
+
self.node = node
|
158
|
+
self.pontoon_type = pontoon_type
|
159
|
+
self.rotation = rotation
|
160
|
+
|
161
|
+
self.label = label
|
162
|
+
|
163
|
+
self.seastate = seastate
|
164
|
+
self.environment = environment
|
165
|
+
self._current_affects_Q = current_affects_Q
|
166
|
+
self._current_affects_k = current_affects_k
|
167
|
+
|
168
|
+
self.Cquad = np.zeros(6) # local, direct quadratic damping
|
169
|
+
self._Cd = None
|
170
|
+
|
171
|
+
|
172
|
+
def __repr__(self):
|
173
|
+
return f'Pontoon object: {self.label}'
|
174
|
+
def __str__(self):
|
175
|
+
return f'Pontoon object: {self.label} at {self.node}'
|
176
|
+
|
177
|
+
def sea_get(self, parameter):
|
178
|
+
return self.seastate.get_at_pos(parameter, self.node.x[0], self.node.x[1])
|
179
|
+
|
180
|
+
|
181
|
+
@property
|
182
|
+
def current_affects_Q(self):
|
183
|
+
if self._current_affects_Q is not None:
|
184
|
+
return self._current_affects_Q
|
185
|
+
elif (self.seastate is not None) and ('current_affects_Q' in self.seastate.pontoon_options):
|
186
|
+
return self.seastate.pontoon_options['current_affects_Q']
|
187
|
+
|
188
|
+
@current_affects_Q.setter
|
189
|
+
def current_affects_Q(self, val):
|
190
|
+
self._current_affects_Q = val
|
191
|
+
|
192
|
+
@property
|
193
|
+
def current_affects_k(self):
|
194
|
+
if self._current_affects_k is not None:
|
195
|
+
return self._current_affects_k
|
196
|
+
elif (self.seastate is not None) and ('current_affects_k' in self.seastate.pontoon_options):
|
197
|
+
return self.seastate.pontoon_options['current_affects_k']
|
198
|
+
|
199
|
+
@current_affects_k.setter
|
200
|
+
def current_affects_k(self, val):
|
201
|
+
self._current_affects_k = val
|
202
|
+
|
203
|
+
@property
|
204
|
+
def Cquad_lin(self):
|
205
|
+
if hasattr(self, '_Cquad_lin'):
|
206
|
+
return self._Cquad_lin
|
207
|
+
else:
|
208
|
+
return np.zeros([6, 6])
|
209
|
+
|
210
|
+
@Cquad_lin.setter
|
211
|
+
def Cquad_lin(self, Cquad_lin):
|
212
|
+
self._Cquad_lin = Cquad_lin
|
213
|
+
|
214
|
+
@property
|
215
|
+
def tmat(self):
|
216
|
+
return self.get_tmat_from_rotation()
|
217
|
+
|
218
|
+
@property
|
219
|
+
def cquad(self):
|
220
|
+
T = self.tmat
|
221
|
+
return T.T @ self.cquad_local @ T
|
222
|
+
|
223
|
+
@property
|
224
|
+
def cquad_local(self):
|
225
|
+
rho = self.environment.rho
|
226
|
+
|
227
|
+
# Directly specified C_quad
|
228
|
+
if hasattr(self, 'Cquad'): #to ensure backwards compatibility
|
229
|
+
Cquad = self.Cquad
|
230
|
+
else:
|
231
|
+
Cquad = np.zeros(6)
|
232
|
+
|
233
|
+
# C_quad from drag definitions
|
234
|
+
Cquad_Cd = 0.5 * rho * self.Cd * self.area
|
235
|
+
|
236
|
+
return np.diag(Cquad_Cd + Cquad)
|
237
|
+
|
238
|
+
|
239
|
+
@property
|
240
|
+
def Cd(self):
|
241
|
+
if hasattr(self, '_Cd') and self._Cd is not None:
|
242
|
+
val = self._Cd
|
243
|
+
else:
|
244
|
+
val = self.pontoon_type.Cd
|
245
|
+
|
246
|
+
if len(val)==3:
|
247
|
+
val = np.hstack([val, [0,0,0]])
|
248
|
+
else:
|
249
|
+
val = np.array(val)
|
250
|
+
|
251
|
+
return val
|
252
|
+
|
253
|
+
@Cd.setter
|
254
|
+
def Cd(self, Cd):
|
255
|
+
self._Cd = Cd
|
256
|
+
|
257
|
+
@property
|
258
|
+
def area(self):
|
259
|
+
val = self.pontoon_type.area
|
260
|
+
|
261
|
+
if len(val)==3:
|
262
|
+
val = np.hstack([val, [0, 0, 0]])
|
263
|
+
else:
|
264
|
+
val = np.array(val)
|
265
|
+
|
266
|
+
return val
|
267
|
+
|
268
|
+
# To simplify expressions inside class:
|
269
|
+
@property
|
270
|
+
def S(self):
|
271
|
+
return self.sea_get('S')
|
272
|
+
|
273
|
+
@property
|
274
|
+
def D(self):
|
275
|
+
return self.sea_get('D')
|
276
|
+
|
277
|
+
@property
|
278
|
+
def U(self):
|
279
|
+
return self.sea_get('U')
|
280
|
+
|
281
|
+
@property
|
282
|
+
def thetaU(self):
|
283
|
+
return self.sea_get('thetaU')
|
284
|
+
|
285
|
+
@property
|
286
|
+
def depth(self):
|
287
|
+
return self.sea_get('depth')
|
288
|
+
|
289
|
+
@property
|
290
|
+
def theta0(self):
|
291
|
+
return self.sea_get('theta0')
|
292
|
+
|
293
|
+
@property
|
294
|
+
def theta_shift(self):
|
295
|
+
return self.sea_get('theta_shift')
|
296
|
+
|
297
|
+
def doppler_shift_omega(self, omega_k, theta, kappa=None):
|
298
|
+
if kappa is None:
|
299
|
+
kappa = self.get_kappa(omega_k, theta=theta, U=0.0)
|
300
|
+
|
301
|
+
omega_shifted = theta*0.0
|
302
|
+
|
303
|
+
for ix, kappai in enumerate(kappa):
|
304
|
+
thetai_abs = theta[ix] + self.theta_shift
|
305
|
+
Uproj = self.U * np.cos(self.thetaU - thetai_abs)
|
306
|
+
omega_shifted[ix] = omega_k - Uproj * kappai
|
307
|
+
|
308
|
+
return omega_shifted
|
309
|
+
|
310
|
+
|
311
|
+
def get_kappa(self, omega_k, theta=0.0, U=None):
|
312
|
+
if not self.current_affects_k:
|
313
|
+
U = 0.0
|
314
|
+
elif U is None:
|
315
|
+
U = self.U*1.0
|
316
|
+
|
317
|
+
if U == 0.0:
|
318
|
+
kappa = dispersion_relation_scalar(omega_k, self.depth,
|
319
|
+
g=self.environment.g)
|
320
|
+
|
321
|
+
if np.ndim(theta)!=0:
|
322
|
+
kappa = kappa*np.ones(len(theta))
|
323
|
+
else:
|
324
|
+
kappa = theta*0.0
|
325
|
+
for ix, thetai in enumerate(theta):
|
326
|
+
thetai_abs = thetai + self.theta_shift
|
327
|
+
Uproj = U * np.cos(self.thetaU - thetai_abs)
|
328
|
+
kappa[ix] = dispersion_relation_scalar(omega_k, h=self.depth,
|
329
|
+
g=self.environment.g,
|
330
|
+
U=Uproj)
|
331
|
+
|
332
|
+
return kappa
|
333
|
+
|
334
|
+
def get_n(self):
|
335
|
+
return np.array([np.cos(self.rotation), np.sin(self.rotation), 0])
|
336
|
+
|
337
|
+
def get_tmat_from_rotation(self, e2=np.array([0, 0, 1])):
|
338
|
+
n = self.get_n()
|
339
|
+
e1 = np.cross(e2, n)
|
340
|
+
return blkdiag(np.vstack([n, e1, e2]), 2)
|
341
|
+
|
342
|
+
|
343
|
+
def get_K(self, omega, local=False):
|
344
|
+
if local:
|
345
|
+
T = np.eye(6)
|
346
|
+
else:
|
347
|
+
T = self.tmat
|
348
|
+
|
349
|
+
if self.pontoon_type.K is not None:
|
350
|
+
return T.T @ self.pontoon_type.K(omega) @ T
|
351
|
+
else:
|
352
|
+
return T*0
|
353
|
+
|
354
|
+
|
355
|
+
def get_C(self, omega, local=False):
|
356
|
+
if local:
|
357
|
+
T = np.eye(6)
|
358
|
+
else:
|
359
|
+
T = self.tmat
|
360
|
+
|
361
|
+
|
362
|
+
return T.T @ (self.pontoon_type.C(omega)) @ T
|
363
|
+
|
364
|
+
|
365
|
+
def get_M(self, omega, local=False):
|
366
|
+
if local:
|
367
|
+
T = np.eye(6)
|
368
|
+
else:
|
369
|
+
T = self.tmat
|
370
|
+
|
371
|
+
return T.T @ self.pontoon_type.M(omega) @ T
|
372
|
+
|
373
|
+
|
374
|
+
def get_Q0_legacy(self, omega_k, local=False):
|
375
|
+
if local:
|
376
|
+
T = np.eye(6)
|
377
|
+
else:
|
378
|
+
T = self.tmat
|
379
|
+
|
380
|
+
return T.T @ self.pontoon_type.Q(omega_k)
|
381
|
+
|
382
|
+
|
383
|
+
def get_Q0(self, omega, local=False):
|
384
|
+
if local:
|
385
|
+
T = np.eye(6)
|
386
|
+
else:
|
387
|
+
T = self.tmat
|
388
|
+
|
389
|
+
if np.ndim(omega)==0: #omega is scalar
|
390
|
+
return T.T @ self.pontoon_type.Q(omega)
|
391
|
+
else:
|
392
|
+
Qeval = self.pontoon_type.Q(omega)
|
393
|
+
for k in range(len(omega)):
|
394
|
+
Qeval[:,:,k] = T.T @ Qeval[:,:,k]
|
395
|
+
|
396
|
+
return Qeval
|
397
|
+
|
398
|
+
|
399
|
+
def get_Q(self, omega_k, theta, local=False, theta_interpolation='quadratic'):
|
400
|
+
if (not self.current_affects_Q) or (self.U == 0.0): #omega is scalar
|
401
|
+
Qk = interp1d_angular(self.pontoon_type.theta + self.rotation - self.theta_shift,
|
402
|
+
self.get_Q0(omega_k, local=local), axis=1, fill_value=0,
|
403
|
+
kind=theta_interpolation, bounds_error=False,
|
404
|
+
assume_sorted=False)(theta)
|
405
|
+
return Qk
|
406
|
+
else:
|
407
|
+
omega = self.doppler_shift_omega(omega_k, theta)
|
408
|
+
Q0 = self.get_Q0(omega, local=local)
|
409
|
+
Qk = np.zeros([6, len(theta)]).astype(complex)
|
410
|
+
for ix in range(len(theta)): # TODO: first implementation for loop - will make quicker by avoiding loop later
|
411
|
+
Qk[:, ix] = interp1d_angular(self.pontoon_type.theta + self.rotation - self.theta_shift,
|
412
|
+
Q0[:, :, ix], axis=1, fill_value=0,
|
413
|
+
kind=theta_interpolation, bounds_error=False,
|
414
|
+
assume_sorted=False)(theta[ix])
|
415
|
+
return Qk
|
416
|
+
|
417
|
+
|
418
|
+
def evaluate_Q(self, omega=None, theta=None,
|
419
|
+
theta_interpolation='quadratic', local=False):
|
420
|
+
|
421
|
+
if omega is None:
|
422
|
+
omega = self.pontoon_type.original_omegaQ
|
423
|
+
|
424
|
+
if theta is None:
|
425
|
+
theta = self.pontoon_type.theta
|
426
|
+
|
427
|
+
if np.ndim(theta)==0:
|
428
|
+
theta = np.array([theta])
|
429
|
+
|
430
|
+
Q = np.zeros([6, len(theta), len(omega)]).astype('complex')
|
431
|
+
|
432
|
+
for k, omega_k in enumerate(omega):
|
433
|
+
Q[:, :, k] = self.get_Q(omega_k, theta,
|
434
|
+
local=local, theta_interpolation=theta_interpolation)
|
435
|
+
|
436
|
+
return Q, theta, omega
|
437
|
+
|
438
|
+
|
439
|
+
def get_coh_2d(self, omega_k, theta, x0=[0,0], sign=-1):
|
440
|
+
kappa = self.get_kappa(omega_k, theta=theta)
|
441
|
+
|
442
|
+
# Minus sign in coh confirmed matching with phases lags in OrcaFlex
|
443
|
+
|
444
|
+
coh = np.exp(self.pontoon_type.phase_sign*1j*kappa* ((self.node.x[0]-x0[0])*np.cos(theta+self.theta_shift) +
|
445
|
+
(self.node.x[1]-x0[1])*np.sin(theta+self.theta_shift)))
|
446
|
+
return coh
|
447
|
+
|
448
|
+
|
449
|
+
def get_Z(self, omega_k, theta_int, theta_interpolation='quadratic',
|
450
|
+
local=False, x0=[0,0]):
|
451
|
+
|
452
|
+
if theta_int is None: # => long-crested
|
453
|
+
theta_int = np.array([self.theta0 - self.theta_shift])
|
454
|
+
|
455
|
+
if self.D.__code__.co_argcount==2: # count number of inputs
|
456
|
+
D = self.D(theta_int, omega_k)
|
457
|
+
else:
|
458
|
+
D = self.D(theta_int)
|
459
|
+
|
460
|
+
# Interpolate hydrodynamic transfer function, get 2d coh and est. Z
|
461
|
+
Q_int = self.get_Q(omega_k, theta_int, local=local,
|
462
|
+
theta_interpolation=theta_interpolation)
|
463
|
+
|
464
|
+
coh_2d = self.get_coh_2d(omega_k, theta_int, x0=x0)
|
465
|
+
Z = np.sqrt(self.S(omega_k)) * Q_int * np.tile(np.sqrt(D), [6, 1]) * np.tile(coh_2d, [6, 1])
|
466
|
+
|
467
|
+
return Z
|
468
|
+
|
469
|
+
def get_theta_int(self, omega_k, ds=None, max_rel_error=0.01):
|
470
|
+
if ds is None:
|
471
|
+
ds = self.max_distance
|
472
|
+
|
473
|
+
dtheta = np.min([maxincrement(ds, omega_k, 0, 2*np.pi, max_rel_error), 2*np.pi/20]) #minimum 20 divisions
|
474
|
+
|
475
|
+
return np.arange(-np.pi, np.pi, dtheta)
|
476
|
+
|
477
|
+
|
478
|
+
'''
|
479
|
+
SEASTATE CLASS
|
480
|
+
'''
|
481
|
+
|
482
|
+
class Seastate:
|
483
|
+
def __init__(self, Tp, Hs, gamma, theta0, s, depth=None, origin=None, ranges=None,
|
484
|
+
options={}, pontoon_options={}, name=None, plot_label=None, centered_dirdist=True,
|
485
|
+
U=None, thetaU=None, use_robust_D=True, angle_unit='deg'):
|
486
|
+
|
487
|
+
if angle_unit == 'deg':
|
488
|
+
ang_conv = np.pi/180
|
489
|
+
|
490
|
+
if origin is None:
|
491
|
+
self.origin = np.array([0,0])
|
492
|
+
else:
|
493
|
+
self.origin = origin
|
494
|
+
|
495
|
+
if depth is None:
|
496
|
+
self.depth = lambda x,y: np.inf
|
497
|
+
|
498
|
+
if ranges is None:
|
499
|
+
self.ranges = dict(x=[-np.inf, np.inf], y=[-np.inf, np.inf])
|
500
|
+
else:
|
501
|
+
self.ranges = ranges
|
502
|
+
|
503
|
+
self.name = name
|
504
|
+
if plot_label is None:
|
505
|
+
self.plot_label = name
|
506
|
+
else:
|
507
|
+
self.plot_label = plot_label
|
508
|
+
|
509
|
+
self.pars = ['Tp', 'Hs', 'gamma', 'theta0', 's']
|
510
|
+
self.pars_pretty = dict(Tp='$T_p$ [s]', Hs='$H_s$ [m]', gamma=r'$\gamma$', theta0=r'$\theta_0$ $[^\circ]$', s='s')
|
511
|
+
|
512
|
+
if s is None:
|
513
|
+
s = np.inf
|
514
|
+
if U is None:
|
515
|
+
U = 0.0
|
516
|
+
if thetaU is None:
|
517
|
+
thetaU = 0.0
|
518
|
+
|
519
|
+
self.Tp = Tp
|
520
|
+
self.Hs = Hs
|
521
|
+
self.gamma = gamma
|
522
|
+
self.s = s
|
523
|
+
self.theta0 = self.scale(theta0, ang_conv)
|
524
|
+
self.centered_dirdist = centered_dirdist
|
525
|
+
self.use_robust_D = use_robust_D
|
526
|
+
|
527
|
+
self.U = U
|
528
|
+
self.thetaU = self.scale(thetaU, ang_conv)
|
529
|
+
|
530
|
+
self.x0 = self.origin[0]
|
531
|
+
self.y0 = self.origin[1]
|
532
|
+
self.options = {'keep_coherence': True}
|
533
|
+
self.options.update(**options)
|
534
|
+
|
535
|
+
self.pontoon_options = {'current_affects_Q': True, 'current_affects_k': True}
|
536
|
+
self.pontoon_options.update(**pontoon_options)
|
537
|
+
|
538
|
+
self.fun_pars = [par for par in self.pars if self.isfun(par)]
|
539
|
+
|
540
|
+
|
541
|
+
def __str__(self):
|
542
|
+
string = f'''\
|
543
|
+
WAWI SeaState
|
544
|
+
--------------
|
545
|
+
Hs={self.Hs:.2f}m
|
546
|
+
Tp={self.Tp:.2f}s
|
547
|
+
theta0={self.theta0*180/np.pi:.1f}deg
|
548
|
+
gamma={self.gamma:.2f}
|
549
|
+
s={self.s:.1f}
|
550
|
+
U (current)={self.U:.1f}m/s
|
551
|
+
thetaU (current)={self.thetaU*180/np.pi:.1f}deg
|
552
|
+
'''
|
553
|
+
|
554
|
+
return string
|
555
|
+
|
556
|
+
|
557
|
+
@property
|
558
|
+
def homogeneous(self):
|
559
|
+
if len(self.fun_pars)==0:
|
560
|
+
return True
|
561
|
+
else:
|
562
|
+
return False
|
563
|
+
|
564
|
+
@property
|
565
|
+
def theta_shift(self):
|
566
|
+
if self.centered_dirdist:
|
567
|
+
return self.theta0
|
568
|
+
else:
|
569
|
+
return 0.0
|
570
|
+
|
571
|
+
@property
|
572
|
+
def short_crested(self):
|
573
|
+
return self.s not in [np.inf, None]
|
574
|
+
|
575
|
+
@property
|
576
|
+
def D(self):
|
577
|
+
if not self.short_crested:
|
578
|
+
return lambda x, y: lambda theta: 1*(theta==self.evpar('theta0', x, y)-self.evpar('theta_shift',x,y))
|
579
|
+
else:
|
580
|
+
if self.use_robust_D:
|
581
|
+
Dfun = wawi.wave.dirdist_robust
|
582
|
+
else:
|
583
|
+
Dfun = wawi.wave.dirdist_decimal
|
584
|
+
|
585
|
+
return lambda x,y: Dfun(self.evpar('s', x, y), theta0=self.evpar('theta0', x, y)-self.evpar('theta_shift',x,y))
|
586
|
+
|
587
|
+
|
588
|
+
@property
|
589
|
+
def S(self):
|
590
|
+
return lambda x,y: wawi.wave.jonswap(self.evpar('Hs', x, y),
|
591
|
+
self.evpar('Tp', x, y), self.evpar('gamma', x, y))
|
592
|
+
|
593
|
+
@property
|
594
|
+
def theta_int(self):
|
595
|
+
if self.short_crested == False:
|
596
|
+
th = np.array([self.theta0 - self.theta_shift])
|
597
|
+
elif 'truncate_theta' in self.options:
|
598
|
+
theta_lims = np.array(self.options['truncate_theta'])
|
599
|
+
dtheta = self.options['dtheta']
|
600
|
+
th = np.arange(theta_lims[0], theta_lims[1]+dtheta, dtheta)
|
601
|
+
else:
|
602
|
+
th = None
|
603
|
+
|
604
|
+
return th
|
605
|
+
|
606
|
+
def simulate(self, x, y, omega, fs=None, theta=None, phase=None, return_phases=False,
|
607
|
+
print_progress=True, time_history=True, grid_mode=True):
|
608
|
+
|
609
|
+
if theta is None:
|
610
|
+
theta = self.theta_int
|
611
|
+
|
612
|
+
output = xsim(x, y, self.S, self.D, omega, theta=theta, fs=fs,
|
613
|
+
grid_mode=grid_mode, print_progress=print_progress, time_history=time_history,
|
614
|
+
phase=phase, return_phases=return_phases, theta_shift=self.theta_shift)
|
615
|
+
|
616
|
+
if return_phases:
|
617
|
+
eta, t, phase = output
|
618
|
+
return eta, t, phase
|
619
|
+
else:
|
620
|
+
eta, t = output
|
621
|
+
return eta, t
|
622
|
+
|
623
|
+
|
624
|
+
@staticmethod
|
625
|
+
def scale(par, factor):
|
626
|
+
if isfunction(par):
|
627
|
+
if 'x' in par.__code__.co_varnames and 'y' in par.__code__.co_varnames:
|
628
|
+
return lambda x,y: par(x, y)*factor
|
629
|
+
elif 'x' in par.__code__.co_varnames:
|
630
|
+
return lambda x: par(x)*factor
|
631
|
+
elif 'y' in par.__code__.co_varnames:
|
632
|
+
return lambda y: par(y)*factor
|
633
|
+
else:
|
634
|
+
return par*factor
|
635
|
+
|
636
|
+
@classmethod
|
637
|
+
def from_json(cls, json_file, **kwargs):
|
638
|
+
with open(json_file, 'r') as fileobj:
|
639
|
+
data = json.load(fileobj)
|
640
|
+
|
641
|
+
Tp = eval(data['S']['Tp'])
|
642
|
+
Hs = eval(data['S']['Hs'])
|
643
|
+
gamma = eval(data['S']['gamma'])
|
644
|
+
|
645
|
+
theta0 = eval(data['D']['theta0']) #assumed in deg
|
646
|
+
s = eval(data['D']['s'])
|
647
|
+
|
648
|
+
if 'current' in data:
|
649
|
+
U = eval(data['current']['U'])
|
650
|
+
thetaU = eval(data['current']['thetaU'])
|
651
|
+
else:
|
652
|
+
U = None
|
653
|
+
thetaU = None
|
654
|
+
|
655
|
+
if 'options' in data:
|
656
|
+
options = data['options']
|
657
|
+
else:
|
658
|
+
options = {}
|
659
|
+
|
660
|
+
if 'pontoon_options' in data:
|
661
|
+
pontoon_options = data['pontoon_options']
|
662
|
+
else:
|
663
|
+
pontoon_options = {}
|
664
|
+
|
665
|
+
if 'origin' in data:
|
666
|
+
origin = np.array(data['origin'])
|
667
|
+
else:
|
668
|
+
origin = np.array([0,0])
|
669
|
+
|
670
|
+
if 'ranges' in data:
|
671
|
+
ranges = data['ranges']
|
672
|
+
if 'x' not in ranges:
|
673
|
+
ranges['x'] = [-np.inf, np.inf]
|
674
|
+
if 'y' not in ranges:
|
675
|
+
ranges['y'] = [-np.inf, np.inf]
|
676
|
+
else:
|
677
|
+
ranges = None
|
678
|
+
|
679
|
+
if 'name' in data:
|
680
|
+
name = data['name']
|
681
|
+
else:
|
682
|
+
name = 'Unnamed seastate'
|
683
|
+
|
684
|
+
if 'plot_label' in data:
|
685
|
+
plot_label = data['plot_label']
|
686
|
+
else:
|
687
|
+
plot_label = None
|
688
|
+
|
689
|
+
if 'centered_dirdist' in options:
|
690
|
+
centered_dirdist = options.pop('centered_dirdist')
|
691
|
+
else:
|
692
|
+
centered_dirdist = True
|
693
|
+
|
694
|
+
return cls(Tp, Hs, gamma, theta0, s, origin=origin, ranges=ranges,
|
695
|
+
name=name, plot_label=plot_label, options=options,
|
696
|
+
U=U, thetaU=thetaU, pontoon_options=pontoon_options,
|
697
|
+
centered_dirdist=centered_dirdist,
|
698
|
+
**kwargs)
|
699
|
+
|
700
|
+
|
701
|
+
def evpar(self, parameter, x, y):
|
702
|
+
|
703
|
+
# Constant extrapolate if outside borders
|
704
|
+
if x>np.max(self.ranges['x']): x = np.max(self.ranges['x'])
|
705
|
+
if x<np.min(self.ranges['x']): x = np.min(self.ranges['x'])
|
706
|
+
if y>np.max(self.ranges['y']): y = np.max(self.ranges['y'])
|
707
|
+
if y<np.min(self.ranges['y']): y = np.min(self.ranges['y'])
|
708
|
+
|
709
|
+
par = getattr(self, parameter)
|
710
|
+
if isfunction(par):
|
711
|
+
if 'x' in par.__code__.co_varnames and 'y' in par.__code__.co_varnames:
|
712
|
+
return par(x - self.x0, y - self.y0)
|
713
|
+
elif 'x' in par.__code__.co_varnames:
|
714
|
+
return par(x - self.x0)
|
715
|
+
elif 'y' in par.__code__.co_varnames:
|
716
|
+
return par(y - self.y0)
|
717
|
+
else:
|
718
|
+
return par
|
719
|
+
|
720
|
+
def get_at_pos(self, parameter, x, y):
|
721
|
+
par = getattr(self, parameter)
|
722
|
+
if isfunction(par):
|
723
|
+
if 'x' in par.__code__.co_varnames and 'y' in par.__code__.co_varnames:
|
724
|
+
return par(x - self.x0, y - self.y0)
|
725
|
+
elif 'x' in par.__code__.co_varnames:
|
726
|
+
return par(x - self.x0)
|
727
|
+
elif 'y' in par.__code__.co_varnames:
|
728
|
+
return par(y - self.y0)
|
729
|
+
else:
|
730
|
+
return par
|
731
|
+
|
732
|
+
|
733
|
+
def isfun(self, parameter):
|
734
|
+
return isfunction(getattr(self, parameter))
|
735
|
+
|
736
|
+
def with_unit(self, parameter):
|
737
|
+
return self.pars_pretty[parameter]
|
738
|
+
|
739
|
+
|
740
|
+
'''
|
741
|
+
NODE CLASS (MINIMUM CLASS FOR WHEN BEEF MODEL IS NOT APPENDED TO MODEL OBJECT)
|
742
|
+
'''
|
743
|
+
|
744
|
+
class Node:
|
745
|
+
def __init__(self, label, x=0.0, y=0.0, z=0.0):
|
746
|
+
self.x0 = np.array([x,y,z,0.0,0.0,0.0]).astype(float)
|
747
|
+
self.x = np.array([x,y,z,0.0,0.0,0.0]).astype(float)
|
748
|
+
self.label = label
|
749
|
+
|
750
|
+
def __repr__(self):
|
751
|
+
return f'Node {self.label}'
|
752
|
+
|
753
|
+
def __str__(self):
|
754
|
+
return f'Node {self.label}'
|
755
|
+
|
756
|
+
|
757
|
+
'''
|
758
|
+
PONTOONTYPE CLASS
|
759
|
+
'''
|
760
|
+
|
761
|
+
class PontoonType:
|
762
|
+
def __init__(self, K=None, C=None, M=None, Q=None, original_omega=None, original_omegaQ=None,
|
763
|
+
theta=None, label='unnamed', Cd=None, area=None, stl_path=None):
|
764
|
+
self.K = K
|
765
|
+
self.C = C
|
766
|
+
self.M = M
|
767
|
+
self.Q = Q
|
768
|
+
self.original_omega = original_omega
|
769
|
+
self.original_omegaQ = original_omegaQ
|
770
|
+
self.theta = theta
|
771
|
+
self.label = label
|
772
|
+
self.stl_path = stl_path
|
773
|
+
|
774
|
+
self.phases_lead = False
|
775
|
+
|
776
|
+
self.generate_mesh()
|
777
|
+
self._Cd = Cd
|
778
|
+
self._area = area
|
779
|
+
|
780
|
+
def __repr__(self):
|
781
|
+
return f'PontoonType object: {self.label}'
|
782
|
+
def __str__(self):
|
783
|
+
return f'PontoonType object: {self.label}'
|
784
|
+
|
785
|
+
def generate_mesh(self):
|
786
|
+
if self.stl_path is not None:
|
787
|
+
stl = pv.STLReader(self.stl_path)
|
788
|
+
self.mesh = stl.read().translate([0,0,0])
|
789
|
+
else:
|
790
|
+
self.mesh = None
|
791
|
+
|
792
|
+
@property
|
793
|
+
def phase_sign(self):
|
794
|
+
if hasattr(self, 'phases_lead'):
|
795
|
+
return -int(1 - 2*self.phases_lead*1)
|
796
|
+
else:
|
797
|
+
return -1 #standard is phases lag
|
798
|
+
|
799
|
+
@property
|
800
|
+
def Cd(self):
|
801
|
+
if hasattr(self, '_Cd') and self._Cd is not None:
|
802
|
+
return self._Cd
|
803
|
+
else:
|
804
|
+
return np.array([0,0,0,0,0,0])
|
805
|
+
|
806
|
+
|
807
|
+
@Cd.setter
|
808
|
+
def Cd(self, Cd):
|
809
|
+
self._Cd = Cd
|
810
|
+
|
811
|
+
@property
|
812
|
+
def area(self):
|
813
|
+
if hasattr(self, '_area') and self._area is not None:
|
814
|
+
return self._area
|
815
|
+
else:
|
816
|
+
return np.array([0,0,0,0,0,0])
|
817
|
+
|
818
|
+
@area.setter
|
819
|
+
def area(self, area):
|
820
|
+
self._area = area
|
821
|
+
|
822
|
+
|
823
|
+
@classmethod
|
824
|
+
def from_numeric(cls, interpolation_kind='linear', A=None, M0=None, B=None, Kh=None,
|
825
|
+
omega=None, Q=None, theta=None, omegaQ=None, **kwargs):
|
826
|
+
|
827
|
+
if omegaQ is None:
|
828
|
+
omegaQ = omega*1
|
829
|
+
|
830
|
+
if A is None and M0 is None:
|
831
|
+
M = None
|
832
|
+
else:
|
833
|
+
if A is None:
|
834
|
+
A = np.zeros([M0.shape + [len(omega)]])
|
835
|
+
elif M0 is None:
|
836
|
+
M0 = A[:,:,0]*0
|
837
|
+
M = interp1d(omega, (A.T+M0.T).T, axis=2, fill_value='extrapolate', kind=interpolation_kind)
|
838
|
+
|
839
|
+
if B is None:
|
840
|
+
C = None
|
841
|
+
else:
|
842
|
+
C = interp1d(omega, B, axis=2, fill_value='extrapolate', kind=interpolation_kind)
|
843
|
+
|
844
|
+
if Kh is not None:
|
845
|
+
K = lambda omega: Kh
|
846
|
+
else:
|
847
|
+
K = None
|
848
|
+
|
849
|
+
if Q is not None:
|
850
|
+
Q = interp1d(omegaQ, Q, fill_value=0.0, kind=interpolation_kind, axis=2, bounds_error=False)
|
851
|
+
|
852
|
+
return cls(M=M, C=C, K=K, Q=Q, original_omega=omega, original_omegaQ=omegaQ, theta=theta, **kwargs)
|
853
|
+
|
854
|
+
|
855
|
+
@classmethod
|
856
|
+
def from_wadam(cls, path, interpolation_kind='linear',
|
857
|
+
include=['added mass', 'added damping', 'restoring stiffness', 'inertia'], **kwargs):
|
858
|
+
|
859
|
+
from wawi.io import import_wadam_mat, import_wadam_hydro_transfer
|
860
|
+
|
861
|
+
A, B, Kh, M0, omega = import_wadam_mat(path)
|
862
|
+
omega_Q, theta, Q = import_wadam_hydro_transfer(path)
|
863
|
+
|
864
|
+
if 'added mass' not in include:
|
865
|
+
A = A*0
|
866
|
+
|
867
|
+
if 'added damping' not in include:
|
868
|
+
B = B*0
|
869
|
+
|
870
|
+
if 'restoring stiffness' not in include:
|
871
|
+
Kh = Kh*0
|
872
|
+
|
873
|
+
if 'inertia' not in include:
|
874
|
+
M0 = M0*0
|
875
|
+
|
876
|
+
M = interp1d(omega, (A.T+M0.T).T, axis=2, fill_value='extrapolate', kind=interpolation_kind)
|
877
|
+
C = interp1d(omega, B, axis=2, fill_value='extrapolate', kind=interpolation_kind)
|
878
|
+
K = lambda omega: Kh
|
879
|
+
|
880
|
+
Q = interp1d(omega_Q, Q, fill_value='extrapolate', kind=interpolation_kind, axis=2)
|
881
|
+
|
882
|
+
return cls(M=M, C=C, K=K, Q=Q, original_omega=omega, theta=theta, **kwargs)
|