wawi 0.0.7__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.

wawi/model/_model.py ADDED
@@ -0,0 +1,1324 @@
1
+ import numpy as np
2
+ import beef
3
+ from beef import fe
4
+ from copy import deepcopy as copy
5
+ import pyvista as pv
6
+ from beef.rotation import rodrot, R_from_drot
7
+
8
+ from pathlib import Path
9
+
10
+ from ._hydro import Hydro, Pontoon, Environment
11
+
12
+ from wawi.structural import var_from_modal, expmax_from_modal, peakfactor_from_modal
13
+ from wawi.modal import maxreal, normalize_phi, iteig_freq, iteig
14
+ from wawi.general import transform_3dmat, eval_3d_fun, fun_const_sum, eval_3d_fun
15
+ from wawi.structural import freqsim
16
+ from wawi.tools import print_progress as pp
17
+ from wawi.wave import stochastic_linearize, waveaction_fft, harmonic_linearize
18
+ from wawi.wind import windaction, windaction_static
19
+ import dill
20
+
21
+
22
+ import matplotlib.pyplot as plt
23
+
24
+ from scipy.interpolate import interp1d
25
+ from scipy.linalg import block_diag
26
+ from scipy.signal import savgol_filter
27
+
28
+ '''
29
+ RESULTS SUBMODULE
30
+ '''
31
+
32
+ class Results:
33
+ def __init__(self, psi=None, lambd=None, S=None, omega=None, model=None):
34
+ self.psi = psi
35
+ self.lambd = lambd
36
+ self.S = S
37
+ self.omega = omega
38
+ self.include_eig = None
39
+ self.model = model
40
+
41
+ @property
42
+ def mdiag(self):
43
+ Kfun, Cfun, Mfun = self.model.get_system_matrices(self.include_eig)
44
+ m = np.zeros(self.lambd.shape[0])
45
+ for ix, wi in enumerate(self.wd):
46
+ m[ix] = np.real(np.diag(np.real(self.psi.T) @ Mfun(wi) @ np.real(self.psi))[ix])
47
+
48
+ m[m<0] = np.nan
49
+ return m
50
+
51
+ @property
52
+ def kdiag(self):
53
+ Kfun, Cfun, Mfun = self.model.get_system_matrices(self.include_eig)
54
+ k = np.zeros(self.lambd.shape[0])
55
+ for ix, wi in enumerate(self.wd):
56
+ k[ix] = np.real(np.diag(np.real(self.psi.T) @ Kfun(wi) @ np.real(self.psi))[ix])
57
+
58
+ k[k<0] = np.nan
59
+
60
+ return k
61
+
62
+ @property
63
+ def cdiag(self):
64
+ Kfun, Cfun, Mfun = self.model.get_system_matrices(self.include_eig)
65
+ c = np.zeros(self.lambd.shape[0])
66
+ for ix, wi in enumerate(self.wd):
67
+ c[ix] = np.real(np.diag(np.real(self.psi.T) @ Cfun(wi) @ np.real(self.psi))[ix])
68
+
69
+ c[c<0] = np.nan
70
+
71
+ return c
72
+
73
+ @property
74
+ def xi(self):
75
+ if self.lambd is not None:
76
+ return -np.real(self.lambd)/np.abs(self.lambd)
77
+
78
+ @property
79
+ def wn(self):
80
+ if self.lambd is not None:
81
+ return np.abs(self.lambd)
82
+
83
+ @property
84
+ def wd(self):
85
+ if self.lambd is not None:
86
+ return np.abs(np.imag(self.lambd))
87
+
88
+ @property
89
+ def fn(self):
90
+ if self.lambd is not None:
91
+ return np.abs(self.lambd)/2/np.pi
92
+
93
+ @property
94
+ def fd(self):
95
+ if self.lambd is not None:
96
+ return np.abs(np.imag(self.lambd))/2/np.pi
97
+
98
+ @property
99
+ def Tn(self):
100
+ if self.lambd is not None:
101
+ return 1/(np.abs(self.lambd)/2/np.pi)
102
+
103
+ @property
104
+ def Td(self):
105
+ if self.lambd is not None:
106
+ return 1/(np.abs(np.imag(self.lambd))/2/np.pi)
107
+
108
+ '''
109
+ DRAG ELEMENT CLASS
110
+ '''
111
+ class DragElement:
112
+ def __init__(self, element, Cd, D=1.0, rho=1025.0, group=None, eltype=None):
113
+
114
+ self.group = group
115
+ self._D = D
116
+ self._Cd = Cd
117
+ self.element = element
118
+ self.rho = rho
119
+ self._eltype = eltype
120
+
121
+
122
+
123
+ @property
124
+ def eltype(self):
125
+ if hasattr(self, '_eltype') and self._eltype is not None:
126
+ return self._eltype
127
+ elif isinstance(self.element, fe.BeamElement3d):
128
+ return 'beam'
129
+ else:
130
+ return 'bar'
131
+
132
+ @eltype.setter
133
+ def eltype(self, eltype):
134
+ self._eltype = eltype
135
+
136
+
137
+ @property
138
+ def cquad_local(self):
139
+ c = (0.5 * self.Cd * self.rho * self.D)[:3] #Linearized as 0.25 Cd D L sqrt(8/pi) * stdudot according to A Wang- seems to work well for linearized,check nonlin also
140
+ from beef.general import bar_foundation_stiffness, generic_beam_mat
141
+
142
+ if self.eltype == 'bar':
143
+ return bar_foundation_stiffness(self.element.L, *(c*0.5))
144
+ elif self.eltype == 'beam':
145
+ val_dict = {'yy': c[1], 'zz': c[2]}
146
+ return generic_beam_mat(self.element.L, **val_dict)
147
+ elif self.eltype in ['node', 'lumped']:
148
+ C = np.zeros([12,12])
149
+ c = 0.5*c*self.element.L # half on each end
150
+ C[:3,:3] = np.diag(c)
151
+ C[6:6+3,6:6+3] = np.diag(c)
152
+ return C
153
+ else:
154
+ return np.zeros([12,12])
155
+
156
+ @property
157
+ def cquad(self):
158
+ return self.element.tmat.T @ self.cquad_local @ self.element.tmat
159
+
160
+
161
+ def get_cquad_lin(self, var_udot, stochastic=True,
162
+ input_is_local=False, ensure_local_output=False):
163
+ # var_udot is covariance matrix of velocity of nodes
164
+ # local_input: whether or not the var_udot is referring to a local csys - if not, it will be transformed to local
165
+ # prior to C computation
166
+ # local_output: whether or not the output should be local - global output is standard
167
+ if not input_is_local:
168
+ Tin = self.element.tmat
169
+ else:
170
+ Tin = np.eye(12)
171
+
172
+ if not ensure_local_output:
173
+ Tout = self.element.tmat
174
+ else:
175
+ Tout = np.eye(12)
176
+
177
+ if stochastic:
178
+ std_udot = np.sqrt(np.abs(Tin @ var_udot @ Tin.T))
179
+ C = stochastic_linearize(self.cquad_local, std_udot)
180
+ else:
181
+ udot = np.diag(np.sqrt(np.abs(Tin @ var_udot @ Tin.T)))
182
+ C = harmonic_linearize(self.cquad_local, udot)
183
+
184
+ return Tout.T @ C @ Tout
185
+
186
+
187
+ # Get and set methods D and Cd
188
+ @property
189
+ def D(self):
190
+ if np.ndim(self._D) == 0:
191
+ return np.hstack([self._D*np.ones(3), [0, 0, 0]])
192
+ elif len(self._D) == 3:
193
+ return np.hstack([self._D, [0,0,0]])
194
+ else:
195
+ return self._D
196
+
197
+ @D.setter
198
+ def D(self, D):
199
+ self._D = D
200
+
201
+ @property
202
+ def Cd(self):
203
+ if np.ndim(self._Cd) == 0:
204
+ return np.hstack([self._Cd*np.ones(3), [0,0,0]])
205
+ elif len(self._Cd) == 3:
206
+ return np.hstack([self._Cd, [0,0,0]])
207
+ else:
208
+ return self._Cd
209
+
210
+ @Cd.setter
211
+ def Cd(self, Cd):
212
+ self._Cd = Cd
213
+
214
+ def __repr__(self):
215
+ return f'DragElement <{self.group}> [{self.element.label}]'
216
+
217
+ def __str__(self):
218
+ return f'DragElement <{self.group}> [{self.element.label}]'
219
+
220
+ '''
221
+ MODEL CLASS
222
+ '''
223
+
224
+ class Model:
225
+ def __init__(self, hydro=None, aero=None, eldef=None, modal_dry=None, seastate=None, windstate=None,
226
+ n_dry_modes=None, x0_wave=None, phases_lead=False,
227
+ use_multibody=True, avoid_eldef=False, drag_elements={}):
228
+
229
+ self.results = Results(model=self)
230
+ self.hydro = hydro
231
+ self.aero = aero
232
+ self.modal_dry = modal_dry # ModalDry object
233
+ self.eldef = eldef # BEEF ElDef object
234
+ self.f_static = None
235
+
236
+ self.n_dry_modes = n_dry_modes
237
+
238
+ if modal_dry is not None:
239
+ self.modal_dry.n_modes = n_dry_modes
240
+
241
+ if not avoid_eldef and self.eldef is None:
242
+ self.construct_simple_eldef(node_labels=hydro.nodelabels)
243
+
244
+ if self.aero is not None:
245
+ self.assign_windstate(windstate)
246
+
247
+ if self.hydro is not None:
248
+ self.prepare_waveaction()
249
+ self.assign_seastate(seastate)
250
+
251
+ if x0_wave is None:
252
+ x,y = self.get_all_pos()
253
+ x0_wave = np.array([np.mean(x), np.mean(y)])
254
+ self.x0 = x0_wave
255
+
256
+ if modal_dry is not None:
257
+ self.assign_dry_modes()
258
+
259
+ self.use_multibody = use_multibody # use multibody if available
260
+ self.phases_lead = phases_lead # define if phases lead or lag. When lags (lead=False): eta = exp(iwt-kx)
261
+
262
+ if type(drag_elements) == dict:
263
+ self.assign_drag_elements(drag_elements)
264
+ else:
265
+ self.drag_elements = drag_elements
266
+
267
+ self.Cquad_lin = 0.0
268
+
269
+
270
+ @staticmethod #alternative constructor
271
+ def from_wwi(path):
272
+ with open(path, 'rb') as f:
273
+ model = dill.load(f)
274
+
275
+ return model
276
+
277
+
278
+ def assign_drag_elements(self, drag_elements):
279
+ drag_els = [None]*len(drag_elements)
280
+ for ix, group_key in enumerate(drag_elements):
281
+ els = self.eldef.get_els_with_sec(drag_elements[group_key]['sections'])
282
+
283
+ # Filter if elements are specified as well
284
+ if hasattr(drag_elements[group_key], 'elements'):
285
+ els = [el for el in els if el in drag_elements[group_key]['elements']]
286
+
287
+ Cd = drag_elements[group_key]['Cd']
288
+ D = drag_elements[group_key]['D']
289
+
290
+ if 'rho' in drag_elements[group_key] and drag_elements[group_key]['rho'] is not None:
291
+ rho = drag_elements[group_key]['rho']
292
+ else:
293
+ rho = self.hydro.environment.rho
294
+
295
+ if 'eltype' in drag_elements[group_key]:
296
+ eltype = drag_elements[group_key]['eltype']
297
+ else:
298
+ eltype = None
299
+
300
+ drag_els[ix] = [DragElement(el, Cd, D=D, rho=rho, group=group_key, eltype=eltype) for el in els]
301
+
302
+ self.drag_elements = [a for b in drag_els for a in b]
303
+
304
+
305
+ def get_modal_cquad(self, dry=False, psi=None):
306
+ if psi is None:
307
+ if dry:
308
+ psi = np.eye(self.n_modes)
309
+ else:
310
+ psi = self.results.psi
311
+
312
+ c_quad = np.zeros(self.n_modes)
313
+
314
+ if 'full' in self.modal_dry.phi_full and hasattr(self, 'Cquad'):
315
+ phi0 = np.real(self.get_dry_phi(key='full'))
316
+ phi = np.real(phi0 @ psi)
317
+ Cquad_model = self.Cquad
318
+
319
+ for mode in range(self.n_modes):
320
+ phiabs = np.diag(np.abs(phi[:, mode]))
321
+ c_quad[mode] = c_quad[mode] + phi[:,mode].T @ (Cquad_model * phiabs) @ phi[:,mode]
322
+
323
+ if 'hydro' in self.modal_dry.phi_full:
324
+ phi0 = self.get_dry_phi(key='hydro')
325
+ phi = np.real(phi0 @ psi)
326
+ Cquad_pontoons = self.hydro.get_all_cquad()
327
+
328
+ for mode in range(len(c_quad)):
329
+ phiabs = np.diag(np.abs(phi[:, mode]))
330
+ c_quad[mode] = c_quad[mode] + phi[:,mode].T @ (Cquad_pontoons * phiabs) @ phi[:,mode]
331
+
332
+ return c_quad
333
+
334
+ @property
335
+ def tmat_full(self):
336
+ all_tmats = []
337
+ for node in self.eldef.nodes:
338
+ all_tmats.append(self.eldef.get_node_csys(node.label))
339
+ return block_diag(*all_tmats)
340
+
341
+ @property
342
+ def Cquad_lin_pontoons(self): # Only pontoons
343
+ mat = self.initialize_const_matrix()
344
+ for ix, p in enumerate(self.hydro.pontoons):
345
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.Cquad_lin
346
+ return self.hydro.phi.T @ mat @ self.hydro.phi
347
+
348
+
349
+ @property
350
+ def n_modes(self):
351
+ return self.modal_dry.n_modes
352
+
353
+ @n_modes.setter
354
+ def n_modes(self, n):
355
+ self.modal_dry.n_modes = n
356
+ self.assign_dry_modes()
357
+
358
+
359
+ @property
360
+ def Cquad(self): # Only drag elements
361
+ # Requires global representation (phi_full) currently. Consider supporting modal form referring to specific keys.
362
+ # self.local only refers to phi of pontoons
363
+ C = np.zeros([self.eldef.ndofs, self.eldef.ndofs])
364
+
365
+ if (hasattr(self, 'drag_elements')) and (self.drag_elements is not None) and (len(self.drag_elements) > 0):
366
+ for e in self.drag_elements:
367
+ ix = e.element.global_dofs
368
+ C[np.ix_(ix,ix)] = C[np.ix_(ix,ix)] + e.cquad
369
+
370
+ return C
371
+
372
+ def get_Cquad_lin(self, var_udot, local=None, stochastic=True): # Only drag elements
373
+
374
+ if local is None:
375
+ local = self.local*1
376
+
377
+ if not hasattr(self, 'drag_elements') or len(self.drag_elements) == 0:
378
+ return np.zeros([self.eldef.ndofs, self.eldef.ndofs])
379
+ else:
380
+ C = np.zeros([self.eldef.ndofs, self.eldef.ndofs])
381
+
382
+ if np.ndim(var_udot)==1:
383
+ var_udot = np.diag(var_udot)
384
+
385
+ for e in self.drag_elements:
386
+ ix = e.element.global_dofs
387
+ C[np.ix_(ix,ix)] = C[np.ix_(ix,ix)] + e.get_cquad_lin(var_udot[np.ix_(ix,ix)],
388
+ input_is_local=local,
389
+ ensure_local_output=local,
390
+ stochastic=stochastic)
391
+
392
+ return C
393
+
394
+
395
+
396
+ def construct_simple_eldef(self, node_labels=None):
397
+ if node_labels is None:
398
+ node_labels = np.arange(1,len(self.hydro.pontoons)+1,1)
399
+
400
+ element_matrix = beef.nodes_to_beam_element_matrix(node_labels)
401
+ node_matrix = np.vstack([np.hstack([node_labels[ix], self.hydro.pontoons[ix].node.x0[:3]]) for ix in range(len(node_labels))])
402
+ part = fe.Part(node_matrix, element_matrix)
403
+
404
+ for element in part.elements:
405
+ element.assign_e2(np.array([0,1,0]))
406
+
407
+ part.assign_global_dofs()
408
+ part.update_all_geometry()
409
+
410
+ self.eldef = part
411
+ self.connect_eldef()
412
+
413
+ if 'hydro' in self.modal_dry.phi_full:
414
+ self.modal_dry.phi_full['full'] = self.modal_dry.phi_full['hydro']
415
+
416
+
417
+ def initialize_const_matrix(self, astype=float):
418
+ return np.zeros([len(self.hydro.pontoons)*6, len(self.hydro.pontoons)*6]).astype(astype)
419
+
420
+ def initialize_freq_matrix(self, n_freq, astype=float):
421
+ return np.zeros([len(self.hydro.pontoons)*6, len(self.hydro.pontoons)*6, n_freq]).astype(astype)
422
+
423
+ def __repr__(self):
424
+ return f'WAWI model <{self.__hash__()}>'
425
+
426
+ def __str__(self):
427
+ return f'WAWI model <{self.__hash__()}>'
428
+
429
+ def to_wwi(self, path):
430
+ with open(path, 'wb') as f:
431
+ dill.dump(self, f, -1)
432
+
433
+
434
+ def get_all_pos(self):
435
+ x = np.array([pont.node.x[0] for pont in self.hydro.pontoons])
436
+ y = np.array([pont.node.x[1] for pont in self.hydro.pontoons])
437
+ return x,y
438
+
439
+
440
+ def assign_dry_modes(self):
441
+ if self.hydro is not None:
442
+ self.hydro.phi = self.modal_dry.get_phi(key=self.hydro.phi_key)
443
+ if self.aero is not None:
444
+ self.aero.phi = self.modal_dry.get_phi(key=self.aero.phi_key)
445
+
446
+
447
+ def connect_eldef(self):
448
+ if self.eldef is not None:
449
+ if self.hydro is not None:
450
+ # Connect pontoons to nodes in beef eldef
451
+ for pontoon in self.hydro.pontoons:
452
+ pontoon.node = self.eldef.get_node(pontoon.node.label)
453
+
454
+ # Establish connectivity to aerodynamic sections
455
+ if self.aero is not None:
456
+ self.aero.eldef = dict()
457
+ node_labels_org = self.eldef.get_node_labels()
458
+
459
+ for group in self.aero.elements:
460
+ self.aero.eldef[group] = self.eldef.get_element_subset(self.aero.elements[group], renumber=False)
461
+ node_labels_sorted = np.array([nl for nl in node_labels_org if nl in self.aero.eldef[group].get_node_labels()])
462
+ self.aero.eldef[group].arrange_nodes(node_labels_sorted)
463
+
464
+ # These refer to the full dof set, no renumbering yet
465
+ self.aero.phi_ixs[group] = self.aero.eldef[group].global_dofs*1
466
+
467
+ # Rearrange dofs based on nodes in subselection, so global dof ixs refer to subset eldef
468
+ self.aero.eldef[group].assign_node_dofcounts()
469
+ self.aero.eldef[group].assign_global_dofs()
470
+ self.aero.elements[group] = self.aero.eldef[group].elements
471
+
472
+
473
+ def plot_mode(self, mode_ix, use_dry=False, scale=300, title=None, plot_wind_axes=False,
474
+ plot_states=['deformed', 'undeformed'], plot_wave_direction=False, **kwargs):
475
+
476
+ if use_dry:
477
+ phi_plot = self.get_dry_phi(key='full')
478
+ else:
479
+ phi_plot = np.real(maxreal(self.get_phi(key='full')))
480
+
481
+ self.eldef.deform_linear(phi_plot[:, mode_ix]*scale, only_deform=True)
482
+
483
+ if title is None:
484
+ title = f'Mode {mode_ix+1}'
485
+
486
+ pl = self.plot(plot_states=plot_states, title=title, plot_wind_axes=plot_wind_axes, plot_wave_direction=plot_wave_direction, **kwargs)
487
+ return pl
488
+
489
+ def export_modeshapes(self, folder, n_modes=None, format='pdf', title=None, zoom=1.0, **kwargs):
490
+ if title is None:
491
+ title = lambda mode: f'Mode {mode+1}:\nTd = {self.results.Td[mode]:.2f}s\nxi = {self.results.xi[mode]*100:.2f}%'
492
+
493
+ elif type(title) is str:
494
+ title = lambda mode: title + ''
495
+
496
+ folder = Path(folder)
497
+ if n_modes is None:
498
+ n_modes = self.n_modes
499
+
500
+ for mode in range(n_modes):
501
+ pl = self.plot_mode(mode, show=False, title=title(mode), **kwargs)
502
+ save_path = folder / f'mode{mode+1:004}.{format}'
503
+ pl.camera.zoom(zoom)
504
+ pl.save_graphic(save_path)
505
+
506
+
507
+ def copy(self):
508
+ return copy(self)
509
+
510
+ def plot(self, use_R=False, plot_water=True,
511
+ waterplane_padding=[1800, 1800], plot_wind_axes=True, wind_ax=[0],
512
+ title=None, show=True, plot_wind_at_centre=True, axis_scaling=100, plot_states=['undeformed'],
513
+ plot_wave_direction=True, wave_origin='center', pontoons_on=['deformed', 'undeformed'],
514
+ thickness_scaling=None, annotate_pontoon_type=False, **kwargs):
515
+
516
+ if thickness_scaling == 'area':
517
+ lambda sec: np.sqrt(sec.A)
518
+
519
+
520
+ tmat_settings = dict(show_edges=False)
521
+ tmat_colors = ['#ff0000', '#00ff00', '#0000ff']
522
+
523
+ if self.eldef is None:
524
+ pl = pv.Plotter()
525
+ else:
526
+ pl = self.eldef.plot(show=False, plot_states=plot_states,thickness_scaling=thickness_scaling, **kwargs)
527
+
528
+ bounds = np.array(pl.bounds)
529
+ origin = (bounds[0::2] + bounds[1::2])/2
530
+
531
+ if plot_wind_at_centre:
532
+ wind_origin = origin*1
533
+ wind_origin[2] = 10
534
+ else:
535
+ wind_origin = np.array(self.aero.windstate.x_ref)
536
+
537
+ if self.hydro is not None:
538
+ for pontoon in self.hydro.pontoons:
539
+ if pontoon.pontoon_type.mesh is not None:
540
+ for state in pontoons_on:
541
+ x_field = 'x' if state=='deformed' else 'x0'
542
+
543
+ if state in plot_states:
544
+ # Establish rotation tensor (from global undeformed to global deformed)
545
+ if use_R and hasattr(pontoon.node, 'R') and state == 'deformed':
546
+ Tn = pontoon.node.R
547
+ else:
548
+ node_rots = getattr(pontoon.node, x_field)[3:]
549
+ Tn = R_from_drot(node_rots)
550
+
551
+ # Establish rotation tensor (from local to global undeformed)
552
+ Tzrot = rodrot(pontoon.rotation, style='column')
553
+
554
+ # Stack 4x4 rotation tensor
555
+ T = np.eye(4)
556
+ T[:3, :3] = Tn @ Tzrot
557
+
558
+ mesh_current = copy(pontoon.pontoon_type.mesh)
559
+ mesh_current = mesh_current.transform(T).translate(getattr(pontoon.node, x_field)[:3])
560
+ pl.add_mesh(mesh_current)
561
+
562
+ # Plot wind arrow
563
+ if plot_wind_axes and self.aero is not None and self.aero.windstate is not None:
564
+ for ax in wind_ax:
565
+ vec = self.aero.windstate.T[ax, :]*axis_scaling
566
+ pl.add_arrows(wind_origin, vec, color=tmat_colors[ax], **tmat_settings)
567
+
568
+ pl.add_point_labels(np.vstack([wind_origin]), [f'U={self.aero.windstate.U0:.2f} m/s from heading {self.aero.windstate.direction:.1f} deg (CW)'])
569
+
570
+ # Plot wave arrow
571
+ if plot_wave_direction and self.hydro.seastate is not None:
572
+ if self.hydro.seastate.homogeneous:
573
+ if wave_origin=='center':
574
+ wave_origin = origin*1
575
+
576
+ vec = rodrot(self.hydro.seastate.theta0)[0,:]*axis_scaling
577
+ pl.add_arrows(np.array(wave_origin-vec*3), vec, color='black', **tmat_settings)
578
+ pl.add_point_labels(np.vstack([wave_origin-vec*3]), [f'dir={self.hydro.seastate.theta0*180/np.pi:.1f} deg, Hs={self.hydro.seastate.Hs:.2f} m, Tp={self.hydro.seastate.Tp:.1f} s'])
579
+ else:
580
+ vec = []
581
+ pos = []
582
+ fun_pars = self.hydro.seastate.fun_pars
583
+ for pontoon in self.hydro.pontoons:
584
+ pars = dict(theta0=f'theta0 = {pontoon.sea_get("theta0")*180/np.pi:.1f}deg',
585
+ Hs=f'Hs = {pontoon.sea_get("Hs"):.2f}m',
586
+ Tp=f'Tp = {pontoon.sea_get("Tp"):.1f}s')
587
+
588
+ vec = rodrot(pontoon.sea_get('theta0'))[0,:]*axis_scaling
589
+
590
+ if wave_origin=='center':
591
+ wave_origin = origin*1
592
+
593
+ string = ','.join([pars[key] for key in fun_pars])
594
+
595
+ pl.add_arrows(np.array(pontoon.node.x0[:3]+pontoon.tmat[0,:][:3]*200), vec, color='black', **tmat_settings)
596
+ pl.add_point_labels(np.vstack([pontoon.node.x0[:3]]), [string])
597
+
598
+ if annotate_pontoon_type:
599
+ for pontoon in self.hydro.pontoons:
600
+ pl.add_point_labels(np.vstack([pontoon.node.x0[:3]]), [pontoon.pontoon_type.label])
601
+
602
+
603
+ # Water plane
604
+ if plot_water:
605
+ sizes = np.abs(bounds[0::2]-bounds[1::2])
606
+ xsize = sizes[0]+2*waterplane_padding[0]
607
+ ysize = sizes[1]+2*waterplane_padding[1]
608
+
609
+ if self.hydro is not None and self.hydro.environment is not None:
610
+ origin[2] = self.hydro.environment.waterlevel
611
+ else:
612
+ origin[2] = 0.0
613
+ water_level = pv.Plane(i_size=xsize, j_size=ysize, center=origin)
614
+ pl.add_mesh(water_level, color='#00aaff', opacity=0.3)
615
+
616
+ if title is not None:
617
+ pl.add_title(title, color='black', font_size=12)
618
+
619
+ if show:
620
+ pl.show()
621
+
622
+ return pl
623
+
624
+
625
+ @property
626
+ def theta_int(self):
627
+ return self.hydro.seastate.theta_int
628
+
629
+
630
+ @property
631
+ def local(self):
632
+ if self.modal_dry is not None:
633
+ return self.modal_dry.local_phi
634
+ else:
635
+ return False
636
+
637
+ @property
638
+ def dry_K(self):
639
+ if self.modal_dry is None:
640
+ return 0
641
+ else:
642
+ return self.modal_dry.K
643
+
644
+ @property
645
+ def dry_C(self):
646
+ if self.modal_dry is None:
647
+ return 0
648
+ else:
649
+ return self.modal_dry.C
650
+
651
+ @property
652
+ def dry_M(self):
653
+ if self.modal_dry is None:
654
+ return 0
655
+ else:
656
+ return self.modal_dry.M
657
+
658
+ def get_dry_phi(self, key='hydro'):
659
+ if self.modal_dry is None:
660
+ return np.eye(self.hydro.ndofs)
661
+ else:
662
+ return self.modal_dry.get_phi(key=key)
663
+
664
+ def get_phi(self, key='hydro', normalize=True, ensure_maxreal=True):
665
+ phi_tot = self.get_dry_phi(key=key) @ self.results.psi
666
+
667
+ if ensure_maxreal:
668
+ phi_tot = maxreal(phi_tot)
669
+
670
+ if normalize:
671
+ phi_tot, __ = normalize_phi(phi_tot)
672
+
673
+ return phi_tot
674
+
675
+ def get_result_psd(self, key='hydro', index=None, convert_to=None, modes=None):
676
+ ix, ix_3d = self.get_mode_ix(modes)
677
+
678
+ if key is not None:
679
+ sel_phi = self.get_dry_phi(key=key)[:, ix]
680
+ else:
681
+ return self.results.S
682
+
683
+ if key in ['hydro', 'full']: #only supported for hydro and full currently
684
+ if key == 'full':
685
+ tmat = self.tmat_full*1
686
+ if convert_to is not None:
687
+ print('Local nodal csys is strictly not possible - averaging introduced (use with care).')
688
+ else:
689
+ tmat = self.tmat*1
690
+
691
+ if (convert_to == 'global') and (self.local):
692
+ sel_phi = tmat.T @ sel_phi
693
+
694
+ elif (convert_to == 'local') and (not self.local):
695
+ sel_phi = tmat @ sel_phi
696
+
697
+ elif convert_to is not None:
698
+ raise ValueError('convert_to only supported for key="hydro" or "full"; use convert_to=None (output will be given in csys of phi matrix with specified key.')
699
+
700
+ if index is not None and np.ndim(index)==0:
701
+ index = np.array([index])
702
+ sel_phi = sel_phi[index, :]
703
+ elif index is not None:
704
+ sel_phi = sel_phi[index, :]
705
+
706
+ psd = transform_3dmat(self.results.S[ix_3d], sel_phi.T)
707
+
708
+ if psd.shape[0]==1:
709
+ psd = psd.flatten()
710
+ psd = np.real(psd)
711
+
712
+ return psd
713
+
714
+ def get_result_std(self, key=None, h=lambda om: 1.0, modes=None):
715
+ ix, ix_3d = self.get_mode_ix(modes)
716
+
717
+ if key is None:
718
+ return np.sqrt(np.diag(np.trapz(np.real(self.results.S[ix_3d]*h(self.results.omega)), self.results.omega, axis=2)))
719
+ else:
720
+ return np.sqrt(var_from_modal(self.results.omega, self.results.S[ix_3d]*h(self.results.omega), self.get_dry_phi(key=key)[:,ix]))
721
+
722
+ def get_result_expmax(self, T, key=None, h=lambda om: 1.0, modes=None):
723
+ ix, ix_3d = self.get_mode_ix(modes)
724
+ if key is None:
725
+ return expmax_from_modal(self.results.omega, self.results.S[ix_3d]*h(self.results.omega), np.eye(self.results.S.shape[0])[:, ix], T)
726
+ else:
727
+ return expmax_from_modal(self.results.omega, self.results.S[ix_3d]*h(self.results.omega), self.get_dry_phi(key=key)[:,ix], T)
728
+
729
+
730
+ def get_result_peakfactor(self, T, key='hydro', h=lambda om: 1.0, modes=None):
731
+ ix, ix_3d = self.get_mode_ix(modes)
732
+ return peakfactor_from_modal(self.results.omega, self.results.S[ix_3d]*h(self.results.omega), self.get_dry_phi(key=key)[:,ix], T)
733
+
734
+ def get_gen_S(self, psi=None):
735
+ if psi is None:
736
+ psi = np.real(self.results.psi) # self.results.psi has already been run through maxreal
737
+
738
+ n_modes = psi.shape[0]
739
+
740
+ # CPSD matrices: dry modal response
741
+ Sy = self.results.S*1
742
+
743
+ # Estimate Sg
744
+ Sg = Sy*0
745
+ psi_inv = np.linalg.pinv(psi)
746
+ Sg = transform_3dmat(Sy[:n_modes,:n_modes,:], psi_inv.T)
747
+
748
+ return Sg
749
+
750
+
751
+ def get_mode_ix(self, modes):
752
+ if modes is None:
753
+ modes = np.arange(0, self.results.S.shape[0])
754
+
755
+ if np.ndim(modes)==0:
756
+ modes = np.array([modes])
757
+
758
+ return np.array(modes), np.ix_(modes,modes, range(self.results.S.shape[2]))
759
+
760
+ @property
761
+ def n_pontoons(self):
762
+ return len(self.hydro.pontoons)
763
+
764
+ @property
765
+ def tmat(self):
766
+ return block_diag(*[pont.tmat for pont in self.hydro.pontoons])
767
+
768
+
769
+
770
+ @property
771
+ def pontoon_x(self):
772
+ return np.array([p.node.x[0] for p in self.hydro.pontoons])
773
+
774
+ @property
775
+ def pontoon_y(self):
776
+ return np.array([p.node.x[1] for p in self.hydro.pontoons])
777
+
778
+ @property
779
+ def pontoon_z(self):
780
+ return np.array([p.node.x[2] for p in self.hydro.pontoons])
781
+
782
+
783
+ def plot_2d_tmats(self, scale=50, show_labels=True, ax=None):
784
+ if ax is None:
785
+ fig, ax = plt.subplots()
786
+
787
+ for pontoon in self.hydro.pontoons:
788
+ x,y,__ = pontoon.node.coordinates
789
+ tmat = pontoon.tmat[:2,:2]*scale
790
+ ex, ey = tmat[0,:], tmat[1,:]
791
+
792
+ ax.plot([x,x+ex[0]], [y,y+ex[1]], color='blue')
793
+ ax.plot([x,x+ey[0]], [y,y+ey[1]], color='red')
794
+
795
+ if show_labels:
796
+ ax.text(x,y,pontoon.label, fontsize=7)
797
+
798
+ ax.axis('equal')
799
+ return ax
800
+
801
+ @classmethod
802
+ def from_nodes_and_types(cls, nodes, pontoon_types, rotation=0, prefix_label='pontoon-',
803
+ labels=None, pontoon_props=dict(), **kwargs):
804
+ if np.ndim(rotation)==0:
805
+ rotation = [rotation]*len(nodes)
806
+
807
+ pontoons = [None]*len(nodes)
808
+ for ix, node in enumerate(nodes):
809
+ if labels is None:
810
+ label = prefix_label+str(ix+1)
811
+ else:
812
+ label = labels[ix]
813
+
814
+ pontoons[ix] = Pontoon(node, pontoon_types[ix], rotation=rotation[ix], label=label)
815
+
816
+ for pontoon in pontoons:
817
+ for key in pontoon_props:
818
+ setattr(pontoon, key, pontoon_props[key])
819
+ if len(pontoons)>0:
820
+ return cls(hydro=Hydro(pontoons), **kwargs)
821
+ else:
822
+ return cls(**kwargs)
823
+
824
+
825
+ def run_eig(self, normalize=False, print_progress=True, freq_kind=True,
826
+ include=['aero', 'hydro', 'drag_elements', 'drag_pontoons'],
827
+ smooth=None, aero_sections=None, **kwargs):
828
+
829
+ include_dict = self.establish_include_dict(include)
830
+
831
+ if (('aero' in include_dict['k'] or 'aero' in include_dict['c']) and hasattr(self, 'aero')
832
+ and self.aero is not None and self.aero.Kfun is None):
833
+ self.aero.prepare_aero_matrices(aero_sections=aero_sections)
834
+
835
+ Kfun, Cfun, Mfun = self.get_system_matrices(include=include_dict)
836
+
837
+ if smooth is not None:
838
+ omega = smooth.pop('omega')
839
+ if Kfun is not None:
840
+ K = np.stack([Kfun(omi) for omi in omega], axis=2)
841
+ K = savgol_filter(K, **smooth)
842
+ Kfun = interp1d(omega, K, kind='quadratic')
843
+
844
+ if Cfun is not None:
845
+ C = np.stack([Cfun(omi) for omi in omega], axis=2)
846
+ C = savgol_filter(C, **smooth)
847
+ Cfun = interp1d(omega, C, kind='quadratic')
848
+
849
+ if Mfun is not None:
850
+ M = np.stack([Mfun(omi) for omi in omega], axis=2)
851
+ M = savgol_filter(M, **smooth)
852
+ Mfun = interp1d(omega, M, kind='quadratic')
853
+
854
+ if freq_kind:
855
+ fun = iteig_freq
856
+ else:
857
+ fun = iteig
858
+
859
+ lambd, psi, __ = fun(Kfun, Cfun, Mfun, print_progress=print_progress,
860
+ normalize=normalize, **kwargs)
861
+
862
+ self.results.psi = maxreal(psi)
863
+ self.results.lambd = lambd
864
+ self.results.include_eig = include
865
+
866
+
867
+ def run_static(self, aero_sections=None, include_selfexctied=['aero']):
868
+ if not hasattr(self.aero, 'F0_m') or (self.aero.F0_m is None): # check if already computed static forces
869
+ self.precompute_windaction(static=True, aero_sections=aero_sections) # compute if not present
870
+
871
+ if ('aero' in include_selfexctied) and (self.aero is not None) and (self.aero.Kfun is None):
872
+ K_ae, __ = self.get_aero_matrices(omega_reduced=0, aero_sections=aero_sections)
873
+ else:
874
+ K_ae = 0.0
875
+
876
+ Ktot = -K_ae + self.dry_K
877
+ self.results.y_static = np.linalg.inv(Ktot) @ self.aero.F0_m
878
+
879
+
880
+ def run_freqsim(self, omega, omega_aero=None, omega_hydro=None,
881
+ print_progress=True, interpolation_kind='linear',
882
+ max_rel_error=0.01, drag_iterations=0, tol=1e-2, include_action=['hydro', 'aero'],
883
+ include_selfexcited=['hydro', 'aero', 'drag_elements', 'drag_pontoons'], ensure_at_peaks=True,
884
+ theta_interpolation='linear', reset_Cquad_lin=True, aero_sections=None):
885
+
886
+ include_dict = self.establish_include_dict(include_selfexcited)
887
+
888
+ if (('aero' in include_dict['k'] or 'aero' in include_dict['c']) and hasattr(self, 'aero')
889
+ and (self.aero is not None) and (self.aero.Kfun is None)):
890
+ self.aero.prepare_aero_matrices(omega=omega_aero, aero_sections=aero_sections)
891
+
892
+ if reset_Cquad_lin and hasattr(self, 'hydro') and self.hydro is not None:
893
+ self.hydro.reset_linearized_drag()
894
+ self.Cquad_lin = 0.0
895
+
896
+ if ensure_at_peaks and hasattr(self.results, 'wd') and self.results.wd is not None:
897
+ wd = self.results.wd
898
+ omega_peaks = wd[(wd>=np.min(omega)) & (wd<=np.max(omega))]
899
+ omega = np.unique(np.hstack([omega, omega_peaks]))
900
+
901
+ if omega_hydro is None:
902
+ omega_hydro = omega*1
903
+
904
+ if omega_aero is None:
905
+ omega_aero = omega*1
906
+
907
+ # Excitation
908
+ Sqq_m = 0.0
909
+
910
+ if (self.hydro is not None) and (self.hydro.seastate is not None) and ('hydro' in include_action):
911
+ if hasattr(self.hydro, 'Sqq_hydro') and (self.hydro.Sqq_hydro is not None):
912
+ Sqq_m = Sqq_m + self.hydro.Sqq_hydro(omega)
913
+ else:
914
+ Sqq_hydro = self.evaluate_waveaction(omega_hydro, print_progress=print_progress,
915
+ max_rel_error=max_rel_error, theta_interpolation=theta_interpolation)
916
+
917
+ Sqq_m = Sqq_m + interp1d(omega_hydro, Sqq_hydro, kind=interpolation_kind, axis=2,
918
+ fill_value=0.0, bounds_error=False)(omega)
919
+
920
+ if (self.aero is not None) and (self.aero.windstate is not None) and ('aero' in include_action):
921
+ if hasattr(self.aero, 'Sqq_aero') and (self.aero.Sqq_aero is not None):
922
+ Sqq_m = Sqq_m + self.aero.Sqq_aero(omega)
923
+ else:
924
+ Sqq_aero = self.evaluate_windaction(omega_aero, print_progress=print_progress, aero_sections=aero_sections)
925
+ Sqq_m = Sqq_m + interp1d(omega_aero, Sqq_aero, kind=interpolation_kind,
926
+ axis=2, fill_value=0.0, bounds_error=False)(omega)
927
+
928
+ def runsim():
929
+ Hnum = eval_3d_fun(self.get_frf_fun(include=include_dict), omega) # System
930
+ Srr_m = freqsim(Sqq_m, Hnum) # Power-spectral density method
931
+
932
+ return Srr_m
933
+
934
+ Srr_m = runsim()
935
+
936
+ # Linearized drag damping
937
+ if drag_iterations>0:
938
+ Clin_pontoons = np.diag(np.zeros(len(self.hydro.pontoons)*6)) # initialize for convergence check
939
+
940
+ if ('drag_elements' in include_dict['c']) and hasattr(self, 'Cquad'):
941
+ Cquad_model = self.Cquad * 1
942
+ model_converged = False
943
+ phi_model = self.get_dry_phi(key='full')
944
+ vnodes = np.zeros(phi_model.shape[0])
945
+ else:
946
+ Cquad_model = None
947
+ model_converged = True
948
+
949
+ if 'drag_pontoons' in include_dict['c']:
950
+ pontoons_converged = False
951
+ vp = np.zeros(Clin_pontoons.shape[0])
952
+ Cquad_pontoons = self.hydro.get_all_cquad()
953
+ else:
954
+ Cquad_pontoons = None
955
+ pontoons_converged = True
956
+
957
+ for n in range(drag_iterations):
958
+ if print_progress:
959
+ pp(n+1, drag_iterations, postfix=f' DRAG LINEARIZATION - RUNNING ITERATION {n+1}')
960
+
961
+ #------ PONTOONS ----------------------------
962
+ if 'drag_pontoons' in include_dict['c']:
963
+ vp_prev = vp * 1
964
+ vp = np.sqrt(var_from_modal(omega, Srr_m*omega**2, self.get_dry_phi(key='hydro')))
965
+ Cquad_lin_pontoons = stochastic_linearize(Cquad_pontoons, vp)
966
+ for ix, p in enumerate(self.hydro.pontoons):
967
+ p.Cquad_lin = Cquad_lin_pontoons[ix*6:ix*6+6, ix*6:ix*6+6]
968
+
969
+ if np.linalg.norm(vp - vp_prev) < tol*(np.max([np.linalg.norm(vp), np.linalg.norm(vp_prev)])):
970
+ pontoons_converged = True
971
+
972
+ #------ DRAG ELEMENTS -----------------------
973
+ if ('drag_elements' in include_dict['c']) and (Cquad_model is not None):
974
+ vnodes_prev = vnodes*1
975
+ var_udot = var_from_modal(omega, Srr_m*omega**2, phi_model, only_diagonal=False)
976
+ self.Cquad_lin = phi_model.T @ self.get_Cquad_lin(var_udot, local=False) @ phi_model
977
+ vnodes = np.sqrt(np.diag(var_udot))
978
+ if np.linalg.norm(vnodes - vnodes_prev) < tol*(np.max([np.linalg.norm(vnodes), np.linalg.norm(vnodes_prev)])):
979
+ model_converged = True
980
+
981
+ # Convergence check
982
+ if pontoons_converged and model_converged and print_progress:
983
+ print('\n STOPPING ITERATION. Linearized damping converged by assertion with specified tolerance criterion.')
984
+ break
985
+
986
+ Srr_m = runsim()
987
+
988
+ # STORE RESULTS
989
+ self.results.omega = omega*1
990
+ self.results.S = Srr_m*1
991
+
992
+ def assign_windstate(self, windstate):
993
+ self.aero.windstate = windstate
994
+
995
+ def assign_seastate(self, seastate=None):
996
+ # Reset pontoon settings
997
+ self.hydro.assign_to_pontoons(**dict(current_affects_k=None, current_affects_Q=None))
998
+ self.hydro.Sqq_hydro = None
999
+
1000
+ if seastate is None:
1001
+ seastate = self.hydro.seastate
1002
+ else:
1003
+ self.hydro.seastate = seastate
1004
+
1005
+ for pontoon in self.hydro.pontoons:
1006
+ pontoon.seastate = seastate
1007
+
1008
+ if seastate is not None:
1009
+ self.hydro.assign_to_pontoons(**self.hydro.seastate.pontoon_options)
1010
+
1011
+ def prepare_waveaction(self):
1012
+ x, y = self.get_all_pos()
1013
+
1014
+ xmesh, ymesh = np.meshgrid(x,x), np.meshgrid(y,y)
1015
+ dx = xmesh[0]-xmesh[1]
1016
+ dy = ymesh[0]-ymesh[1]
1017
+ ds = np.sqrt(dx**2+dy**2)
1018
+
1019
+ min_ix = np.argmin(np.max(ds, axis=0))
1020
+ max_distances = ds[min_ix,:]
1021
+
1022
+ for ix, p in enumerate(self.hydro.pontoons):
1023
+ p.max_distance = max_distances[ix]
1024
+
1025
+ pont_max_distance = self.hydro.pontoons[np.argmax(max_distances)]
1026
+ self.get_theta_int = pont_max_distance.get_theta_int
1027
+
1028
+
1029
+
1030
+ def get_waveaction(self, omega_k, max_rel_error=0.01,
1031
+ theta_interpolation='linear', theta_int=None):
1032
+
1033
+ if theta_int is None and self.theta_int is None:
1034
+ theta_int = self.get_theta_int(omega_k, max_rel_error=max_rel_error)
1035
+ elif theta_int is None:
1036
+ theta_int = self.theta_int
1037
+
1038
+ Z = np.zeros([self.hydro.ndofs, len(theta_int)]).astype('complex')
1039
+
1040
+ for pontoon_index, pontoon in enumerate(self.hydro.pontoons):
1041
+ Z[pontoon_index*6:pontoon_index*6+6, :] = pontoon.get_Z(omega_k, theta_int,
1042
+ theta_interpolation=theta_interpolation,
1043
+ local=self.local, x0=self.x0)
1044
+
1045
+ if self.hydro.seastate.short_crested:
1046
+ # first and last point in trapezoidal integration has 1/2 as factor, others have 1
1047
+ # verified to match for loop over angles and trapz integration.
1048
+ dtheta = theta_int[1] - theta_int[0]
1049
+ Z[:, 0] = np.sqrt(0.5)*Z[:, 0]
1050
+ Z[:, -1] = np.sqrt(0.5)*Z[:, -1]
1051
+ Sqq0 = dtheta * Z @ Z.conj().T
1052
+ else:
1053
+ Sqq0 = Z @ Z.conj().T
1054
+
1055
+ if not self.hydro.seastate.options['keep_coherence']:
1056
+ Sqq0 = block_diag(*[Sqq0[i*6:(i+1)*6, i*6:(i+1)*6] for i in range(int(Sqq0.shape[0]/6))])
1057
+
1058
+ return self.hydro.phi.T @ Sqq0 @ self.hydro.phi
1059
+
1060
+
1061
+ def evaluate_windaction(self, omega=None, aero_sections=None, print_progress=True, static=False, **kwargs):
1062
+ if aero_sections is None:
1063
+ aero_sections = self.aero.elements.keys()
1064
+
1065
+ Sae_m = 0.0
1066
+ T = self.aero.windstate.T
1067
+ U = self.aero.windstate.U
1068
+ rho = self.aero.windstate.rho
1069
+
1070
+ # Sections needs to be merged - all elements are therefore unwrapped
1071
+ els = [a for b in [self.aero.elements[sec] for sec in aero_sections] for a in b] # all requested sections, flattened
1072
+ eldef = self.eldef.get_element_subset(self.eldef.get_elements([el.label for el in els]), renumber=False) # create new eldef for requested elements
1073
+ phi = self.get_dry_phi(key='full')[eldef.global_dofs, :] # grab relevant phi components
1074
+ eldef.assign_global_dofs()
1075
+ nodes = eldef.nodes*1
1076
+ els = eldef.elements*1
1077
+
1078
+ lc = {sec: self.aero.sections[sec].all_lc for sec in aero_sections} # dict with all load coefficients
1079
+ B = {sec: self.aero.sections[sec].B for sec in aero_sections} # dict with all load coefficients
1080
+ D = {sec: self.aero.sections[sec].D for sec in aero_sections} # dict with all load coefficients
1081
+ S = self.aero.get_generic_kaimal(nodes=nodes)
1082
+ section_lookup = {sec: self.aero.elements[sec] for sec in aero_sections}
1083
+
1084
+ if static:
1085
+ F0_m = windaction_static(lc, els, T, phi,
1086
+ B, D, U, print_progress=print_progress, rho=rho,
1087
+ section_lookup=section_lookup, nodes=nodes)
1088
+ return F0_m
1089
+ else:
1090
+ admittance = {sec: self.aero.sections[sec].admittance for sec in aero_sections}
1091
+ Sae_m_fun = windaction(omega, S, lc, els, T, phi,
1092
+ B, D, U, print_progress=print_progress, rho=rho,
1093
+ section_lookup=section_lookup, nodes=nodes, admittance=admittance, **kwargs)
1094
+
1095
+ Sae_m = np.stack([Sae_m_fun(om_k) for om_k in omega], axis=2)
1096
+
1097
+ return Sae_m
1098
+
1099
+ def evaluate_windaction_static(self, aero_sections=None, print_progress=True, **kwargs):
1100
+ return self.evaluate_windaction(aero_sections=None, print_progress=True, static=True, **kwargs)
1101
+
1102
+ def precompute_windaction(self, omega, include=['dynamic'], interpolation_kind='linear', **kwargs):
1103
+
1104
+ if 'dynamic' in include:
1105
+ self.aero.Sqq_aero = interp1d(omega, self.evaluate_windaction(omega=omega, static=False, **kwargs),
1106
+ kind=interpolation_kind, axis=2, fill_value=0.0, bounds_error=False)
1107
+ if 'static' in include:
1108
+ self.aero.F0_m = self.evaluate_windaction(static=True, **kwargs)
1109
+
1110
+
1111
+ def precompute_waveaction(self, omega, interpolation_kind='linear', method='standard', **kwargs):
1112
+ if method=='standard':
1113
+ Sqq0 = self.evaluate_waveaction(omega, **kwargs)
1114
+
1115
+ elif method=='fft':
1116
+ if not self.hydro.seastate.homogeneous:
1117
+ raise ValueError('Only method standard" is supported for inhomogeneous conditions')
1118
+ # Sqq0 = transform_3dmat(waveaction_fft(self.hydro.pontoons, omega, **kwargs), self.hydro.phi)
1119
+ raise NotImplementedError('FFT not implemented yet. Will be at some point.')
1120
+
1121
+ elif method in ['fourier', 'cos2s', 'cos2s-fourer'] :
1122
+ if not self.hydro.homogeneous:
1123
+ raise ValueError('Only method standard" is supported for inhomogeneous conditions')
1124
+
1125
+ raise NotImplementedError('Fourier (cos 2s) not implemented yet. Will be at some point.')
1126
+
1127
+ self.hydro.Sqq_hydro = interp1d(omega, Sqq0, kind=interpolation_kind, axis=2, fill_value=0.0, bounds_error=False)
1128
+
1129
+
1130
+
1131
+ def evaluate_waveaction(self, omega, max_rel_error=0.01, print_progress=True, theta_int=None,
1132
+ theta_interpolation='quadratic', **kwargs):
1133
+
1134
+ if theta_int is None:
1135
+ theta_int = self.theta_int
1136
+
1137
+ ndofs = self.hydro.phi.shape[1]
1138
+ Sqq = np.zeros([ndofs, ndofs, len(omega)]).astype('complex')
1139
+ for k, omega_k in enumerate(omega):
1140
+ Sqq[:,:,k] = self.get_waveaction(omega_k, max_rel_error=max_rel_error, theta_int=theta_int,
1141
+ theta_interpolation=theta_interpolation, **kwargs)
1142
+
1143
+ if print_progress:
1144
+ pp(k+1, len(omega), postfix=' ESTABLISHING WAVE EXCITATION ')
1145
+
1146
+ return Sqq
1147
+
1148
+ # FRF
1149
+ def get_added_frf(self, omega_k, inverse=False):
1150
+ if inverse:
1151
+ return -omega_k**2*self.get_added_M(omega_k) + 1j*omega_k*self.get_added_C(omega_k) + self.get_added_K(omega_k)
1152
+ else:
1153
+ return np.linalg.inv(-omega_k**2*self.get_added_M(omega_k) +
1154
+ 1j*omega_k*self.get_added_C(omega_k) + self.get_added_K(omega_k))
1155
+
1156
+ def get_dry_frf(self, omega_k, inverse=False):
1157
+ if inverse:
1158
+ return -omega_k**2*self.dry_M + 1j*omega_k*self.dry_C + self.dry_K
1159
+ else:
1160
+ return np.linalg.inv(-omega_k**2*self.dry_M + 1j*omega_k*self.dry_C + self.dry_K)
1161
+
1162
+ def get_aero_K(self, omega_k):
1163
+ if self.aero is not None:
1164
+ K_aero = self.aero.Kfun(omega_k)
1165
+ else:
1166
+ K_aero = 0.0
1167
+
1168
+ return -K_aero
1169
+
1170
+ def get_aero_C(self, omega_k):
1171
+
1172
+ if self.aero is not None:
1173
+ C_aero = self.aero.Cfun(omega_k)
1174
+ else:
1175
+ C_aero = 0.0
1176
+
1177
+ return -C_aero
1178
+
1179
+
1180
+ def get_aero_M(self, omega_k):
1181
+ return 0.0
1182
+
1183
+ def get_hydro_K(self, omega_k):
1184
+ if self.hydro is not None:
1185
+ mat = self.initialize_const_matrix()
1186
+
1187
+ for ix, p in enumerate(self.hydro.pontoons):
1188
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_K(omega_k, local=self.local)
1189
+ return self.hydro.phi.T @ mat @ self.hydro.phi
1190
+ else:
1191
+ return 0.0
1192
+
1193
+ def get_hydro_C(self, omega_k):
1194
+ if self.hydro is not None:
1195
+ mat = self.initialize_const_matrix()
1196
+ for ix, p in enumerate(self.hydro.pontoons):
1197
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_C(omega_k, local=self.local)
1198
+ return self.hydro.phi.T @ mat @ self.hydro.phi
1199
+ else:
1200
+ return 0.0
1201
+
1202
+
1203
+ def get_hydro_M(self, omega_k):
1204
+ if self.hydro is not None:
1205
+ mat = self.initialize_const_matrix()
1206
+ for ix, p in enumerate(self.hydro.pontoons):
1207
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_M(omega_k, local=self.local)
1208
+
1209
+ return self.hydro.phi.T @ mat @ self.hydro.phi
1210
+ else:
1211
+ return 0.0
1212
+
1213
+
1214
+ def get_added_K(self, omega_k):
1215
+ if self.hydro is not None:
1216
+ mat = self.initialize_const_matrix()
1217
+
1218
+ for ix, p in enumerate(self.hydro.pontoons):
1219
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_K(omega_k, local=self.local)
1220
+ K_hydro = self.hydro.phi.T @ mat @ self.hydro.phi
1221
+ else:
1222
+ K_hydro = 0.0
1223
+
1224
+ if self.aero is not None:
1225
+ K_aero = self.aero.Kfun(omega_k)
1226
+ else:
1227
+ K_aero = 0.0
1228
+
1229
+ return K_hydro - K_aero
1230
+
1231
+ def get_added_C(self, omega_k):
1232
+ if self.hydro is not None:
1233
+ mat = self.initialize_const_matrix()
1234
+ for ix, p in enumerate(self.hydro.pontoons):
1235
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_C(omega_k, local=self.local)
1236
+ C_hydro = self.hydro.phi.T @ mat @ self.hydro.phi
1237
+ else:
1238
+ C_hydro = 0.0
1239
+
1240
+ if self.aero is not None:
1241
+ C_aero = self.aero.Cfun(omega_k)
1242
+ else:
1243
+ C_aero = 0.0
1244
+
1245
+ return C_hydro - C_aero
1246
+
1247
+
1248
+ def get_added_M(self, omega_k):
1249
+ if self.hydro is not None:
1250
+ mat = self.initialize_const_matrix()
1251
+ for ix, p in enumerate(self.hydro.pontoons):
1252
+ mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_M(omega_k, local=self.local)
1253
+
1254
+ return self.hydro.phi.T @ mat @ self.hydro.phi
1255
+ else:
1256
+ return 0.0
1257
+
1258
+ @staticmethod
1259
+ def establish_include_dict(include):
1260
+ if type(include) is not dict:
1261
+ include_dict = dict()
1262
+ keys = ['k', 'c', 'm']
1263
+ for key in keys:
1264
+ include_dict[key] = include*1
1265
+ return include_dict
1266
+ else:
1267
+ replacement_keys = dict(stiffness='k', mass='m', damping='c')
1268
+ include_dict = {replacement_keys.get(k, k): v for k, v in include.items()}
1269
+ return include_dict
1270
+
1271
+ def get_system_matrices(self, include=['hydro', 'aero', 'drag_elements', 'drag_pontoons']):
1272
+ include_dict = self.establish_include_dict(include)
1273
+
1274
+ # Stiffness
1275
+ if ('hydro' in include_dict['k'] and self.hydro is not None) and ('aero' in include_dict['k'] and self.aero is not None):
1276
+ Kfun = fun_const_sum(self.get_added_K, self.dry_K)
1277
+ elif ('hydro' in include_dict['k'] and self.hydro is not None):
1278
+ Kfun = fun_const_sum(self.get_hydro_K, self.dry_K)
1279
+ elif ('aero' in include_dict['k'] and self.aero is not None):
1280
+ Kfun = fun_const_sum(self.get_aero_K, self.dry_K)
1281
+ else:
1282
+ Kfun = lambda omega_k: self.dry_K
1283
+
1284
+ # Damping
1285
+ dry_C = self.dry_C*1
1286
+ if 'drag_elements' in include_dict['c']:
1287
+ dry_C += self.Cquad_lin
1288
+ if ('drag_pontoons' in include_dict['c'] and self.hydro is not None):
1289
+ dry_C += self.Cquad_lin_pontoons
1290
+
1291
+ if ('hydro' in include_dict['c'] and self.hydro is not None) and ('aero' in include_dict['c'] and self.aero is not None):
1292
+ Cfun = fun_const_sum(self.get_added_C, dry_C)
1293
+ elif ('hydro' in include_dict['c'] and self.hydro is not None):
1294
+ Cfun = fun_const_sum(self.get_hydro_C, dry_C)
1295
+ elif 'aero' in include_dict['c'] and self.aero is not None:
1296
+ Cfun = fun_const_sum(self.get_aero_C, dry_C)
1297
+ else:
1298
+ Cfun = lambda omega_k: dry_C
1299
+
1300
+ # Mass
1301
+ if ('hydro' in include_dict['m'] and self.hydro is not None) and ('aero' in include_dict['m'] and self.aero is not None):
1302
+ Mfun = fun_const_sum(self.get_added_M, self.dry_M)
1303
+ elif ('hydro' in include_dict['m'] and self.hydro is not None):
1304
+ Mfun = fun_const_sum(self.get_hydro_M, self.dry_M)
1305
+ elif 'aero' in include_dict['m'] and self.aero is not None:
1306
+ Mfun = fun_const_sum(self.get_aero_M, self.dry_M)
1307
+ else:
1308
+ Mfun = lambda omega_k: self.dry_M
1309
+
1310
+ return Kfun, Cfun, Mfun
1311
+
1312
+
1313
+ def get_frf_fun(self, include=['hydro', 'aero', 'drag_elements'], opt = 0):
1314
+ Kfun, Cfun, Mfun = self.get_system_matrices(include)
1315
+
1316
+ def frf(omega_k):
1317
+ return np.linalg.inv(-omega_k**2*Mfun(omega_k) + 1j*omega_k*Cfun(omega_k) + Kfun(omega_k))
1318
+
1319
+ def imp(omega_k):
1320
+ return (-omega_k**2*Mfun(omega_k) + 1j*omega_k*Cfun(omega_k) + Kfun(omega_k))
1321
+ if opt == 0:
1322
+ return frf
1323
+ else:
1324
+ return imp