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/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
+