wawi 0.0.1__py3-none-any.whl → 0.0.3__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,696 @@
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
+ 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 types file not found. No pontoon_types 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['xi0'])
156
+ else:
157
+ xi0 = 0.0
158
+
159
+ if 'm_min' in modal:
160
+ m_min = modal['m_min']
161
+ else:
162
+ m_min = 0.0
163
+
164
+ if 'phi_x' in modal:
165
+ phi_x = modal['phi_x']
166
+ else:
167
+ phi_x = None
168
+
169
+ modal_dry = ModalDry(modal['phi'], m=np.array(modal['m']), k=np.array(modal['k']),
170
+ local_phi=modal['local'], xi0=xi0, phi_x=phi_x, m_min=m_min)
171
+
172
+ # Element definition
173
+ if element_data != {}:
174
+ if 'sections' in element_data:
175
+ sd = element_data['sections']
176
+ section_dict = {key: Section(E=sd[key]['E'], poisson=sd[key]['poisson'],
177
+ A=sd[key]['A'], I_y=sd[key]['Iy'], I_z=sd[key]['Iz'],
178
+ J=sd[key]['J'], m=sd[key]['m'], name=key) for key in sd}
179
+
180
+ sections = [section_dict[key] for key in element_data['section_assignment']]
181
+ else:
182
+ sections = None
183
+
184
+ node_matrix = np.array(element_data['node_matrix'])
185
+ node_matrix[:,0] = node_matrix[:,0].astype(int)
186
+ element_matrix = np.array(element_data['element_matrix'])
187
+
188
+ eldef = Part(node_matrix, element_matrix, sections=sections,
189
+ assemble=False, forced_ndofs=6)
190
+
191
+ if sort_nodes_by_x:
192
+ ix = np.argsort([n.coordinates[0] for n in eldef.nodes])
193
+ node_labels_sorted = eldef.get_node_labels()[ix]
194
+ eldef.arrange_nodes(node_labels_sorted, arrange_dof_ixs=True)
195
+
196
+ if 'full' in modal_dry.phi_full: #adjust phi_full
197
+ modal_dry.phi_full['full'] = modal_dry.phi_full['full'][n2d_ix(ix, n_dofs=6), :]
198
+
199
+ # Assign orientation
200
+ for key in orientations:
201
+ if 'e2' in orientations[key]:
202
+ e2 = orientations[key]['e2']
203
+ e3 = None
204
+ elif 'e3' in orientations[key]:
205
+ e3 = orientations[key]['e3']
206
+ e2 = None
207
+ else:
208
+ raise ValueError('Orientations should contain either e2 or e3')
209
+
210
+ elements = orientations[key]['elements']
211
+ for el_label in elements:
212
+ el = eldef.get_element(int(el_label))
213
+
214
+
215
+ el.assign_e2(e2)
216
+ el.assign_e3(e3)
217
+
218
+ el.initiate_geometry()
219
+ else:
220
+ eldef = None
221
+
222
+ # Create model object
223
+ model = Model.from_nodes_and_types(pontoon_nodes, [ptypes[pt] for pt in pontoon_types], modal_dry=modal_dry,
224
+ rotation=pontoon_rotation, eldef=eldef, labels=pontoon_names)
225
+
226
+ # Aero mode
227
+ if aero_sections is not None:
228
+ # Load aero sections
229
+ try:
230
+ with open(model_folder / aero_sections, 'r') as f:
231
+ data = json.load(f)
232
+
233
+ sections = dict()
234
+ element_assignments = dict()
235
+ for key in data:
236
+ element_assignments[key] = data[key].pop('elements')
237
+ sections[key] = AeroSection(**data[key])
238
+
239
+ model.aero = Aero(sections=sections, element_assignments=element_assignments)
240
+ except:
241
+ print('Specified aero_sections file found. No aerodynamics definitions applied.')
242
+
243
+ # Drag elements model
244
+ if drag_elements is not None:
245
+ try:
246
+ with open(model_folder / drag_elements, 'r') as f:
247
+ data = json.load(f)
248
+
249
+ model.assign_drag_elements(data)
250
+ except:
251
+ print('Specified drag_elements file found or invalid. No drag elements defined.')
252
+
253
+ model.connect_eldef()
254
+ model.assign_dry_modes()
255
+
256
+ return model
257
+
258
+
259
+ def save_wwi(model, model_path):
260
+ '''
261
+ Save WAWI model object to specified model path.
262
+ '''
263
+ with open(model_path, 'wb') as f:
264
+ dill.dump(model, f, -1)
265
+
266
+
267
+ def convert_to_wwi(model_path, output=None):
268
+ '''
269
+ Convert folder with WAWI object definition files to wwi (model) file
270
+ '''
271
+ model_path = Path(model_path)
272
+ if output is None:
273
+ output = model_path / 'model.wwi'
274
+
275
+ model = load_model(model_path)
276
+
277
+ if model_path.is_dir():
278
+ with open(output, 'wb') as f:
279
+ dill.dump(model, f, -1)
280
+
281
+
282
+ def load_model(model_path, save_if_nonexistent=False, sort_nodes_by_x=True):
283
+ '''
284
+ Load wwi-file with WAWI model object or folder with relevant definitions.
285
+
286
+ Parameters
287
+ -----------
288
+ model_path : str
289
+ path of wwi-file
290
+ save_if_nonexistent : False, optional
291
+ whether or not to save model as wwi if not existing
292
+ sort_nodes_by_x : True, optional
293
+ whether or not to sort the nodes of eldef by their x-coordinate
294
+
295
+ Returns
296
+ -----------
297
+ model : `wawi.model.Model`
298
+ WAWI model
299
+ '''
300
+
301
+ model_path = Path(model_path)
302
+
303
+ if model_path.is_dir() and not (model_path/'model.wwi').is_file():
304
+ model = import_folder(model_path, sort_nodes_by_x=sort_nodes_by_x)
305
+ if save_if_nonexistent:
306
+ convert_to_wwi(model_path)
307
+ else:
308
+ with open(model_path, 'rb') as f:
309
+ model = dill.load(f)
310
+
311
+ return model
312
+
313
+
314
+ def import_wadam_hydro_transfer(wadam_file):
315
+ '''
316
+ Import WADAM output file.
317
+
318
+ Parameters
319
+ -----------
320
+ wadam_file : str
321
+ path to file to import
322
+
323
+ Returns
324
+ ----------
325
+ omega : float
326
+ numpy array describing numerical frequency axis
327
+ theta : float
328
+ numpy array describing numerical directional axis
329
+ fhyd : float
330
+ complex numpy 3d-array describing the transfer functions relating
331
+ regular waves with unit height to the 6 relevant forces and moments
332
+ 6-by-len(theta)-by-len(omega)
333
+ '''
334
+
335
+ string = ('.+W A V E P E R I O D.+=\s+(?P<period>.+):.+\n.+'
336
+ 'H E A D I N G A N G L E.+=\s+(?P<theta>.+):(?:.*\n){5,10}'
337
+
338
+ '.+EXCITING FORCES AND MOMENTS FROM THE HASKIN RELATIONS(?:.*\n){4,6}\s+'
339
+
340
+ '-F1-\s+(?P<F1_real>[-?.E\d+]+)\s+(?P<F1_imag>[-?.E\d+]+)\s.+\n\s+'
341
+ '-F2-\s+(?P<F2_real>[-?.E\d+]+)\s+(?P<F2_imag>[-?.E\d+]+)\s.+\n\s+'
342
+ '-F3-\s+(?P<F3_real>[-?.E\d+]+)\s+(?P<F3_imag>[-?.E\d+]+)\s.+\n\s+'
343
+ '-F4-\s+(?P<F4_real>[-?.E\d+]+)\s+(?P<F4_imag>[-?.E\d+]+)\s.+\n\s+'
344
+ '-F5-\s+(?P<F5_real>[-?.E\d+]+)\s+(?P<F5_imag>[-?.E\d+]+)\s.+\n\s+'
345
+ '-F6-\s+(?P<F6_real>[-?.E\d+]+)\s+(?P<F6_imag>[-?.E\d+]+)\s.+\n')
346
+ regex = re.compile(string)
347
+
348
+ nondim_string = ('\s+NON-DIMENSIONALIZING FACTORS:\n(?:.*\n)+'
349
+ '\s+RO\s+=\s+(?P<rho>[-?.E\d+]+)\n'
350
+ '\s+G\s+=\s+(?P<g>[-?.E\d+]+)\n'
351
+ '\s+VOL\s+=\s+(?P<vol>[-?.E\d+]+)\n'
352
+ '\s+L\s+=\s+(?P<l>[-?.E\d+]+)\n'
353
+ '\s+WA\s+=\s+(?P<wa>[-?.E\d+]+)')
354
+
355
+ regex_nondim = re.compile(nondim_string)
356
+
357
+ with open(wadam_file, encoding='utf-8') as file:
358
+ data = file.read()
359
+
360
+ periods = []
361
+ thetas = []
362
+ Q = []
363
+
364
+ for match in regex.finditer(data):
365
+ data_dict = dict(zip(match.groupdict().keys(), [float(val) for val in match.groupdict().values()]))
366
+ F1 = data_dict['F1_real'] + data_dict['F1_imag']*1j
367
+ F2 = data_dict['F2_real'] + data_dict['F2_imag']*1j
368
+ F3 = data_dict['F3_real'] + data_dict['F3_imag']*1j
369
+ F4 = data_dict['F4_real'] + data_dict['F4_imag']*1j
370
+ F5 = data_dict['F5_real'] + data_dict['F5_imag']*1j
371
+ F6 = data_dict['F6_real'] + data_dict['F6_imag']*1j
372
+ Q.append(np.array([F1,F2,F3,F4,F5,F6]))
373
+
374
+ thetas.append(data_dict['theta'])
375
+ periods.append(data_dict['period'])
376
+
377
+ theta = np.unique(np.array(thetas))*np.pi/180.0
378
+ period = np.unique(np.array(periods))
379
+ omega = np.flip(2*np.pi/period, axis=0)
380
+ Q = np.vstack(Q).T
381
+ fhyd = np.zeros([6, len(theta), len(omega)]).astype('complex')
382
+ for n in range(len(theta)):
383
+ for k in range(len(omega)):
384
+ fhyd[:,n,k] = Q[:, k*len(theta)+n]
385
+
386
+ # Re-dimensionalize and flip
387
+ nondim_parameters = regex_nondim.search(data).groupdict()
388
+ nd = dict(zip(nondim_parameters.keys(), [float(val) for val in nondim_parameters.values()]))
389
+
390
+ rho, g, vol, wa, l = nd['rho'], nd['g'], nd['vol'], nd['wa'], nd['l']
391
+ dim = 0*fhyd
392
+ dim[0::6,:,:] = rho*vol*g*wa/l
393
+ dim[1::6,:,:] = rho*vol*g*wa/l
394
+ dim[2::6,:,:] = rho*vol*g*wa/l
395
+
396
+ dim[3::6,:,:] = rho*vol*g*wa
397
+ dim[4::6,:,:] = rho*vol*g*wa
398
+ dim[5::6,:,:] = rho*vol*g*wa
399
+
400
+ fhyd = np.flip(fhyd, axis=2) * dim
401
+
402
+ return omega, theta, fhyd
403
+
404
+
405
+ def import_wadam_mat(wadam_file):
406
+ '''
407
+ Import system matrices given by input WADAM results (frequency dependent and constant).
408
+
409
+ Parameters
410
+ -------------
411
+ wadam_file : str
412
+ path to WADAM file to open
413
+
414
+ Returns
415
+ -------------
416
+ mass : float
417
+ 6-by-6 added mass matrix of body
418
+ damping : float
419
+ 6-by-6 radiation damping matrix of body
420
+ stiffness : float
421
+ 6-by-6 restoring stiffness matrix of body
422
+ static_mass : float
423
+ 6-by-6 static (constant) mass matrix of body
424
+ this represents inertia of pontoon object itself
425
+ omega : float
426
+ numpy array describing numerical frequency axis (corresponding to mass and damping
427
+ output)
428
+ '''
429
+
430
+ static_mass_target = 'MASS INERTIA COEFFICIENT MATRIX'
431
+ stiffness_target = 'HYDROSTATIC RESTORING COEFFICIENT MATRIX'
432
+ mass_target = 'ADDED MASS MATRIX '
433
+ damping_target = 'DAMPING MATRIX '
434
+ period_target = 'WAVE PERIOD = '
435
+ non_dim_target = ' THE OUTPUT IS NON-DIMENSIONALIZED USING -'
436
+
437
+ f = open(wadam_file)
438
+ active_search = False
439
+ current_elements = []
440
+ current_range = [0]
441
+
442
+ stiffness = np.empty([0,6,6])
443
+ static_mass = np.empty([0,6,6])
444
+ mass = np.empty([0,6,6])
445
+ damping = np.empty([0,6,6])
446
+ period = np.empty([0,1,1])
447
+ non_dim = np.empty([0])
448
+
449
+ temp_data=[]
450
+ data_storage = {'static_mass': static_mass, 'stiffness': stiffness, 'period': period, 'mass':mass,'damping':damping,'non_dim':non_dim}
451
+ append_axis = {'static_mass': 0, 'stiffness': 0, 'period': 0, 'mass':0,'damping':0,'non_dim':None}
452
+
453
+ for lineno, line in enumerate(f):
454
+ static_mass_switch = line.find(static_mass_target)!=-1
455
+ stiffness_switch = line.find(stiffness_target)!=-1
456
+ period_switch = line.find(period_target)!=-1
457
+ mass_switch = line.find(mass_target)!=-1
458
+ damping_switch = line.find(damping_target)!=-1
459
+ non_dim_switch = line.find(non_dim_target)!=-1
460
+
461
+ if static_mass_switch == True:
462
+ active_search=True
463
+ current_range = [el+lineno-1 for el in range(5,11)]
464
+ current_elements = range(1,6+1)
465
+ current_data_string='static_mass'
466
+ current_data = data_storage[current_data_string]
467
+ elif stiffness_switch:
468
+ active_search=True
469
+ current_range = [el+lineno-1 for el in range(5,11)]
470
+ current_elements = range(1,6+1)
471
+ current_data_string='stiffness'
472
+ current_data = data_storage[current_data_string]
473
+ elif period_switch:
474
+ active_search=True
475
+ current_range = [lineno]
476
+ current_elements = [3]
477
+ current_data_string='period'
478
+ current_data = data_storage[current_data_string]
479
+ elif mass_switch:
480
+ active_search=True
481
+ current_range = [el+lineno-1 for el in range(5,11)]
482
+ current_elements = range(1,6+1)
483
+ current_data_string='mass'
484
+ current_data = data_storage[current_data_string]
485
+ elif damping_switch:
486
+ active_search=True
487
+ current_range = [el+lineno-1 for el in range(5,11)]
488
+ current_elements = range(1,6+1)
489
+ current_data_string='damping'
490
+ current_data = data_storage[current_data_string]
491
+ elif non_dim_switch:
492
+ active_search=True
493
+ current_range = [el+lineno-1 for el in range(9,14)]
494
+ current_elements = [2]
495
+ current_data_string='non_dim'
496
+ current_data = data_storage[current_data_string]
497
+
498
+ if (active_search==True) and (lineno in current_range):
499
+ data_line = [float(i) for i in [line.split()[el] for el in current_elements]]
500
+ temp_data.append(data_line)
501
+ elif (active_search==True) and lineno>=max(current_range):
502
+ current_data=np.append(current_data,[temp_data],axis=append_axis[current_data_string])
503
+ temp_data=[]
504
+ current_range = [0]
505
+ current_elements = []
506
+ active_search=False
507
+ data_storage[current_data_string] = current_data
508
+
509
+ # Non-dimensionalizing factors
510
+ non_dim = data_storage['non_dim']
511
+ ro = non_dim[0]
512
+ g = non_dim[1]
513
+ vol = non_dim[2]
514
+ l = non_dim[3]
515
+
516
+ non_dim_defs = {'static_mass': np.array([[ro*vol, ro*vol*l],
517
+ [ro*vol*l, ro*vol*l*l]]),
518
+ 'mass': np.array([[ro*vol, ro*vol*l],
519
+ [ro*vol*l, ro*vol*l*l]]),
520
+ 'damping': np.array([[ro*vol*np.sqrt(g/l), ro*vol*np.sqrt(g*l)],
521
+ [ro*vol*np.sqrt(g*l), ro*vol*l*np.sqrt(g*l)]]),
522
+ 'stiffness':np.array([[ro*vol*g/l, ro*vol*g],
523
+ [ro*vol*g, ro*vol*g*l]])}
524
+
525
+ # Re-dimensionalize data
526
+ for current_data_string in non_dim_defs.keys():
527
+ dim_matrix = np.repeat(np.repeat(non_dim_defs[current_data_string],3,axis=0),3,axis=1)
528
+ data_storage[current_data_string] = np.multiply(data_storage[current_data_string], dim_matrix)
529
+
530
+ static_mass = data_storage['static_mass'][0]
531
+ stiffness = data_storage['stiffness'][0]
532
+ mass = np.moveaxis(data_storage['mass'],0,2)
533
+ damping = np.moveaxis(data_storage['damping'],0,2)
534
+ omega = 2.0*np.pi/data_storage['period'].flatten()
535
+ sortix = np.argsort(omega)
536
+ omega = omega[sortix]
537
+
538
+ mass = mass[:,:, sortix]
539
+ damping = damping[:,:, sortix]
540
+ stiffness = (stiffness + stiffness.T)/2
541
+
542
+ return mass, damping, stiffness, static_mass, omega
543
+
544
+
545
+
546
+ def import_wamit_mat(wamit_path, suffix=['hst', 'mmx', '1'], rho=1020, L=1.0, g=9.80665):
547
+ '''
548
+ Import system matrices from WAMIT results (frequency dependent and constant).
549
+
550
+ Parameters
551
+ -------------
552
+ wamit_path : str
553
+ path to WAMIT file to open
554
+ suffix : ['hst', 'mmx', '1']
555
+ all suffixes used - all default values required for full output
556
+ rho : 1020, optional
557
+ water mass density used to redimensionalize results
558
+ L : 1.0, optional
559
+ redimensionaling length - 1.0 is fine for most use cases
560
+ g : 9.80665, optional
561
+ gravitational constant for redimensionalization of data
562
+
563
+ Returns
564
+ -------------
565
+ A : float
566
+ 6-by-6 added mass matrix of body
567
+ B : float
568
+ 6-by-6 radiation damping matrix of body
569
+ K0 : float
570
+ 6-by-6 restoring stiffness matrix of body
571
+ M0 : float
572
+ 6-by-6 static (constant) mass matrix of body
573
+ this represents inertia of pontoon object itself
574
+ omega : float
575
+ numpy array describing numerical frequency axis (corresponding to mass and damping
576
+ output)
577
+ '''
578
+ if suffix.count('hst')>=1:
579
+ # Hydrostatic stiffness
580
+ k_list = np.loadtxt(wamit_path+'.hst')
581
+ K0 = np.reshape(k_list[:,2], [6,6]) #assumes the order of elements in k_list
582
+ dimK = 0*K0
583
+ dimK[2, 2] = rho*g*L**2
584
+ dimK[2, 3] = dimK[3, 2] = rho*g*L**3
585
+ dimK[2, 4] = dimK[4, 2] = rho*g*L**3
586
+ dimK[3::, :] = rho*g*L**4
587
+ dimK[:, 3::] = rho*g*L**4
588
+
589
+ K0 = K0*dimK
590
+
591
+ else:
592
+ K0 = 0
593
+
594
+ if suffix.count('mmx')>=1:
595
+ # Inertia matrix
596
+ m_list = np.loadtxt(wamit_path+'.mmx', skiprows=12)
597
+ M0 = np.reshape(m_list[:,2], [6,6])
598
+ else:
599
+ M0 = 0
600
+
601
+ if suffix.count('1')>=1:
602
+ # Added mass and added damping
603
+ mc_list = np.loadtxt(wamit_path+'.1')
604
+ period = mc_list[0::10,0]
605
+ Aper = np.zeros([6,6,len(period)])
606
+ Bper = np.zeros([6,6,len(period)])
607
+
608
+ for k in range(0,int(mc_list.shape[0]/10)):
609
+ mc_this = mc_list[k*10:k*10+10,:]
610
+ dof1, dof2 = mc_this[:,1].astype('int')-1, mc_this[:, 2].astype('int')-1
611
+
612
+ for ix, i in enumerate(dof1):
613
+ j = dof2[ix]
614
+ Aper[i, j, k] = mc_this[ix, 3]
615
+ Bper[i, j, k] = mc_this[ix, 4]
616
+
617
+ # Re-dimensionalize and flip
618
+ omega = np.flip(2*np.pi/period, axis=0)
619
+
620
+ dimA = 0*Aper
621
+ dimA[0:3, 0:3, :] = rho*L**3
622
+ dimA[0:3, 3:-1, :] = rho*L**4
623
+ dimA[3:-1, 0:3, :] = rho*L**4
624
+ dimA[3:-1, 3:-1, :] = rho*L**5
625
+
626
+ dimB = 0*Bper
627
+ dimB[0:3, 0:3, :] = rho*L**3*omega
628
+ dimB[0:3, 3:-1, :] = rho*L**4*omega
629
+ dimB[3:-1, 0:3, :] = rho*L**4*omega
630
+ dimB[3:-1, 3:-1, :] = rho*L**5*omega
631
+
632
+ A = np.flip(Aper, axis=2)*dimA
633
+ B = np.flip(Bper, axis=2)*dimB
634
+ else:
635
+ A = 0
636
+ B = 0
637
+
638
+ return A, B, K0, M0, omega
639
+
640
+
641
+ def import_wamit_force(wamit_path, A=1.0, rho=1020, L=1.0, g=9.80665):
642
+ '''
643
+ Import hydrodynamic transfer function from WAMIT '.2'-file.
644
+
645
+ Parameters
646
+ -----------
647
+ wamit_path :
648
+ A : 1.0, optional
649
+ area used to redimensionalize results (1.0 is fine for most use cases)
650
+ rho : 1020, optional
651
+ water mass density used to redimensionalize results
652
+ L : 1.0, optional
653
+ redimensionaling length (1.0 is fine for most use cases)
654
+ g : 9.80665, optional
655
+ gravitational constant for redimensionalization of data
656
+
657
+ Returns
658
+ ----------
659
+ omega : float
660
+ numpy array describing numerical frequency axis
661
+ theta : float
662
+ numpy array describing numerical directional axis
663
+ fhyd : float
664
+ complex numpy 3d-array describing the transfer functions relating
665
+ regular waves with unit height to the 6 relevant forces and moments
666
+ 6-by-len(theta)-by-len(omega)
667
+ '''
668
+
669
+ # Transfer function
670
+ q_list = np.loadtxt(wamit_path+'.2')
671
+ period = np.unique(q_list[:,0])
672
+ n_omega = len(period)
673
+ n_theta = int(len(q_list[:,0])/len(period)/6)
674
+ theta = q_list[0:6*n_theta:6,1]
675
+ f_hyd_per = np.zeros([6, n_theta, n_omega]).astype('complex')
676
+
677
+ for k in range(0, n_omega):
678
+ q_this = q_list[k*n_theta*6:k*n_theta*6+n_theta*6]
679
+ for dof in range(0,6):
680
+ f_hyd_per[dof, :, k] = q_this[dof::6, 5] + q_this[dof::6, 6]*1j
681
+
682
+ omega = np.flip(2*np.pi/period, axis=0)
683
+
684
+ # Re-dimensionalize and flip
685
+ dim = 0*f_hyd_per
686
+ dim[0::6,:,:] = rho*g*A*L**2
687
+ dim[1::6,:,:] = rho*g*A*L**2
688
+ dim[2::6,:,:] = rho*g*A*L**2
689
+ dim[3::6,:,:] = rho*g*A*L**3
690
+ dim[4::6,:,:] = rho*g*A*L**3
691
+ dim[5::6,:,:] = rho*g*A*L**3
692
+
693
+ f_hyd = np.flip(f_hyd_per, axis=2) * dim
694
+
695
+ return omega, theta, f_hyd
696
+