wawi 0.0.8__py3-none-any.whl → 0.0.11__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 +5 -3
- wawi/ext/__init__.py +0 -0
- wawi/ext/abq.py +259 -0
- wawi/ext/ansys.py +0 -0
- wawi/ext/orcaflex.py +0 -0
- wawi/ext/sofistik.py +0 -0
- wawi/io.py +1 -1
- 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 +1338 -0
- wawi/model/_screening.py +124 -0
- wawi/time_domain.py +45 -0
- wawi/wind.py +1 -1
- {wawi-0.0.8.dist-info → wawi-0.0.11.dist-info}/METADATA +10 -2
- wawi-0.0.11.dist-info/RECORD +38 -0
- {wawi-0.0.8.dist-info → wawi-0.0.11.dist-info}/WHEEL +1 -1
- wawi-0.0.11.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.11.dist-info/licenses}/LICENSE +0 -0
wawi/model/_model.py
ADDED
@@ -0,0 +1,1338 @@
|
|
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
|
+
# index: applied after transformation to requested csys (convert_to)
|
677
|
+
|
678
|
+
ix, ix_3d = self.get_mode_ix(modes)
|
679
|
+
|
680
|
+
if key is not None:
|
681
|
+
sel_phi = self.get_dry_phi(key=key)[:, ix]
|
682
|
+
else:
|
683
|
+
return self.results.S
|
684
|
+
|
685
|
+
if key in ['hydro', 'full']: #only supported for hydro and full currently
|
686
|
+
if key == 'full':
|
687
|
+
tmat = self.tmat_full*1
|
688
|
+
if convert_to is not None:
|
689
|
+
print('Local nodal csys is strictly not possible - averaging introduced (use with care).')
|
690
|
+
else:
|
691
|
+
tmat = self.tmat*1
|
692
|
+
|
693
|
+
if (convert_to == 'global') and (self.local):
|
694
|
+
sel_phi = tmat.T @ sel_phi
|
695
|
+
|
696
|
+
elif (convert_to == 'local') and (not self.local):
|
697
|
+
sel_phi = tmat @ sel_phi
|
698
|
+
|
699
|
+
elif convert_to is not None:
|
700
|
+
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.')
|
701
|
+
|
702
|
+
if index is not None and np.ndim(index)==0:
|
703
|
+
index = np.array([index])
|
704
|
+
sel_phi = sel_phi[index, :]
|
705
|
+
elif index is not None:
|
706
|
+
sel_phi = sel_phi[index, :]
|
707
|
+
|
708
|
+
psd = transform_3dmat(self.results.S[ix_3d], sel_phi.T)
|
709
|
+
|
710
|
+
if psd.shape[0]==1:
|
711
|
+
psd = psd.flatten()
|
712
|
+
psd = np.real(psd)
|
713
|
+
|
714
|
+
return psd
|
715
|
+
|
716
|
+
def get_result_std(self, key=None, h=lambda om: 1.0, modes=None):
|
717
|
+
ix, ix_3d = self.get_mode_ix(modes)
|
718
|
+
|
719
|
+
if key is None:
|
720
|
+
return np.sqrt(np.diag(np.trapz(np.real(self.results.S[ix_3d]*h(self.results.omega)), self.results.omega, axis=2)))
|
721
|
+
else:
|
722
|
+
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]))
|
723
|
+
|
724
|
+
def get_result_expmax(self, T, key=None, h=lambda om: 1.0, modes=None):
|
725
|
+
ix, ix_3d = self.get_mode_ix(modes)
|
726
|
+
if key is None:
|
727
|
+
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)
|
728
|
+
else:
|
729
|
+
return expmax_from_modal(self.results.omega, self.results.S[ix_3d]*h(self.results.omega), self.get_dry_phi(key=key)[:,ix], T)
|
730
|
+
|
731
|
+
|
732
|
+
def get_result_peakfactor(self, T, key='hydro', h=lambda om: 1.0, modes=None):
|
733
|
+
ix, ix_3d = self.get_mode_ix(modes)
|
734
|
+
return peakfactor_from_modal(self.results.omega, self.results.S[ix_3d]*h(self.results.omega), self.get_dry_phi(key=key)[:,ix], T)
|
735
|
+
|
736
|
+
def get_gen_S(self, psi=None):
|
737
|
+
if psi is None:
|
738
|
+
psi = np.real(self.results.psi) # self.results.psi has already been run through maxreal
|
739
|
+
|
740
|
+
n_modes = psi.shape[0]
|
741
|
+
|
742
|
+
# CPSD matrices: dry modal response
|
743
|
+
Sy = self.results.S*1
|
744
|
+
|
745
|
+
# Estimate Sg
|
746
|
+
Sg = Sy*0
|
747
|
+
psi_inv = np.linalg.pinv(psi)
|
748
|
+
Sg = transform_3dmat(Sy[:n_modes,:n_modes,:], psi_inv.T)
|
749
|
+
|
750
|
+
return Sg
|
751
|
+
|
752
|
+
|
753
|
+
def get_mode_ix(self, modes):
|
754
|
+
if modes is None:
|
755
|
+
modes = np.arange(0, self.results.S.shape[0])
|
756
|
+
|
757
|
+
if np.ndim(modes)==0:
|
758
|
+
modes = np.array([modes])
|
759
|
+
|
760
|
+
return np.array(modes), np.ix_(modes,modes, range(self.results.S.shape[2]))
|
761
|
+
|
762
|
+
@property
|
763
|
+
def n_pontoons(self):
|
764
|
+
return len(self.hydro.pontoons)
|
765
|
+
|
766
|
+
@property
|
767
|
+
def tmat(self):
|
768
|
+
return block_diag(*[pont.tmat for pont in self.hydro.pontoons])
|
769
|
+
|
770
|
+
|
771
|
+
|
772
|
+
@property
|
773
|
+
def pontoon_x(self):
|
774
|
+
return np.array([p.node.x[0] for p in self.hydro.pontoons])
|
775
|
+
|
776
|
+
@property
|
777
|
+
def pontoon_y(self):
|
778
|
+
return np.array([p.node.x[1] for p in self.hydro.pontoons])
|
779
|
+
|
780
|
+
@property
|
781
|
+
def pontoon_z(self):
|
782
|
+
return np.array([p.node.x[2] for p in self.hydro.pontoons])
|
783
|
+
|
784
|
+
|
785
|
+
def plot_2d_tmats(self, scale=50, show_labels=True, ax=None):
|
786
|
+
if ax is None:
|
787
|
+
fig, ax = plt.subplots()
|
788
|
+
|
789
|
+
for pontoon in self.hydro.pontoons:
|
790
|
+
x,y,__ = pontoon.node.coordinates
|
791
|
+
tmat = pontoon.tmat[:2,:2]*scale
|
792
|
+
ex, ey = tmat[0,:], tmat[1,:]
|
793
|
+
|
794
|
+
ax.plot([x,x+ex[0]], [y,y+ex[1]], color='blue')
|
795
|
+
ax.plot([x,x+ey[0]], [y,y+ey[1]], color='red')
|
796
|
+
|
797
|
+
if show_labels:
|
798
|
+
ax.text(x,y,pontoon.label, fontsize=7)
|
799
|
+
|
800
|
+
ax.axis('equal')
|
801
|
+
return ax
|
802
|
+
|
803
|
+
@classmethod
|
804
|
+
def from_nodes_and_types(cls, nodes, pontoon_types, rotation=0, prefix_label='pontoon-',
|
805
|
+
labels=None, pontoon_props=dict(), **kwargs):
|
806
|
+
if np.ndim(rotation)==0:
|
807
|
+
rotation = [rotation]*len(nodes)
|
808
|
+
|
809
|
+
pontoons = [None]*len(nodes)
|
810
|
+
for ix, node in enumerate(nodes):
|
811
|
+
if labels is None:
|
812
|
+
label = prefix_label+str(ix+1)
|
813
|
+
else:
|
814
|
+
label = labels[ix]
|
815
|
+
|
816
|
+
pontoons[ix] = Pontoon(node, pontoon_types[ix], rotation=rotation[ix], label=label)
|
817
|
+
|
818
|
+
for pontoon in pontoons:
|
819
|
+
for key in pontoon_props:
|
820
|
+
setattr(pontoon, key, pontoon_props[key])
|
821
|
+
if len(pontoons)>0:
|
822
|
+
return cls(hydro=Hydro(pontoons), **kwargs)
|
823
|
+
else:
|
824
|
+
return cls(**kwargs)
|
825
|
+
|
826
|
+
|
827
|
+
def run_eig(self, normalize=False, print_progress=True, freq_kind=True,
|
828
|
+
include=['aero', 'hydro', 'drag_elements', 'drag_pontoons'],
|
829
|
+
smooth=None, aero_sections=None, **kwargs):
|
830
|
+
|
831
|
+
include_dict = self.establish_include_dict(include)
|
832
|
+
|
833
|
+
if (('aero' in include_dict['k'] or 'aero' in include_dict['c']) and hasattr(self, 'aero')
|
834
|
+
and self.aero is not None and self.aero.Kfun is None):
|
835
|
+
self.aero.prepare_aero_matrices(aero_sections=aero_sections)
|
836
|
+
|
837
|
+
Kfun, Cfun, Mfun = self.get_system_matrices(include=include_dict)
|
838
|
+
|
839
|
+
if smooth is not None:
|
840
|
+
omega = smooth.pop('omega')
|
841
|
+
if Kfun is not None:
|
842
|
+
K = np.stack([Kfun(omi) for omi in omega], axis=2)
|
843
|
+
K = savgol_filter(K, **smooth)
|
844
|
+
Kfun = interp1d(omega, K, kind='quadratic')
|
845
|
+
|
846
|
+
if Cfun is not None:
|
847
|
+
C = np.stack([Cfun(omi) for omi in omega], axis=2)
|
848
|
+
C = savgol_filter(C, **smooth)
|
849
|
+
Cfun = interp1d(omega, C, kind='quadratic')
|
850
|
+
|
851
|
+
if Mfun is not None:
|
852
|
+
M = np.stack([Mfun(omi) for omi in omega], axis=2)
|
853
|
+
M = savgol_filter(M, **smooth)
|
854
|
+
Mfun = interp1d(omega, M, kind='quadratic')
|
855
|
+
|
856
|
+
if freq_kind:
|
857
|
+
fun = iteig_freq
|
858
|
+
else:
|
859
|
+
fun = iteig
|
860
|
+
|
861
|
+
lambd, psi, __ = fun(Kfun, Cfun, Mfun, print_progress=print_progress,
|
862
|
+
normalize=normalize, **kwargs)
|
863
|
+
|
864
|
+
self.results.psi = maxreal(psi)
|
865
|
+
self.results.lambd = lambd
|
866
|
+
self.results.include_eig = include
|
867
|
+
|
868
|
+
|
869
|
+
def run_static(self, aero_sections=None, include_selfexctied=['aero']):
|
870
|
+
if not hasattr(self.aero, 'F0_m') or (self.aero.F0_m is None): # check if already computed static forces
|
871
|
+
self.precompute_windaction(static=True, aero_sections=aero_sections) # compute if not present
|
872
|
+
|
873
|
+
if ('aero' in include_selfexctied) and (self.aero is not None) and (self.aero.Kfun is None):
|
874
|
+
K_ae, __ = self.get_aero_matrices(omega_reduced=0, aero_sections=aero_sections)
|
875
|
+
else:
|
876
|
+
K_ae = 0.0
|
877
|
+
|
878
|
+
Ktot = -K_ae + self.dry_K
|
879
|
+
self.results.y_static = np.linalg.inv(Ktot) @ self.aero.F0_m
|
880
|
+
|
881
|
+
|
882
|
+
def run_freqsim(self, omega, omega_aero=None, omega_hydro=None,
|
883
|
+
print_progress=True, interpolation_kind='linear',
|
884
|
+
max_rel_error=0.01, drag_iterations=0, tol=1e-2, include_action=['hydro', 'aero'],
|
885
|
+
include_selfexcited=['hydro', 'aero', 'drag_elements', 'drag_pontoons'], ensure_at_peaks=True,
|
886
|
+
theta_interpolation='linear', reset_Cquad_lin=True, aero_sections=None):
|
887
|
+
|
888
|
+
include_dict = self.establish_include_dict(include_selfexcited)
|
889
|
+
|
890
|
+
if (('aero' in include_dict['k'] or 'aero' in include_dict['c']) and hasattr(self, 'aero')
|
891
|
+
and (self.aero is not None) and (self.aero.Kfun is None)):
|
892
|
+
self.aero.prepare_aero_matrices(omega=omega_aero, aero_sections=aero_sections)
|
893
|
+
|
894
|
+
if reset_Cquad_lin and hasattr(self, 'hydro') and self.hydro is not None:
|
895
|
+
self.hydro.reset_linearized_drag()
|
896
|
+
self.Cquad_lin = 0.0
|
897
|
+
|
898
|
+
if ensure_at_peaks and hasattr(self.results, 'wd') and self.results.wd is not None:
|
899
|
+
wd = self.results.wd
|
900
|
+
omega_peaks = wd[(wd>=np.min(omega)) & (wd<=np.max(omega))]
|
901
|
+
omega = np.unique(np.hstack([omega, omega_peaks]))
|
902
|
+
|
903
|
+
if omega_hydro is None:
|
904
|
+
omega_hydro = omega*1
|
905
|
+
|
906
|
+
if omega_aero is None:
|
907
|
+
omega_aero = omega*1
|
908
|
+
|
909
|
+
# Excitation
|
910
|
+
Sqq_m = 0.0
|
911
|
+
|
912
|
+
if (self.hydro is not None) and (self.hydro.seastate is not None) and ('hydro' in include_action):
|
913
|
+
if hasattr(self.hydro, 'Sqq_hydro') and (self.hydro.Sqq_hydro is not None):
|
914
|
+
Sqq_m = Sqq_m + self.hydro.Sqq_hydro(omega)
|
915
|
+
else:
|
916
|
+
Sqq_hydro = self.evaluate_waveaction(omega_hydro, print_progress=print_progress,
|
917
|
+
max_rel_error=max_rel_error, theta_interpolation=theta_interpolation)
|
918
|
+
|
919
|
+
Sqq_m = Sqq_m + interp1d(omega_hydro, Sqq_hydro, kind=interpolation_kind, axis=2,
|
920
|
+
fill_value=0.0, bounds_error=False)(omega)
|
921
|
+
|
922
|
+
if (self.aero is not None) and (self.aero.windstate is not None) and ('aero' in include_action):
|
923
|
+
if hasattr(self.aero, 'Sqq_aero') and (self.aero.Sqq_aero is not None):
|
924
|
+
Sqq_m = Sqq_m + self.aero.Sqq_aero(omega)
|
925
|
+
else:
|
926
|
+
Sqq_aero = self.evaluate_windaction(omega_aero, print_progress=print_progress, aero_sections=aero_sections)
|
927
|
+
Sqq_m = Sqq_m + interp1d(omega_aero, Sqq_aero, kind=interpolation_kind,
|
928
|
+
axis=2, fill_value=0.0, bounds_error=False)(omega)
|
929
|
+
|
930
|
+
def runsim():
|
931
|
+
Hnum = eval_3d_fun(self.get_frf_fun(include=include_dict), omega) # System
|
932
|
+
Srr_m = freqsim(Sqq_m, Hnum) # Power-spectral density method
|
933
|
+
|
934
|
+
return Srr_m
|
935
|
+
|
936
|
+
Srr_m = runsim()
|
937
|
+
|
938
|
+
# Linearized drag damping
|
939
|
+
if drag_iterations>0:
|
940
|
+
Clin_pontoons = np.diag(np.zeros(len(self.hydro.pontoons)*6)) # initialize for convergence check
|
941
|
+
|
942
|
+
if ('drag_elements' in include_dict['c']) and hasattr(self, 'Cquad'):
|
943
|
+
Cquad_model = self.Cquad * 1
|
944
|
+
model_converged = False
|
945
|
+
phi_model = self.get_dry_phi(key='full')
|
946
|
+
vnodes = np.zeros(phi_model.shape[0])
|
947
|
+
else:
|
948
|
+
Cquad_model = None
|
949
|
+
model_converged = True
|
950
|
+
|
951
|
+
if 'drag_pontoons' in include_dict['c']:
|
952
|
+
pontoons_converged = False
|
953
|
+
vp = np.zeros(Clin_pontoons.shape[0])
|
954
|
+
Cquad_pontoons = self.hydro.get_all_cquad()
|
955
|
+
else:
|
956
|
+
Cquad_pontoons = None
|
957
|
+
pontoons_converged = True
|
958
|
+
|
959
|
+
for n in range(drag_iterations):
|
960
|
+
if print_progress:
|
961
|
+
pp(n+1, drag_iterations, postfix=f' DRAG LINEARIZATION - RUNNING ITERATION {n+1}')
|
962
|
+
|
963
|
+
#------ PONTOONS ----------------------------
|
964
|
+
if 'drag_pontoons' in include_dict['c']:
|
965
|
+
vp_prev = vp * 1
|
966
|
+
vp = np.sqrt(var_from_modal(omega, Srr_m*omega**2, self.get_dry_phi(key='hydro')))
|
967
|
+
Cquad_lin_pontoons = stochastic_linearize(Cquad_pontoons, vp)
|
968
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
969
|
+
p.Cquad_lin = Cquad_lin_pontoons[ix*6:ix*6+6, ix*6:ix*6+6]
|
970
|
+
|
971
|
+
if np.linalg.norm(vp - vp_prev) < tol*(np.max([np.linalg.norm(vp), np.linalg.norm(vp_prev)])):
|
972
|
+
pontoons_converged = True
|
973
|
+
|
974
|
+
#------ DRAG ELEMENTS -----------------------
|
975
|
+
if ('drag_elements' in include_dict['c']) and (Cquad_model is not None):
|
976
|
+
vnodes_prev = vnodes*1
|
977
|
+
var_udot = var_from_modal(omega, Srr_m*omega**2, phi_model, only_diagonal=False)
|
978
|
+
self.Cquad_lin = phi_model.T @ self.get_Cquad_lin(var_udot, local=False) @ phi_model
|
979
|
+
vnodes = np.sqrt(np.diag(var_udot))
|
980
|
+
if np.linalg.norm(vnodes - vnodes_prev) < tol*(np.max([np.linalg.norm(vnodes), np.linalg.norm(vnodes_prev)])):
|
981
|
+
model_converged = True
|
982
|
+
|
983
|
+
# Convergence check
|
984
|
+
if pontoons_converged and model_converged and print_progress:
|
985
|
+
print('\n STOPPING ITERATION. Linearized damping converged by assertion with specified tolerance criterion.')
|
986
|
+
break
|
987
|
+
|
988
|
+
Srr_m = runsim()
|
989
|
+
|
990
|
+
# STORE RESULTS
|
991
|
+
self.results.omega = omega*1
|
992
|
+
self.results.S = Srr_m*1
|
993
|
+
|
994
|
+
def assign_windstate(self, windstate):
|
995
|
+
self.aero.windstate = windstate
|
996
|
+
|
997
|
+
def assign_seastate(self, seastate=None):
|
998
|
+
# Reset pontoon settings
|
999
|
+
self.hydro.assign_to_pontoons(**dict(current_affects_k=None, current_affects_Q=None))
|
1000
|
+
self.hydro.Sqq_hydro = None
|
1001
|
+
|
1002
|
+
if seastate is None:
|
1003
|
+
seastate = self.hydro.seastate
|
1004
|
+
else:
|
1005
|
+
self.hydro.seastate = seastate
|
1006
|
+
|
1007
|
+
for pontoon in self.hydro.pontoons:
|
1008
|
+
pontoon.seastate = seastate
|
1009
|
+
|
1010
|
+
if seastate is not None:
|
1011
|
+
self.hydro.assign_to_pontoons(**self.hydro.seastate.pontoon_options)
|
1012
|
+
|
1013
|
+
def prepare_waveaction(self):
|
1014
|
+
x, y = self.get_all_pos()
|
1015
|
+
|
1016
|
+
xmesh, ymesh = np.meshgrid(x,x), np.meshgrid(y,y)
|
1017
|
+
dx = xmesh[0]-xmesh[1]
|
1018
|
+
dy = ymesh[0]-ymesh[1]
|
1019
|
+
ds = np.sqrt(dx**2+dy**2)
|
1020
|
+
|
1021
|
+
min_ix = np.argmin(np.max(ds, axis=0))
|
1022
|
+
max_distances = ds[min_ix,:]
|
1023
|
+
|
1024
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1025
|
+
p.max_distance = max_distances[ix]
|
1026
|
+
|
1027
|
+
pont_max_distance = self.hydro.pontoons[np.argmax(max_distances)]
|
1028
|
+
self.get_theta_int = pont_max_distance.get_theta_int
|
1029
|
+
|
1030
|
+
|
1031
|
+
|
1032
|
+
def get_waveaction(self, omega_k, max_rel_error=0.01,
|
1033
|
+
theta_interpolation='linear', theta_int=None):
|
1034
|
+
|
1035
|
+
if theta_int is None and self.theta_int is None:
|
1036
|
+
theta_int = self.get_theta_int(omega_k, max_rel_error=max_rel_error)
|
1037
|
+
elif theta_int is None:
|
1038
|
+
theta_int = self.theta_int
|
1039
|
+
|
1040
|
+
Z = np.zeros([self.hydro.ndofs, len(theta_int)]).astype('complex')
|
1041
|
+
|
1042
|
+
for pontoon_index, pontoon in enumerate(self.hydro.pontoons):
|
1043
|
+
Z[pontoon_index*6:pontoon_index*6+6, :] = pontoon.get_Z(omega_k, theta_int,
|
1044
|
+
theta_interpolation=theta_interpolation,
|
1045
|
+
local=self.local, x0=self.x0)
|
1046
|
+
|
1047
|
+
if self.hydro.seastate.short_crested:
|
1048
|
+
# first and last point in trapezoidal integration has 1/2 as factor, others have 1
|
1049
|
+
# verified to match for loop over angles and trapz integration.
|
1050
|
+
dtheta = theta_int[1] - theta_int[0]
|
1051
|
+
Z[:, 0] = np.sqrt(0.5)*Z[:, 0]
|
1052
|
+
Z[:, -1] = np.sqrt(0.5)*Z[:, -1]
|
1053
|
+
Sqq0 = dtheta * Z @ Z.conj().T
|
1054
|
+
else:
|
1055
|
+
Sqq0 = Z @ Z.conj().T
|
1056
|
+
|
1057
|
+
if not self.hydro.seastate.options['keep_coherence']:
|
1058
|
+
Sqq0 = block_diag(*[Sqq0[i*6:(i+1)*6, i*6:(i+1)*6] for i in range(int(Sqq0.shape[0]/6))])
|
1059
|
+
|
1060
|
+
return self.hydro.phi.T @ Sqq0 @ self.hydro.phi
|
1061
|
+
|
1062
|
+
|
1063
|
+
def evaluate_windaction(self, omega=None, aero_sections=None, print_progress=True, static=False, **kwargs):
|
1064
|
+
if aero_sections is None:
|
1065
|
+
aero_sections = self.aero.elements.keys()
|
1066
|
+
|
1067
|
+
Sae_m = 0.0
|
1068
|
+
T = self.aero.windstate.T
|
1069
|
+
U = self.aero.windstate.U
|
1070
|
+
rho = self.aero.windstate.rho
|
1071
|
+
|
1072
|
+
# Sections needs to be merged - all elements are therefore unwrapped
|
1073
|
+
els = [a for b in [self.aero.elements[sec] for sec in aero_sections] for a in b] # all requested sections, flattened
|
1074
|
+
eldef = self.eldef.get_element_subset(self.eldef.get_elements([el.label for el in els]), renumber=False) # create new eldef for requested elements
|
1075
|
+
phi = self.get_dry_phi(key='full')[eldef.global_dofs, :] # grab relevant phi components
|
1076
|
+
eldef.assign_global_dofs()
|
1077
|
+
nodes = eldef.nodes*1
|
1078
|
+
els = eldef.elements*1
|
1079
|
+
|
1080
|
+
lc = {sec: self.aero.sections[sec].all_lc for sec in aero_sections} # dict with all load coefficients
|
1081
|
+
B = {sec: self.aero.sections[sec].B for sec in aero_sections} # dict with all load coefficients
|
1082
|
+
D = {sec: self.aero.sections[sec].D for sec in aero_sections} # dict with all load coefficients
|
1083
|
+
S = self.aero.get_generic_kaimal(nodes=nodes)
|
1084
|
+
section_lookup = {sec: self.aero.elements[sec] for sec in aero_sections}
|
1085
|
+
|
1086
|
+
if static:
|
1087
|
+
F0_m = windaction_static(lc, els, T, phi,
|
1088
|
+
B, D, U, print_progress=print_progress, rho=rho,
|
1089
|
+
section_lookup=section_lookup, nodes=nodes)
|
1090
|
+
return F0_m
|
1091
|
+
else:
|
1092
|
+
admittance = {sec: self.aero.sections[sec].admittance for sec in aero_sections}
|
1093
|
+
Sae_m_fun = windaction(omega, S, lc, els, T, phi,
|
1094
|
+
B, D, U, print_progress=print_progress, rho=rho,
|
1095
|
+
section_lookup=section_lookup, nodes=nodes, admittance=admittance, **kwargs)
|
1096
|
+
|
1097
|
+
Sae_m = np.stack([Sae_m_fun(om_k) for om_k in omega], axis=2)
|
1098
|
+
|
1099
|
+
return Sae_m
|
1100
|
+
|
1101
|
+
def evaluate_windaction_static(self, aero_sections=None, print_progress=True, **kwargs):
|
1102
|
+
return self.evaluate_windaction(aero_sections=None, print_progress=True, static=True, **kwargs)
|
1103
|
+
|
1104
|
+
def precompute_windaction(self, omega, include=['dynamic'], interpolation_kind='linear', **kwargs):
|
1105
|
+
|
1106
|
+
if 'dynamic' in include:
|
1107
|
+
self.aero.Sqq_aero = interp1d(omega, self.evaluate_windaction(omega=omega, static=False, **kwargs),
|
1108
|
+
kind=interpolation_kind, axis=2, fill_value=0.0, bounds_error=False)
|
1109
|
+
if 'static' in include:
|
1110
|
+
self.aero.F0_m = self.evaluate_windaction(static=True, **kwargs)
|
1111
|
+
|
1112
|
+
|
1113
|
+
def precompute_waveaction(self, omega, interpolation_kind='linear', method='standard', **kwargs):
|
1114
|
+
if method=='standard':
|
1115
|
+
Sqq0 = self.evaluate_waveaction(omega, **kwargs)
|
1116
|
+
|
1117
|
+
elif method=='fft':
|
1118
|
+
if not self.hydro.seastate.homogeneous:
|
1119
|
+
raise ValueError('Only method standard" is supported for inhomogeneous conditions')
|
1120
|
+
# Sqq0 = transform_3dmat(waveaction_fft(self.hydro.pontoons, omega, **kwargs), self.hydro.phi)
|
1121
|
+
raise NotImplementedError('FFT not implemented yet. Will be at some point.')
|
1122
|
+
|
1123
|
+
elif method in ['fourier', 'cos2s', 'cos2s-fourer'] :
|
1124
|
+
if not self.hydro.homogeneous:
|
1125
|
+
raise ValueError('Only method standard" is supported for inhomogeneous conditions')
|
1126
|
+
|
1127
|
+
raise NotImplementedError('Fourier (cos 2s) not implemented yet. Will be at some point.')
|
1128
|
+
|
1129
|
+
self.hydro.Sqq_hydro = interp1d(omega, Sqq0, kind=interpolation_kind, axis=2, fill_value=0.0, bounds_error=False)
|
1130
|
+
|
1131
|
+
|
1132
|
+
|
1133
|
+
def evaluate_waveaction(self, omega, max_rel_error=0.01, print_progress=True, theta_int=None,
|
1134
|
+
theta_interpolation='quadratic', **kwargs):
|
1135
|
+
|
1136
|
+
if theta_int is None:
|
1137
|
+
theta_int = self.theta_int
|
1138
|
+
|
1139
|
+
ndofs = self.hydro.phi.shape[1]
|
1140
|
+
Sqq = np.zeros([ndofs, ndofs, len(omega)]).astype('complex')
|
1141
|
+
for k, omega_k in enumerate(omega):
|
1142
|
+
Sqq[:,:,k] = self.get_waveaction(omega_k, max_rel_error=max_rel_error, theta_int=theta_int,
|
1143
|
+
theta_interpolation=theta_interpolation, **kwargs)
|
1144
|
+
|
1145
|
+
if print_progress:
|
1146
|
+
pp(k+1, len(omega), postfix=' ESTABLISHING WAVE EXCITATION ')
|
1147
|
+
|
1148
|
+
return Sqq
|
1149
|
+
|
1150
|
+
# FRF
|
1151
|
+
def get_added_frf(self, omega_k, inverse=False):
|
1152
|
+
if inverse:
|
1153
|
+
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)
|
1154
|
+
else:
|
1155
|
+
return np.linalg.inv(-omega_k**2*self.get_added_M(omega_k) +
|
1156
|
+
1j*omega_k*self.get_added_C(omega_k) + self.get_added_K(omega_k))
|
1157
|
+
|
1158
|
+
def get_dry_frf(self, omega_k, inverse=False):
|
1159
|
+
if inverse:
|
1160
|
+
return -omega_k**2*self.dry_M + 1j*omega_k*self.dry_C + self.dry_K
|
1161
|
+
else:
|
1162
|
+
return np.linalg.inv(-omega_k**2*self.dry_M + 1j*omega_k*self.dry_C + self.dry_K)
|
1163
|
+
|
1164
|
+
def get_aero_K(self, omega_k):
|
1165
|
+
if self.aero is not None:
|
1166
|
+
K_aero = self.aero.Kfun(omega_k)
|
1167
|
+
else:
|
1168
|
+
K_aero = 0.0
|
1169
|
+
|
1170
|
+
return -K_aero
|
1171
|
+
|
1172
|
+
def get_aero_C(self, omega_k):
|
1173
|
+
|
1174
|
+
if self.aero is not None:
|
1175
|
+
C_aero = self.aero.Cfun(omega_k)
|
1176
|
+
else:
|
1177
|
+
C_aero = 0.0
|
1178
|
+
|
1179
|
+
return -C_aero
|
1180
|
+
|
1181
|
+
|
1182
|
+
def get_aero_M(self, omega_k):
|
1183
|
+
return 0.0
|
1184
|
+
|
1185
|
+
def get_hydro_K(self, omega_k):
|
1186
|
+
if self.hydro is not None:
|
1187
|
+
mat = self.initialize_const_matrix()
|
1188
|
+
|
1189
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1190
|
+
mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_K(omega_k, local=self.local)
|
1191
|
+
return self.hydro.phi.T @ mat @ self.hydro.phi
|
1192
|
+
else:
|
1193
|
+
return 0.0
|
1194
|
+
|
1195
|
+
def get_hydro_C(self, omega_k):
|
1196
|
+
if self.hydro is not None:
|
1197
|
+
mat = self.initialize_const_matrix()
|
1198
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1199
|
+
mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_C(omega_k, local=self.local)
|
1200
|
+
return self.hydro.phi.T @ mat @ self.hydro.phi
|
1201
|
+
else:
|
1202
|
+
return 0.0
|
1203
|
+
|
1204
|
+
|
1205
|
+
def get_hydro_M(self, omega_k):
|
1206
|
+
if self.hydro is not None:
|
1207
|
+
mat = self.initialize_const_matrix()
|
1208
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1209
|
+
mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_M(omega_k, local=self.local)
|
1210
|
+
|
1211
|
+
return self.hydro.phi.T @ mat @ self.hydro.phi
|
1212
|
+
else:
|
1213
|
+
return 0.0
|
1214
|
+
|
1215
|
+
|
1216
|
+
def get_added_K(self, omega_k):
|
1217
|
+
if self.hydro is not None:
|
1218
|
+
mat = self.initialize_const_matrix()
|
1219
|
+
|
1220
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1221
|
+
mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_K(omega_k, local=self.local)
|
1222
|
+
K_hydro = self.hydro.phi.T @ mat @ self.hydro.phi
|
1223
|
+
else:
|
1224
|
+
K_hydro = 0.0
|
1225
|
+
|
1226
|
+
if self.aero is not None:
|
1227
|
+
K_aero = self.aero.Kfun(omega_k)
|
1228
|
+
else:
|
1229
|
+
K_aero = 0.0
|
1230
|
+
|
1231
|
+
return K_hydro - K_aero
|
1232
|
+
|
1233
|
+
def get_added_C(self, omega_k):
|
1234
|
+
if self.hydro is not None:
|
1235
|
+
mat = self.initialize_const_matrix()
|
1236
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1237
|
+
mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_C(omega_k, local=self.local)
|
1238
|
+
C_hydro = self.hydro.phi.T @ mat @ self.hydro.phi
|
1239
|
+
else:
|
1240
|
+
C_hydro = 0.0
|
1241
|
+
|
1242
|
+
if self.aero is not None:
|
1243
|
+
C_aero = self.aero.Cfun(omega_k)
|
1244
|
+
else:
|
1245
|
+
C_aero = 0.0
|
1246
|
+
|
1247
|
+
return C_hydro - C_aero
|
1248
|
+
|
1249
|
+
|
1250
|
+
def get_added_M(self, omega_k):
|
1251
|
+
if self.hydro is not None:
|
1252
|
+
mat = self.initialize_const_matrix()
|
1253
|
+
for ix, p in enumerate(self.hydro.pontoons):
|
1254
|
+
mat[ix*6:ix*6+6, ix*6:ix*6+6] = p.get_M(omega_k, local=self.local)
|
1255
|
+
|
1256
|
+
return self.hydro.phi.T @ mat @ self.hydro.phi
|
1257
|
+
else:
|
1258
|
+
return 0.0
|
1259
|
+
|
1260
|
+
@staticmethod
|
1261
|
+
def establish_include_dict(include):
|
1262
|
+
if type(include) is not dict:
|
1263
|
+
include_dict = dict()
|
1264
|
+
keys = ['k', 'c', 'm']
|
1265
|
+
for key in keys:
|
1266
|
+
include_dict[key] = include*1
|
1267
|
+
return include_dict
|
1268
|
+
else:
|
1269
|
+
replacement_keys = dict(stiffness='k', mass='m', damping='c')
|
1270
|
+
include_dict = {replacement_keys.get(k, k): v for k, v in include.items()}
|
1271
|
+
return include_dict
|
1272
|
+
|
1273
|
+
def get_system_matrices(self, include=['hydro', 'aero', 'drag_elements', 'drag_pontoons']):
|
1274
|
+
include_dict = self.establish_include_dict(include)
|
1275
|
+
|
1276
|
+
# Stiffness
|
1277
|
+
if ('hydro' in include_dict['k'] and self.hydro is not None) and ('aero' in include_dict['k'] and self.aero is not None):
|
1278
|
+
Kfun = fun_const_sum(self.get_added_K, self.dry_K)
|
1279
|
+
elif ('hydro' in include_dict['k'] and self.hydro is not None):
|
1280
|
+
Kfun = fun_const_sum(self.get_hydro_K, self.dry_K)
|
1281
|
+
elif ('aero' in include_dict['k'] and self.aero is not None):
|
1282
|
+
Kfun = fun_const_sum(self.get_aero_K, self.dry_K)
|
1283
|
+
else:
|
1284
|
+
Kfun = lambda omega_k: self.dry_K
|
1285
|
+
|
1286
|
+
# Damping
|
1287
|
+
dry_C = self.dry_C*1
|
1288
|
+
if 'drag_elements' in include_dict['c']:
|
1289
|
+
dry_C += self.Cquad_lin
|
1290
|
+
if ('drag_pontoons' in include_dict['c'] and self.hydro is not None):
|
1291
|
+
dry_C += self.Cquad_lin_pontoons
|
1292
|
+
|
1293
|
+
if ('hydro' in include_dict['c'] and self.hydro is not None) and ('aero' in include_dict['c'] and self.aero is not None):
|
1294
|
+
Cfun = fun_const_sum(self.get_added_C, dry_C)
|
1295
|
+
elif ('hydro' in include_dict['c'] and self.hydro is not None):
|
1296
|
+
Cfun = fun_const_sum(self.get_hydro_C, dry_C)
|
1297
|
+
elif 'aero' in include_dict['c'] and self.aero is not None:
|
1298
|
+
Cfun = fun_const_sum(self.get_aero_C, dry_C)
|
1299
|
+
else:
|
1300
|
+
Cfun = lambda omega_k: dry_C
|
1301
|
+
|
1302
|
+
# Mass
|
1303
|
+
if ('hydro' in include_dict['m'] and self.hydro is not None) and ('aero' in include_dict['m'] and self.aero is not None):
|
1304
|
+
Mfun = fun_const_sum(self.get_added_M, self.dry_M)
|
1305
|
+
elif ('hydro' in include_dict['m'] and self.hydro is not None):
|
1306
|
+
Mfun = fun_const_sum(self.get_hydro_M, self.dry_M)
|
1307
|
+
elif 'aero' in include_dict['m'] and self.aero is not None:
|
1308
|
+
Mfun = fun_const_sum(self.get_aero_M, self.dry_M)
|
1309
|
+
else:
|
1310
|
+
Mfun = lambda omega_k: self.dry_M
|
1311
|
+
|
1312
|
+
return Kfun, Cfun, Mfun
|
1313
|
+
|
1314
|
+
|
1315
|
+
def get_frf_fun(self, include=['hydro', 'aero', 'drag_elements'], opt = 0):
|
1316
|
+
Kfun, Cfun, Mfun = self.get_system_matrices(include)
|
1317
|
+
|
1318
|
+
def frf(omega_k):
|
1319
|
+
return np.linalg.inv(-omega_k**2*Mfun(omega_k) + 1j*omega_k*Cfun(omega_k) + Kfun(omega_k))
|
1320
|
+
|
1321
|
+
def imp(omega_k):
|
1322
|
+
return (-omega_k**2*Mfun(omega_k) + 1j*omega_k*Cfun(omega_k) + Kfun(omega_k))
|
1323
|
+
if opt == 0:
|
1324
|
+
return frf
|
1325
|
+
else:
|
1326
|
+
return imp
|
1327
|
+
|
1328
|
+
def get_node_ix(self, nodelabel):
|
1329
|
+
ix = np.where(self.eldef.get_node_labels()==nodelabel)[0]
|
1330
|
+
if len(ix)>0:
|
1331
|
+
ix = int(ix[0])
|
1332
|
+
else:
|
1333
|
+
ix = None
|
1334
|
+
|
1335
|
+
return ix
|
1336
|
+
|
1337
|
+
def get_node_ixs(self, nodelabels):
|
1338
|
+
return [self.get_node_ix(nodelabel) for nodelabel in nodelabels]
|