wawi 0.0.1__py3-none-any.whl → 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wawi/__init__.py +8 -4
- wawi/fe.py +134 -0
- wawi/general.py +468 -0
- wawi/identification.py +66 -0
- wawi/io.py +719 -0
- wawi/modal.py +608 -0
- wawi/plot.py +569 -0
- wawi/prob.py +9 -0
- wawi/random.py +38 -0
- wawi/signal.py +45 -0
- wawi/structural.py +278 -0
- wawi/time_domain.py +126 -0
- wawi/tools.py +7 -0
- wawi/wave.py +491 -0
- wawi/wind.py +1109 -0
- wawi/wind_code.py +14 -0
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/METADATA +7 -6
- wawi-0.0.5.dist-info/RECORD +21 -0
- wawi-0.0.1.dist-info/RECORD +0 -6
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/LICENSE +0 -0
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/WHEEL +0 -0
- {wawi-0.0.1.dist-info → wawi-0.0.5.dist-info}/top_level.txt +0 -0
wawi/io.py
ADDED
@@ -0,0 +1,719 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
import numpy as np
|
3
|
+
import re
|
4
|
+
import json
|
5
|
+
import csv
|
6
|
+
import dill
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
from wawi.model import ModalDry, PontoonType, Node, Model, Aero, AeroSection
|
10
|
+
|
11
|
+
def import_folder(model_folder, pontoon_stl='pontoon.stl', aero_sections='aero_sections.json',
|
12
|
+
modal='modal.json', pontoon='pontoon.json', eldef='element.json', orientations='orientations.json',
|
13
|
+
drag_elements='drag_elements.json', pontoon_types='pontoon_types.json',
|
14
|
+
sort_nodes_by_x=True, interpolation_kind='quadratic'):
|
15
|
+
|
16
|
+
'''
|
17
|
+
Import folder containing files defining a WAWI model.
|
18
|
+
|
19
|
+
Parameters
|
20
|
+
---------------
|
21
|
+
model_folder : str
|
22
|
+
string defining path of root
|
23
|
+
pontoon_stl : 'pontoon.stl', optional
|
24
|
+
string to relative path of stl-file with pontoon (3d file)
|
25
|
+
aero_sections : 'aero_sections.json', optional
|
26
|
+
string to relative path of json file with aerodynamic sections
|
27
|
+
modal : 'modal.json', optional
|
28
|
+
string to relative path of dry modal definitions
|
29
|
+
pontoon : 'pontoon.json', optional
|
30
|
+
string to relative path of pontoon definitions
|
31
|
+
eldef : 'element.json', optional
|
32
|
+
string to relative path of element definitions (node and element matrices)
|
33
|
+
orientations : 'orientations.json', optional
|
34
|
+
string to relative path of orientations of aero elements
|
35
|
+
drag_elements : 'drag_elements.json', optional
|
36
|
+
string to relative path of file defining drag elements
|
37
|
+
pontoon_types : 'pontoon_types.json', optional
|
38
|
+
string to relative path of pontoon types
|
39
|
+
sort_nodes_by_x : True, optional
|
40
|
+
whether or not to sort the nodes of eldef by their x-coordinate
|
41
|
+
interpolation_kind : {'quadratic', 'linear', ...}
|
42
|
+
interpolation kind used for hydrodynamic transfer function
|
43
|
+
|
44
|
+
Returns
|
45
|
+
---------------
|
46
|
+
model : `wawi.model.Model`
|
47
|
+
WAWI model object
|
48
|
+
|
49
|
+
Notes
|
50
|
+
---------------
|
51
|
+
TODO: Add info here describing the structure and contents of all files.
|
52
|
+
|
53
|
+
See `importing-folder.ipynb` for a full example.
|
54
|
+
'''
|
55
|
+
|
56
|
+
|
57
|
+
from beef.fe import Part, Section
|
58
|
+
from beef.general import n2d_ix
|
59
|
+
|
60
|
+
model_folder = Path(model_folder)
|
61
|
+
|
62
|
+
# Load pontoon data
|
63
|
+
pontoon_data = {}
|
64
|
+
if pontoon is not None:
|
65
|
+
try:
|
66
|
+
with open(model_folder / pontoon, 'r') as f:
|
67
|
+
pontoon_data = json.load(f)
|
68
|
+
except:
|
69
|
+
print('Valid pontoon data not found. No pontoons created.')
|
70
|
+
|
71
|
+
|
72
|
+
# Load modal data
|
73
|
+
with open(model_folder / modal, 'r') as f:
|
74
|
+
modal = json.load(f)
|
75
|
+
|
76
|
+
# Load element definitions
|
77
|
+
if eldef is not None:
|
78
|
+
try:
|
79
|
+
with open(model_folder / eldef, 'r') as f:
|
80
|
+
element_data = json.load(f)
|
81
|
+
except:
|
82
|
+
print('Valid element data not found. No eldef created.')
|
83
|
+
element_data = {}
|
84
|
+
else:
|
85
|
+
element_data = {}
|
86
|
+
|
87
|
+
if orientations is not None:
|
88
|
+
try:
|
89
|
+
with open(model_folder / orientations, 'r') as f:
|
90
|
+
orientations = json.load(f)
|
91
|
+
except:
|
92
|
+
print('Specified orientations file found. No orientation definitions applied.')
|
93
|
+
orientations = {}
|
94
|
+
else:
|
95
|
+
orientations = {}
|
96
|
+
|
97
|
+
|
98
|
+
common_ptype_settings = None
|
99
|
+
if pontoon_types is not None:
|
100
|
+
try:
|
101
|
+
with open(model_folder / pontoon_types, 'r') as f:
|
102
|
+
pontoon_type_settings = json.load(f)
|
103
|
+
|
104
|
+
if '*' in pontoon_type_settings:
|
105
|
+
common_ptype_settings = pontoon_type_settings.pop('*')
|
106
|
+
|
107
|
+
except:
|
108
|
+
print('Valid pontoon type settings file not found. No definitions applied.')
|
109
|
+
pontoon_type_settings = {}
|
110
|
+
|
111
|
+
else:
|
112
|
+
pontoon_type_settings = {}
|
113
|
+
|
114
|
+
# Extract specific pontoon data
|
115
|
+
pontoon_names = [key for key in pontoon_data]
|
116
|
+
pontoon_nodes = [Node(pontoon_data[key]['node'], *pontoon_data[key]['coordinates']) for key in pontoon_data]
|
117
|
+
pontoon_rotation = [pontoon_data[key]['rotation']*np.pi/180 for key in pontoon_data]
|
118
|
+
pontoon_types = [pontoon_data[key]['pontoon_type'] for key in pontoon_data]
|
119
|
+
|
120
|
+
# Pontoon types
|
121
|
+
unique_pontoon_types = list(set(pontoon_types))
|
122
|
+
files = [f'{model_folder}/{pt}.npz' for pt in unique_pontoon_types]
|
123
|
+
ptypes = dict()
|
124
|
+
|
125
|
+
if len(pontoon_type_settings)==0:
|
126
|
+
pontoon_type_settings = {key: {} for key in unique_pontoon_types}
|
127
|
+
|
128
|
+
if common_ptype_settings: #universal setting, overwriting others
|
129
|
+
for name in pontoon_type_settings:
|
130
|
+
for par in common_ptype_settings:
|
131
|
+
pontoon_type_settings[name][par] = common_ptype_settings[par]
|
132
|
+
|
133
|
+
for file in files:
|
134
|
+
name = Path(file).stem
|
135
|
+
P = np.load(file)
|
136
|
+
|
137
|
+
if 'Cd' in P:
|
138
|
+
Cd = P['Cd']
|
139
|
+
else:
|
140
|
+
Cd = np.array([0,0,0,0,0,0])
|
141
|
+
|
142
|
+
if 'A' in P:
|
143
|
+
A = P['A']
|
144
|
+
else:
|
145
|
+
A = np.array([0,0,0,0,0,0])
|
146
|
+
|
147
|
+
ACd = {'area': A, 'Cd':Cd}
|
148
|
+
ptypes[name] = PontoonType.from_numeric(interpolation_kind=interpolation_kind, A=P['M'], B=P['C'],
|
149
|
+
omega=P['omega'], Q=P['Q'], theta=P['theta_Q'], omegaQ=P['omega_Q'], label=name,
|
150
|
+
stl_path=f'{model_folder}/{pontoon_stl}', **{**ACd, **pontoon_type_settings[name]})
|
151
|
+
|
152
|
+
|
153
|
+
# Modal dry object
|
154
|
+
if 'xi0' in modal:
|
155
|
+
xi0 = np.array(modal.pop('xi0'))
|
156
|
+
else:
|
157
|
+
xi0 = 0.0
|
158
|
+
|
159
|
+
if 'm_min' in modal:
|
160
|
+
m_min = modal.pop('m_min')
|
161
|
+
else:
|
162
|
+
m_min = 0.0
|
163
|
+
|
164
|
+
if 'phi_x' in modal:
|
165
|
+
phi_x = modal.pop('phi_x')
|
166
|
+
else:
|
167
|
+
phi_x = None
|
168
|
+
|
169
|
+
if 'local' in modal:
|
170
|
+
local_phi = modal.pop('local')
|
171
|
+
else:
|
172
|
+
local_phi = False
|
173
|
+
|
174
|
+
phi = modal.pop('phi')
|
175
|
+
|
176
|
+
modal_dry = ModalDry(phi, xi0=xi0, local_phi=local_phi, phi_x=phi_x, m_min=m_min, **modal)
|
177
|
+
|
178
|
+
# Element definition
|
179
|
+
if element_data != {}:
|
180
|
+
if 'sections' in element_data:
|
181
|
+
sd = element_data['sections']
|
182
|
+
section_dict = {key: Section(E=sd[key]['E'], poisson=sd[key]['poisson'],
|
183
|
+
A=sd[key]['A'], I_y=sd[key]['Iy'], I_z=sd[key]['Iz'],
|
184
|
+
J=sd[key]['J'], m=sd[key]['m'], name=key) for key in sd}
|
185
|
+
|
186
|
+
sections = [section_dict[key] for key in element_data['section_assignment']]
|
187
|
+
else:
|
188
|
+
sections = None
|
189
|
+
|
190
|
+
node_matrix = np.array(element_data['node_matrix'])
|
191
|
+
node_matrix[:,0] = node_matrix[:,0].astype(int)
|
192
|
+
element_matrix = np.array(element_data['element_matrix'])
|
193
|
+
element_matrix = element_matrix.astype(int)
|
194
|
+
|
195
|
+
# Remove elements without valid nodes
|
196
|
+
remove_ix = []
|
197
|
+
remove_els = []
|
198
|
+
|
199
|
+
for ix,row in enumerate(element_matrix):
|
200
|
+
el,node1,node2 = row
|
201
|
+
# print(row)
|
202
|
+
|
203
|
+
if (node1 not in node_matrix[:,0].astype(int)) or (node2 not in node_matrix[:,0].astype(int)):
|
204
|
+
remove_ix.append(ix)
|
205
|
+
remove_els.append(el)
|
206
|
+
|
207
|
+
if len(remove_els)>0:
|
208
|
+
print(f'Elements {remove_els} do not have valid nodes - not included in model.')
|
209
|
+
|
210
|
+
element_matrix = np.delete(element_matrix, remove_ix, axis=0)
|
211
|
+
|
212
|
+
eldef = Part(node_matrix, element_matrix, sections=sections,
|
213
|
+
assemble=False, forced_ndofs=6)
|
214
|
+
|
215
|
+
if sort_nodes_by_x:
|
216
|
+
ix = np.argsort([n.coordinates[0] for n in eldef.nodes])
|
217
|
+
node_labels_sorted = eldef.get_node_labels()[ix]
|
218
|
+
eldef.arrange_nodes(node_labels_sorted, arrange_dof_ixs=True)
|
219
|
+
|
220
|
+
if 'full' in modal_dry.phi_full: #adjust phi_full
|
221
|
+
modal_dry.phi_full['full'] = modal_dry.phi_full['full'][n2d_ix(ix, n_dofs=6), :]
|
222
|
+
|
223
|
+
# Assign orientation
|
224
|
+
for key in orientations:
|
225
|
+
if 'e2' in orientations[key]:
|
226
|
+
e2 = orientations[key]['e2']
|
227
|
+
e3 = None
|
228
|
+
elif 'e3' in orientations[key]:
|
229
|
+
e3 = orientations[key]['e3']
|
230
|
+
e2 = None
|
231
|
+
else:
|
232
|
+
raise ValueError('Orientations should contain either e2 or e3')
|
233
|
+
|
234
|
+
elements = orientations[key]['elements']
|
235
|
+
for el_label in elements:
|
236
|
+
el = eldef.get_element(int(el_label))
|
237
|
+
|
238
|
+
el.assign_e2(e2)
|
239
|
+
el.assign_e3(e3)
|
240
|
+
|
241
|
+
el.initiate_geometry()
|
242
|
+
else:
|
243
|
+
eldef = None
|
244
|
+
|
245
|
+
# Create model object (only hydro part)
|
246
|
+
model = Model.from_nodes_and_types(pontoon_nodes, [ptypes[pt] for pt in pontoon_types], modal_dry=modal_dry,
|
247
|
+
rotation=pontoon_rotation, eldef=eldef, labels=pontoon_names)
|
248
|
+
|
249
|
+
# Aero mode
|
250
|
+
if aero_sections is not None:
|
251
|
+
# Load aero sections
|
252
|
+
try:
|
253
|
+
with open(model_folder / aero_sections, 'r') as f:
|
254
|
+
data = json.load(f)
|
255
|
+
|
256
|
+
sections = dict()
|
257
|
+
element_assignments = dict()
|
258
|
+
for key in data:
|
259
|
+
element_assignments[key] = data[key].pop('elements')
|
260
|
+
sections[key] = AeroSection(**data[key])
|
261
|
+
|
262
|
+
model.aero = Aero(sections=sections, element_assignments=element_assignments)
|
263
|
+
except:
|
264
|
+
print('Specified aero_sections file found. No aerodynamics definitions applied.')
|
265
|
+
|
266
|
+
# Drag elements model
|
267
|
+
if drag_elements is not None:
|
268
|
+
try:
|
269
|
+
with open(model_folder / drag_elements, 'r') as f:
|
270
|
+
data = json.load(f)
|
271
|
+
|
272
|
+
model.assign_drag_elements(data)
|
273
|
+
except:
|
274
|
+
print('Specified drag_elements file found or invalid. No drag elements defined.')
|
275
|
+
|
276
|
+
model.connect_eldef()
|
277
|
+
model.assign_dry_modes()
|
278
|
+
|
279
|
+
return model
|
280
|
+
|
281
|
+
|
282
|
+
def save_wwi(model, model_path):
|
283
|
+
'''
|
284
|
+
Save WAWI model object to specified model path.
|
285
|
+
'''
|
286
|
+
with open(model_path, 'wb') as f:
|
287
|
+
dill.dump(model, f, -1)
|
288
|
+
|
289
|
+
|
290
|
+
def convert_to_wwi(model_path, output=None):
|
291
|
+
'''
|
292
|
+
Convert folder with WAWI object definition files to wwi (model) file
|
293
|
+
'''
|
294
|
+
model_path = Path(model_path)
|
295
|
+
if output is None:
|
296
|
+
output = model_path / 'model.wwi'
|
297
|
+
|
298
|
+
model = load_model(model_path)
|
299
|
+
|
300
|
+
if model_path.is_dir():
|
301
|
+
with open(output, 'wb') as f:
|
302
|
+
dill.dump(model, f, -1)
|
303
|
+
|
304
|
+
|
305
|
+
def load_model(model_path, save_if_nonexistent=False, sort_nodes_by_x=True):
|
306
|
+
'''
|
307
|
+
Load wwi-file with WAWI model object or folder with relevant definitions.
|
308
|
+
|
309
|
+
Parameters
|
310
|
+
-----------
|
311
|
+
model_path : str
|
312
|
+
path of wwi-file
|
313
|
+
save_if_nonexistent : False, optional
|
314
|
+
whether or not to save model as wwi if not existing
|
315
|
+
sort_nodes_by_x : True, optional
|
316
|
+
whether or not to sort the nodes of eldef by their x-coordinate
|
317
|
+
|
318
|
+
Returns
|
319
|
+
-----------
|
320
|
+
model : `wawi.model.Model`
|
321
|
+
WAWI model
|
322
|
+
'''
|
323
|
+
|
324
|
+
model_path = Path(model_path)
|
325
|
+
|
326
|
+
if model_path.is_dir() and not (model_path/'model.wwi').is_file():
|
327
|
+
model = import_folder(model_path, sort_nodes_by_x=sort_nodes_by_x)
|
328
|
+
if save_if_nonexistent:
|
329
|
+
convert_to_wwi(model_path)
|
330
|
+
else:
|
331
|
+
with open(model_path, 'rb') as f:
|
332
|
+
model = dill.load(f)
|
333
|
+
|
334
|
+
return model
|
335
|
+
|
336
|
+
|
337
|
+
def import_wadam_hydro_transfer(wadam_file):
|
338
|
+
'''
|
339
|
+
Import WADAM output file.
|
340
|
+
|
341
|
+
Parameters
|
342
|
+
-----------
|
343
|
+
wadam_file : str
|
344
|
+
path to file to import
|
345
|
+
|
346
|
+
Returns
|
347
|
+
----------
|
348
|
+
omega : float
|
349
|
+
numpy array describing numerical frequency axis
|
350
|
+
theta : float
|
351
|
+
numpy array describing numerical directional axis
|
352
|
+
fhyd : float
|
353
|
+
complex numpy 3d-array describing the transfer functions relating
|
354
|
+
regular waves with unit height to the 6 relevant forces and moments
|
355
|
+
6-by-len(theta)-by-len(omega)
|
356
|
+
'''
|
357
|
+
|
358
|
+
string = ('.+W A V E P E R I O D.+=\s+(?P<period>.+):.+\n.+'
|
359
|
+
'H E A D I N G A N G L E.+=\s+(?P<theta>.+):(?:.*\n){5,10}'
|
360
|
+
|
361
|
+
'.+EXCITING FORCES AND MOMENTS FROM THE HASKIN RELATIONS(?:.*\n){4,6}\s+'
|
362
|
+
|
363
|
+
'-F1-\s+(?P<F1_real>[-?.E\d+]+)\s+(?P<F1_imag>[-?.E\d+]+)\s.+\n\s+'
|
364
|
+
'-F2-\s+(?P<F2_real>[-?.E\d+]+)\s+(?P<F2_imag>[-?.E\d+]+)\s.+\n\s+'
|
365
|
+
'-F3-\s+(?P<F3_real>[-?.E\d+]+)\s+(?P<F3_imag>[-?.E\d+]+)\s.+\n\s+'
|
366
|
+
'-F4-\s+(?P<F4_real>[-?.E\d+]+)\s+(?P<F4_imag>[-?.E\d+]+)\s.+\n\s+'
|
367
|
+
'-F5-\s+(?P<F5_real>[-?.E\d+]+)\s+(?P<F5_imag>[-?.E\d+]+)\s.+\n\s+'
|
368
|
+
'-F6-\s+(?P<F6_real>[-?.E\d+]+)\s+(?P<F6_imag>[-?.E\d+]+)\s.+\n')
|
369
|
+
regex = re.compile(string)
|
370
|
+
|
371
|
+
nondim_string = ('\s+NON-DIMENSIONALIZING FACTORS:\n(?:.*\n)+'
|
372
|
+
'\s+RO\s+=\s+(?P<rho>[-?.E\d+]+)\n'
|
373
|
+
'\s+G\s+=\s+(?P<g>[-?.E\d+]+)\n'
|
374
|
+
'\s+VOL\s+=\s+(?P<vol>[-?.E\d+]+)\n'
|
375
|
+
'\s+L\s+=\s+(?P<l>[-?.E\d+]+)\n'
|
376
|
+
'\s+WA\s+=\s+(?P<wa>[-?.E\d+]+)')
|
377
|
+
|
378
|
+
regex_nondim = re.compile(nondim_string)
|
379
|
+
|
380
|
+
with open(wadam_file, encoding='utf-8') as file:
|
381
|
+
data = file.read()
|
382
|
+
|
383
|
+
periods = []
|
384
|
+
thetas = []
|
385
|
+
Q = []
|
386
|
+
|
387
|
+
for match in regex.finditer(data):
|
388
|
+
data_dict = dict(zip(match.groupdict().keys(), [float(val) for val in match.groupdict().values()]))
|
389
|
+
F1 = data_dict['F1_real'] + data_dict['F1_imag']*1j
|
390
|
+
F2 = data_dict['F2_real'] + data_dict['F2_imag']*1j
|
391
|
+
F3 = data_dict['F3_real'] + data_dict['F3_imag']*1j
|
392
|
+
F4 = data_dict['F4_real'] + data_dict['F4_imag']*1j
|
393
|
+
F5 = data_dict['F5_real'] + data_dict['F5_imag']*1j
|
394
|
+
F6 = data_dict['F6_real'] + data_dict['F6_imag']*1j
|
395
|
+
Q.append(np.array([F1,F2,F3,F4,F5,F6]))
|
396
|
+
|
397
|
+
thetas.append(data_dict['theta'])
|
398
|
+
periods.append(data_dict['period'])
|
399
|
+
|
400
|
+
theta = np.unique(np.array(thetas))*np.pi/180.0
|
401
|
+
period = np.unique(np.array(periods))
|
402
|
+
omega = np.flip(2*np.pi/period, axis=0)
|
403
|
+
Q = np.vstack(Q).T
|
404
|
+
fhyd = np.zeros([6, len(theta), len(omega)]).astype('complex')
|
405
|
+
for n in range(len(theta)):
|
406
|
+
for k in range(len(omega)):
|
407
|
+
fhyd[:,n,k] = Q[:, k*len(theta)+n]
|
408
|
+
|
409
|
+
# Re-dimensionalize and flip
|
410
|
+
nondim_parameters = regex_nondim.search(data).groupdict()
|
411
|
+
nd = dict(zip(nondim_parameters.keys(), [float(val) for val in nondim_parameters.values()]))
|
412
|
+
|
413
|
+
rho, g, vol, wa, l = nd['rho'], nd['g'], nd['vol'], nd['wa'], nd['l']
|
414
|
+
dim = 0*fhyd
|
415
|
+
dim[0::6,:,:] = rho*vol*g*wa/l
|
416
|
+
dim[1::6,:,:] = rho*vol*g*wa/l
|
417
|
+
dim[2::6,:,:] = rho*vol*g*wa/l
|
418
|
+
|
419
|
+
dim[3::6,:,:] = rho*vol*g*wa
|
420
|
+
dim[4::6,:,:] = rho*vol*g*wa
|
421
|
+
dim[5::6,:,:] = rho*vol*g*wa
|
422
|
+
|
423
|
+
fhyd = np.flip(fhyd, axis=2) * dim
|
424
|
+
|
425
|
+
return omega, theta, fhyd
|
426
|
+
|
427
|
+
|
428
|
+
def import_wadam_mat(wadam_file):
|
429
|
+
'''
|
430
|
+
Import system matrices given by input WADAM results (frequency dependent and constant).
|
431
|
+
|
432
|
+
Parameters
|
433
|
+
-------------
|
434
|
+
wadam_file : str
|
435
|
+
path to WADAM file to open
|
436
|
+
|
437
|
+
Returns
|
438
|
+
-------------
|
439
|
+
mass : float
|
440
|
+
6-by-6 added mass matrix of body
|
441
|
+
damping : float
|
442
|
+
6-by-6 radiation damping matrix of body
|
443
|
+
stiffness : float
|
444
|
+
6-by-6 restoring stiffness matrix of body
|
445
|
+
static_mass : float
|
446
|
+
6-by-6 static (constant) mass matrix of body
|
447
|
+
this represents inertia of pontoon object itself
|
448
|
+
omega : float
|
449
|
+
numpy array describing numerical frequency axis (corresponding to mass and damping
|
450
|
+
output)
|
451
|
+
'''
|
452
|
+
|
453
|
+
static_mass_target = 'MASS INERTIA COEFFICIENT MATRIX'
|
454
|
+
stiffness_target = 'HYDROSTATIC RESTORING COEFFICIENT MATRIX'
|
455
|
+
mass_target = 'ADDED MASS MATRIX '
|
456
|
+
damping_target = 'DAMPING MATRIX '
|
457
|
+
period_target = 'WAVE PERIOD = '
|
458
|
+
non_dim_target = ' THE OUTPUT IS NON-DIMENSIONALIZED USING -'
|
459
|
+
|
460
|
+
f = open(wadam_file)
|
461
|
+
active_search = False
|
462
|
+
current_elements = []
|
463
|
+
current_range = [0]
|
464
|
+
|
465
|
+
stiffness = np.empty([0,6,6])
|
466
|
+
static_mass = np.empty([0,6,6])
|
467
|
+
mass = np.empty([0,6,6])
|
468
|
+
damping = np.empty([0,6,6])
|
469
|
+
period = np.empty([0,1,1])
|
470
|
+
non_dim = np.empty([0])
|
471
|
+
|
472
|
+
temp_data=[]
|
473
|
+
data_storage = {'static_mass': static_mass, 'stiffness': stiffness, 'period': period, 'mass':mass,'damping':damping,'non_dim':non_dim}
|
474
|
+
append_axis = {'static_mass': 0, 'stiffness': 0, 'period': 0, 'mass':0,'damping':0,'non_dim':None}
|
475
|
+
|
476
|
+
for lineno, line in enumerate(f):
|
477
|
+
static_mass_switch = line.find(static_mass_target)!=-1
|
478
|
+
stiffness_switch = line.find(stiffness_target)!=-1
|
479
|
+
period_switch = line.find(period_target)!=-1
|
480
|
+
mass_switch = line.find(mass_target)!=-1
|
481
|
+
damping_switch = line.find(damping_target)!=-1
|
482
|
+
non_dim_switch = line.find(non_dim_target)!=-1
|
483
|
+
|
484
|
+
if static_mass_switch == True:
|
485
|
+
active_search=True
|
486
|
+
current_range = [el+lineno-1 for el in range(5,11)]
|
487
|
+
current_elements = range(1,6+1)
|
488
|
+
current_data_string='static_mass'
|
489
|
+
current_data = data_storage[current_data_string]
|
490
|
+
elif stiffness_switch:
|
491
|
+
active_search=True
|
492
|
+
current_range = [el+lineno-1 for el in range(5,11)]
|
493
|
+
current_elements = range(1,6+1)
|
494
|
+
current_data_string='stiffness'
|
495
|
+
current_data = data_storage[current_data_string]
|
496
|
+
elif period_switch:
|
497
|
+
active_search=True
|
498
|
+
current_range = [lineno]
|
499
|
+
current_elements = [3]
|
500
|
+
current_data_string='period'
|
501
|
+
current_data = data_storage[current_data_string]
|
502
|
+
elif mass_switch:
|
503
|
+
active_search=True
|
504
|
+
current_range = [el+lineno-1 for el in range(5,11)]
|
505
|
+
current_elements = range(1,6+1)
|
506
|
+
current_data_string='mass'
|
507
|
+
current_data = data_storage[current_data_string]
|
508
|
+
elif damping_switch:
|
509
|
+
active_search=True
|
510
|
+
current_range = [el+lineno-1 for el in range(5,11)]
|
511
|
+
current_elements = range(1,6+1)
|
512
|
+
current_data_string='damping'
|
513
|
+
current_data = data_storage[current_data_string]
|
514
|
+
elif non_dim_switch:
|
515
|
+
active_search=True
|
516
|
+
current_range = [el+lineno-1 for el in range(9,14)]
|
517
|
+
current_elements = [2]
|
518
|
+
current_data_string='non_dim'
|
519
|
+
current_data = data_storage[current_data_string]
|
520
|
+
|
521
|
+
if (active_search==True) and (lineno in current_range):
|
522
|
+
data_line = [float(i) for i in [line.split()[el] for el in current_elements]]
|
523
|
+
temp_data.append(data_line)
|
524
|
+
elif (active_search==True) and lineno>=max(current_range):
|
525
|
+
current_data=np.append(current_data,[temp_data],axis=append_axis[current_data_string])
|
526
|
+
temp_data=[]
|
527
|
+
current_range = [0]
|
528
|
+
current_elements = []
|
529
|
+
active_search=False
|
530
|
+
data_storage[current_data_string] = current_data
|
531
|
+
|
532
|
+
# Non-dimensionalizing factors
|
533
|
+
non_dim = data_storage['non_dim']
|
534
|
+
ro = non_dim[0]
|
535
|
+
g = non_dim[1]
|
536
|
+
vol = non_dim[2]
|
537
|
+
l = non_dim[3]
|
538
|
+
|
539
|
+
non_dim_defs = {'static_mass': np.array([[ro*vol, ro*vol*l],
|
540
|
+
[ro*vol*l, ro*vol*l*l]]),
|
541
|
+
'mass': np.array([[ro*vol, ro*vol*l],
|
542
|
+
[ro*vol*l, ro*vol*l*l]]),
|
543
|
+
'damping': np.array([[ro*vol*np.sqrt(g/l), ro*vol*np.sqrt(g*l)],
|
544
|
+
[ro*vol*np.sqrt(g*l), ro*vol*l*np.sqrt(g*l)]]),
|
545
|
+
'stiffness':np.array([[ro*vol*g/l, ro*vol*g],
|
546
|
+
[ro*vol*g, ro*vol*g*l]])}
|
547
|
+
|
548
|
+
# Re-dimensionalize data
|
549
|
+
for current_data_string in non_dim_defs.keys():
|
550
|
+
dim_matrix = np.repeat(np.repeat(non_dim_defs[current_data_string],3,axis=0),3,axis=1)
|
551
|
+
data_storage[current_data_string] = np.multiply(data_storage[current_data_string], dim_matrix)
|
552
|
+
|
553
|
+
static_mass = data_storage['static_mass'][0]
|
554
|
+
stiffness = data_storage['stiffness'][0]
|
555
|
+
mass = np.moveaxis(data_storage['mass'],0,2)
|
556
|
+
damping = np.moveaxis(data_storage['damping'],0,2)
|
557
|
+
omega = 2.0*np.pi/data_storage['period'].flatten()
|
558
|
+
sortix = np.argsort(omega)
|
559
|
+
omega = omega[sortix]
|
560
|
+
|
561
|
+
mass = mass[:,:, sortix]
|
562
|
+
damping = damping[:,:, sortix]
|
563
|
+
stiffness = (stiffness + stiffness.T)/2
|
564
|
+
|
565
|
+
return mass, damping, stiffness, static_mass, omega
|
566
|
+
|
567
|
+
|
568
|
+
|
569
|
+
def import_wamit_mat(wamit_path, suffix=['hst', 'mmx', '1'], rho=1020, L=1.0, g=9.80665):
|
570
|
+
'''
|
571
|
+
Import system matrices from WAMIT results (frequency dependent and constant).
|
572
|
+
|
573
|
+
Parameters
|
574
|
+
-------------
|
575
|
+
wamit_path : str
|
576
|
+
path to WAMIT file to open
|
577
|
+
suffix : ['hst', 'mmx', '1']
|
578
|
+
all suffixes used - all default values required for full output
|
579
|
+
rho : 1020, optional
|
580
|
+
water mass density used to redimensionalize results
|
581
|
+
L : 1.0, optional
|
582
|
+
redimensionaling length - 1.0 is fine for most use cases
|
583
|
+
g : 9.80665, optional
|
584
|
+
gravitational constant for redimensionalization of data
|
585
|
+
|
586
|
+
Returns
|
587
|
+
-------------
|
588
|
+
A : float
|
589
|
+
6-by-6 added mass matrix of body
|
590
|
+
B : float
|
591
|
+
6-by-6 radiation damping matrix of body
|
592
|
+
K0 : float
|
593
|
+
6-by-6 restoring stiffness matrix of body
|
594
|
+
M0 : float
|
595
|
+
6-by-6 static (constant) mass matrix of body
|
596
|
+
this represents inertia of pontoon object itself
|
597
|
+
omega : float
|
598
|
+
numpy array describing numerical frequency axis (corresponding to mass and damping
|
599
|
+
output)
|
600
|
+
'''
|
601
|
+
if suffix.count('hst')>=1:
|
602
|
+
# Hydrostatic stiffness
|
603
|
+
k_list = np.loadtxt(wamit_path+'.hst')
|
604
|
+
K0 = np.reshape(k_list[:,2], [6,6]) #assumes the order of elements in k_list
|
605
|
+
dimK = 0*K0
|
606
|
+
dimK[2, 2] = rho*g*L**2
|
607
|
+
dimK[2, 3] = dimK[3, 2] = rho*g*L**3
|
608
|
+
dimK[2, 4] = dimK[4, 2] = rho*g*L**3
|
609
|
+
dimK[3::, :] = rho*g*L**4
|
610
|
+
dimK[:, 3::] = rho*g*L**4
|
611
|
+
|
612
|
+
K0 = K0*dimK
|
613
|
+
|
614
|
+
else:
|
615
|
+
K0 = 0
|
616
|
+
|
617
|
+
if suffix.count('mmx')>=1:
|
618
|
+
# Inertia matrix
|
619
|
+
m_list = np.loadtxt(wamit_path+'.mmx', skiprows=12)
|
620
|
+
M0 = np.reshape(m_list[:,2], [6,6])
|
621
|
+
else:
|
622
|
+
M0 = 0
|
623
|
+
|
624
|
+
if suffix.count('1')>=1:
|
625
|
+
# Added mass and added damping
|
626
|
+
mc_list = np.loadtxt(wamit_path+'.1')
|
627
|
+
period = mc_list[0::10,0]
|
628
|
+
Aper = np.zeros([6,6,len(period)])
|
629
|
+
Bper = np.zeros([6,6,len(period)])
|
630
|
+
|
631
|
+
for k in range(0,int(mc_list.shape[0]/10)):
|
632
|
+
mc_this = mc_list[k*10:k*10+10,:]
|
633
|
+
dof1, dof2 = mc_this[:,1].astype('int')-1, mc_this[:, 2].astype('int')-1
|
634
|
+
|
635
|
+
for ix, i in enumerate(dof1):
|
636
|
+
j = dof2[ix]
|
637
|
+
Aper[i, j, k] = mc_this[ix, 3]
|
638
|
+
Bper[i, j, k] = mc_this[ix, 4]
|
639
|
+
|
640
|
+
# Re-dimensionalize and flip
|
641
|
+
omega = np.flip(2*np.pi/period, axis=0)
|
642
|
+
|
643
|
+
dimA = 0*Aper
|
644
|
+
dimA[0:3, 0:3, :] = rho*L**3
|
645
|
+
dimA[0:3, 3:-1, :] = rho*L**4
|
646
|
+
dimA[3:-1, 0:3, :] = rho*L**4
|
647
|
+
dimA[3:-1, 3:-1, :] = rho*L**5
|
648
|
+
|
649
|
+
dimB = 0*Bper
|
650
|
+
dimB[0:3, 0:3, :] = rho*L**3*omega
|
651
|
+
dimB[0:3, 3:-1, :] = rho*L**4*omega
|
652
|
+
dimB[3:-1, 0:3, :] = rho*L**4*omega
|
653
|
+
dimB[3:-1, 3:-1, :] = rho*L**5*omega
|
654
|
+
|
655
|
+
A = np.flip(Aper, axis=2)*dimA
|
656
|
+
B = np.flip(Bper, axis=2)*dimB
|
657
|
+
else:
|
658
|
+
A = 0
|
659
|
+
B = 0
|
660
|
+
|
661
|
+
return A, B, K0, M0, omega
|
662
|
+
|
663
|
+
|
664
|
+
def import_wamit_force(wamit_path, A=1.0, rho=1020, L=1.0, g=9.80665):
|
665
|
+
'''
|
666
|
+
Import hydrodynamic transfer function from WAMIT '.2'-file.
|
667
|
+
|
668
|
+
Parameters
|
669
|
+
-----------
|
670
|
+
wamit_path :
|
671
|
+
A : 1.0, optional
|
672
|
+
area used to redimensionalize results (1.0 is fine for most use cases)
|
673
|
+
rho : 1020, optional
|
674
|
+
water mass density used to redimensionalize results
|
675
|
+
L : 1.0, optional
|
676
|
+
redimensionaling length (1.0 is fine for most use cases)
|
677
|
+
g : 9.80665, optional
|
678
|
+
gravitational constant for redimensionalization of data
|
679
|
+
|
680
|
+
Returns
|
681
|
+
----------
|
682
|
+
omega : float
|
683
|
+
numpy array describing numerical frequency axis
|
684
|
+
theta : float
|
685
|
+
numpy array describing numerical directional axis
|
686
|
+
fhyd : float
|
687
|
+
complex numpy 3d-array describing the transfer functions relating
|
688
|
+
regular waves with unit height to the 6 relevant forces and moments
|
689
|
+
6-by-len(theta)-by-len(omega)
|
690
|
+
'''
|
691
|
+
|
692
|
+
# Transfer function
|
693
|
+
q_list = np.loadtxt(wamit_path+'.2')
|
694
|
+
period = np.unique(q_list[:,0])
|
695
|
+
n_omega = len(period)
|
696
|
+
n_theta = int(len(q_list[:,0])/len(period)/6)
|
697
|
+
theta = q_list[0:6*n_theta:6,1]
|
698
|
+
f_hyd_per = np.zeros([6, n_theta, n_omega]).astype('complex')
|
699
|
+
|
700
|
+
for k in range(0, n_omega):
|
701
|
+
q_this = q_list[k*n_theta*6:k*n_theta*6+n_theta*6]
|
702
|
+
for dof in range(0,6):
|
703
|
+
f_hyd_per[dof, :, k] = q_this[dof::6, 5] + q_this[dof::6, 6]*1j
|
704
|
+
|
705
|
+
omega = np.flip(2*np.pi/period, axis=0)
|
706
|
+
|
707
|
+
# Re-dimensionalize and flip
|
708
|
+
dim = 0*f_hyd_per
|
709
|
+
dim[0::6,:,:] = rho*g*A*L**2
|
710
|
+
dim[1::6,:,:] = rho*g*A*L**2
|
711
|
+
dim[2::6,:,:] = rho*g*A*L**2
|
712
|
+
dim[3::6,:,:] = rho*g*A*L**3
|
713
|
+
dim[4::6,:,:] = rho*g*A*L**3
|
714
|
+
dim[5::6,:,:] = rho*g*A*L**3
|
715
|
+
|
716
|
+
f_hyd = np.flip(f_hyd_per, axis=2) * dim
|
717
|
+
|
718
|
+
return omega, theta, f_hyd
|
719
|
+
|