capytaine 3.0.0a1__cp38-cp38-macosx_15_0_x86_64.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.
- capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
- capytaine/.dylibs/libgfortran.5.dylib +0 -0
- capytaine/.dylibs/libquadmath.0.dylib +0 -0
- capytaine/__about__.py +21 -0
- capytaine/__init__.py +32 -0
- capytaine/bem/__init__.py +0 -0
- capytaine/bem/airy_waves.py +111 -0
- capytaine/bem/engines.py +321 -0
- capytaine/bem/problems_and_results.py +601 -0
- capytaine/bem/solver.py +718 -0
- capytaine/bodies/__init__.py +4 -0
- capytaine/bodies/bodies.py +630 -0
- capytaine/bodies/dofs.py +146 -0
- capytaine/bodies/hydrostatics.py +540 -0
- capytaine/bodies/multibodies.py +216 -0
- capytaine/green_functions/Delhommeau_float32.cpython-38-darwin.so +0 -0
- capytaine/green_functions/Delhommeau_float64.cpython-38-darwin.so +0 -0
- capytaine/green_functions/__init__.py +2 -0
- capytaine/green_functions/abstract_green_function.py +64 -0
- capytaine/green_functions/delhommeau.py +522 -0
- capytaine/green_functions/hams.py +210 -0
- capytaine/io/__init__.py +0 -0
- capytaine/io/bemio.py +153 -0
- capytaine/io/legacy.py +228 -0
- capytaine/io/wamit.py +479 -0
- capytaine/io/xarray.py +673 -0
- capytaine/meshes/__init__.py +2 -0
- capytaine/meshes/abstract_meshes.py +375 -0
- capytaine/meshes/clean.py +302 -0
- capytaine/meshes/clip.py +347 -0
- capytaine/meshes/export.py +89 -0
- capytaine/meshes/geometry.py +259 -0
- capytaine/meshes/io.py +433 -0
- capytaine/meshes/meshes.py +826 -0
- capytaine/meshes/predefined/__init__.py +6 -0
- capytaine/meshes/predefined/cylinders.py +280 -0
- capytaine/meshes/predefined/rectangles.py +202 -0
- capytaine/meshes/predefined/spheres.py +55 -0
- capytaine/meshes/quality.py +159 -0
- capytaine/meshes/surface_integrals.py +82 -0
- capytaine/meshes/symmetric_meshes.py +641 -0
- capytaine/meshes/visualization.py +353 -0
- capytaine/post_pro/__init__.py +6 -0
- capytaine/post_pro/free_surfaces.py +85 -0
- capytaine/post_pro/impedance.py +92 -0
- capytaine/post_pro/kochin.py +54 -0
- capytaine/post_pro/rao.py +60 -0
- capytaine/tools/__init__.py +0 -0
- capytaine/tools/block_circulant_matrices.py +275 -0
- capytaine/tools/cache_on_disk.py +26 -0
- capytaine/tools/deprecation_handling.py +18 -0
- capytaine/tools/lists_of_points.py +52 -0
- capytaine/tools/memory_monitor.py +45 -0
- capytaine/tools/optional_imports.py +27 -0
- capytaine/tools/prony_decomposition.py +150 -0
- capytaine/tools/symbolic_multiplication.py +161 -0
- capytaine/tools/timer.py +90 -0
- capytaine/ui/__init__.py +0 -0
- capytaine/ui/cli.py +28 -0
- capytaine/ui/rich.py +5 -0
- capytaine-3.0.0a1.dist-info/LICENSE +674 -0
- capytaine-3.0.0a1.dist-info/METADATA +755 -0
- capytaine-3.0.0a1.dist-info/RECORD +65 -0
- capytaine-3.0.0a1.dist-info/WHEEL +4 -0
- capytaine-3.0.0a1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
from scipy.optimize import brentq
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from capytaine.green_functions.abstract_green_function import AbstractGreenFunction, GreenFunctionEvaluationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LiangWuNoblesseGF(AbstractGreenFunction):
|
|
9
|
+
"""Wrapper for the Fortran implementation of the infinite depth Green function of [Liang, Wu, Noblesse, 2018].
|
|
10
|
+
|
|
11
|
+
Uses the same implementation as Delhommeau() for the Rankine and reflected Rankine terms.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
floating_point_precision = "float64"
|
|
15
|
+
|
|
16
|
+
fortran_core = import_module("capytaine.green_functions.Delhommeau_float64")
|
|
17
|
+
tabulation_grid_shape_index = fortran_core.constants.liang_wu_noblesse
|
|
18
|
+
exportable_settings = {'green_function': "LiangWuNoblesseGF"}
|
|
19
|
+
|
|
20
|
+
# Dummy arrays that won't actually be used by the fortran code.
|
|
21
|
+
prony_decomposition = np.zeros((1, 1))
|
|
22
|
+
dispersion_relation_roots = np.empty(1)
|
|
23
|
+
finite_depth_method_index = -9999
|
|
24
|
+
tabulation_nb_integration_points = 1
|
|
25
|
+
tabulated_r_range = np.empty(1)
|
|
26
|
+
tabulated_z_range = np.empty(1)
|
|
27
|
+
tabulated_integrals = np.empty(1)
|
|
28
|
+
dummy_param = -999
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return "LiangWuNoblesseGF()"
|
|
32
|
+
|
|
33
|
+
def __repr__(self):
|
|
34
|
+
return "LiangWuNoblesseGF()"
|
|
35
|
+
|
|
36
|
+
def _repr_pretty_(self, p, cycle):
|
|
37
|
+
p.text(self.__repr__())
|
|
38
|
+
|
|
39
|
+
def evaluate(
|
|
40
|
+
self, mesh1, mesh2, *,
|
|
41
|
+
free_surface=0.0, water_depth=np.inf, wavenumber,
|
|
42
|
+
adjoint_double_layer=True, early_dot_product=True,
|
|
43
|
+
diagonal_term_in_double_layer=True,
|
|
44
|
+
):
|
|
45
|
+
|
|
46
|
+
if free_surface == np.inf or water_depth < np.inf:
|
|
47
|
+
raise NotImplementedError("LiangWuNoblesseGF() is only implemented for infinite depth with a free surface")
|
|
48
|
+
|
|
49
|
+
if wavenumber == np.inf:
|
|
50
|
+
gf_singularities_index = self.fortran_core.constants.high_freq
|
|
51
|
+
else:
|
|
52
|
+
gf_singularities_index = self.fortran_core.constants.low_freq
|
|
53
|
+
|
|
54
|
+
collocation_points, early_dot_product_normals = \
|
|
55
|
+
self._get_colocation_points_and_normals(mesh1, mesh2, adjoint_double_layer)
|
|
56
|
+
|
|
57
|
+
S, K = self._init_matrices(
|
|
58
|
+
(collocation_points.shape[0], mesh2.nb_faces), early_dot_product=early_dot_product
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.fortran_core.matrices.build_matrices(
|
|
62
|
+
collocation_points, early_dot_product_normals,
|
|
63
|
+
mesh2.vertices, mesh2.faces + 1,
|
|
64
|
+
mesh2.faces_centers, mesh2.faces_normals,
|
|
65
|
+
mesh2.faces_areas, mesh2.faces_radiuses,
|
|
66
|
+
*mesh2.quadrature_points,
|
|
67
|
+
wavenumber, np.inf,
|
|
68
|
+
self.tabulation_nb_integration_points, self.tabulation_grid_shape_index,
|
|
69
|
+
self.tabulated_r_range, self.tabulated_z_range, self.tabulated_integrals,
|
|
70
|
+
self.dummy_param, self.prony_decomposition, self.dispersion_relation_roots,
|
|
71
|
+
gf_singularities_index, adjoint_double_layer,
|
|
72
|
+
S, K
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if diagonal_term_in_double_layer:
|
|
76
|
+
self.fortran_core.matrices.add_diagonal_term(
|
|
77
|
+
mesh2.faces_centers, early_dot_product_normals, free_surface, K,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if np.any(np.isnan(S)) or np.any(np.isnan(K)):
|
|
81
|
+
raise GreenFunctionEvaluationError(
|
|
82
|
+
"Green function returned a NaN in the interaction matrix.\n"
|
|
83
|
+
"It could be due to overlapping panels.")
|
|
84
|
+
|
|
85
|
+
if early_dot_product:
|
|
86
|
+
K = K.reshape((collocation_points.shape[0], mesh2.nb_faces))
|
|
87
|
+
|
|
88
|
+
return S, K
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class FinGreen3D(AbstractGreenFunction):
|
|
92
|
+
"""Wrapper for the Fortran implementation of the finite depth Green function of [Liu et al.].
|
|
93
|
+
|
|
94
|
+
Uses the same implementation as Delhommeau() for the Rankine and reflected Rankine terms.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
floating_point_precision = "float64"
|
|
98
|
+
|
|
99
|
+
fortran_core = import_module("capytaine.green_functions.Delhommeau_float64")
|
|
100
|
+
finite_depth_method_index = fortran_core.constants.fingreen3d_method
|
|
101
|
+
gf_singularities_index = fortran_core.constants.low_freq
|
|
102
|
+
|
|
103
|
+
# Dummy arrays that won't actually be used by the fortran code.
|
|
104
|
+
prony_decomposition = np.zeros((1, 1))
|
|
105
|
+
tabulation_nb_integration_points = 1
|
|
106
|
+
tabulated_r_range = np.empty(1)
|
|
107
|
+
tabulated_z_range = np.empty(1)
|
|
108
|
+
tabulated_integrals = np.empty(1)
|
|
109
|
+
dummy_param = -999
|
|
110
|
+
|
|
111
|
+
def __init__(self, *, nb_dispersion_roots=200):
|
|
112
|
+
self.nb_dispersion_roots = nb_dispersion_roots
|
|
113
|
+
self.exportable_settings = {
|
|
114
|
+
'green_function': "FinGreen3D",
|
|
115
|
+
'nb_dispersion_roots': nb_dispersion_roots
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def __str__(self):
|
|
119
|
+
return f"FinGreen3D(nb_dispersion_roots={self.nb_dispersion_roots})"
|
|
120
|
+
|
|
121
|
+
def __repr__(self):
|
|
122
|
+
return f"FinGreen3D(nb_dispersion_roots={self.nb_dispersion_roots})"
|
|
123
|
+
|
|
124
|
+
def _repr_pretty_(self, p, cycle):
|
|
125
|
+
p.text(self.__repr__())
|
|
126
|
+
|
|
127
|
+
def compute_dispersion_relation_roots(self, nk, wavenumber, depth):
|
|
128
|
+
omega2_h_over_g = wavenumber*np.tanh(wavenumber*depth)*depth
|
|
129
|
+
def root(i_root):
|
|
130
|
+
return brentq(lambda y: omega2_h_over_g + y*np.tan(y), (2*i_root+1)*np.pi/2 + 1e-10, (2*i_root+2)*np.pi/2 - 1e-10)/depth
|
|
131
|
+
return np.array([wavenumber] + [root(i_root) for i_root in range(nk-1)])
|
|
132
|
+
|
|
133
|
+
def evaluate(
|
|
134
|
+
self, mesh1, mesh2, *,
|
|
135
|
+
free_surface=0.0, water_depth=np.inf, wavenumber,
|
|
136
|
+
adjoint_double_layer=True, early_dot_product=True,
|
|
137
|
+
diagonal_term_in_double_layer=True,
|
|
138
|
+
):
|
|
139
|
+
|
|
140
|
+
if free_surface == np.inf or water_depth == np.inf:
|
|
141
|
+
raise NotImplementedError("FinGreen3D is only implemented for finite depth with a free surface.")
|
|
142
|
+
if wavenumber == 0.0 or wavenumber == np.inf:
|
|
143
|
+
raise NotImplementedError("FinGreen3D is only implemented for non-zero and non-infinite frequencies")
|
|
144
|
+
|
|
145
|
+
dispersion_relation_roots = self.compute_dispersion_relation_roots(
|
|
146
|
+
self.nb_dispersion_roots,
|
|
147
|
+
wavenumber,
|
|
148
|
+
water_depth
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
collocation_points, early_dot_product_normals = \
|
|
152
|
+
self._get_colocation_points_and_normals(mesh1, mesh2, adjoint_double_layer)
|
|
153
|
+
|
|
154
|
+
S, K = self._init_matrices(
|
|
155
|
+
(collocation_points.shape[0], mesh2.nb_faces), early_dot_product=early_dot_product
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
self.fortran_core.matrices.build_matrices(
|
|
159
|
+
collocation_points, early_dot_product_normals,
|
|
160
|
+
mesh2.vertices, mesh2.faces + 1,
|
|
161
|
+
mesh2.faces_centers, mesh2.faces_normals,
|
|
162
|
+
mesh2.faces_areas, mesh2.faces_radiuses,
|
|
163
|
+
*mesh2.quadrature_points,
|
|
164
|
+
wavenumber, water_depth,
|
|
165
|
+
self.tabulation_nb_integration_points, self.dummy_param,
|
|
166
|
+
self.tabulated_r_range, self.tabulated_z_range, self.tabulated_integrals,
|
|
167
|
+
self.finite_depth_method_index, self.prony_decomposition, dispersion_relation_roots,
|
|
168
|
+
self.gf_singularities_index, adjoint_double_layer,
|
|
169
|
+
S, K
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if diagonal_term_in_double_layer:
|
|
173
|
+
self.fortran_core.matrices.add_diagonal_term(
|
|
174
|
+
mesh2.faces_centers, early_dot_product_normals, free_surface, K,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if np.any(np.isnan(S)) or np.any(np.isnan(K)):
|
|
178
|
+
raise GreenFunctionEvaluationError(
|
|
179
|
+
"Green function returned a NaN in the interaction matrix.\n"
|
|
180
|
+
"It could be due to overlapping panels.")
|
|
181
|
+
|
|
182
|
+
if early_dot_product:
|
|
183
|
+
K = K.reshape((collocation_points.shape[0], mesh2.nb_faces))
|
|
184
|
+
|
|
185
|
+
return S, K
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class HAMS_GF(AbstractGreenFunction):
|
|
189
|
+
floating_point_precision = "float64"
|
|
190
|
+
|
|
191
|
+
exportable_settings = {'green_function': "HAMS_GF"}
|
|
192
|
+
|
|
193
|
+
def __init__(self):
|
|
194
|
+
self.infinite_depth_gf = LiangWuNoblesseGF()
|
|
195
|
+
self.finite_depth_gf = FinGreen3D(nb_dispersion_roots=200)
|
|
196
|
+
|
|
197
|
+
def __str__(self):
|
|
198
|
+
return "HAMS_GF()"
|
|
199
|
+
|
|
200
|
+
def __repr__(self):
|
|
201
|
+
return "HAMS_GF()"
|
|
202
|
+
|
|
203
|
+
def _repr_pretty_(self, p, cycle):
|
|
204
|
+
p.text(self.__repr__())
|
|
205
|
+
|
|
206
|
+
def evaluate(self, mesh1, mesh2, *, water_depth=np.inf, **kwargs):
|
|
207
|
+
if water_depth == np.inf:
|
|
208
|
+
return self.infinite_depth_gf.evaluate(mesh1, mesh2, water_depth=water_depth, **kwargs)
|
|
209
|
+
else:
|
|
210
|
+
return self.finite_depth_gf.evaluate(mesh1, mesh2, water_depth=water_depth, **kwargs)
|
capytaine/io/__init__.py
ADDED
|
File without changes
|
capytaine/io/bemio.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from scipy.optimize import newton
|
|
6
|
+
|
|
7
|
+
LOG = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
#######################
|
|
10
|
+
# Import from Bemio #
|
|
11
|
+
#######################
|
|
12
|
+
|
|
13
|
+
def dataframe_from_bemio(bemio_obj, wavenumber, wavelength):
|
|
14
|
+
"""Transform a :class:`bemio.data_structures.bem.HydrodynamicData` into a
|
|
15
|
+
:class:`pandas.DataFrame`.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
bemio_obj: Bemio data_stuctures.bem.HydrodynamicData class
|
|
20
|
+
Loaded NEMOH, AQWA, or WAMIT data created using `bemio.io.nemoh.read`,
|
|
21
|
+
`bemio.io.aqwa.read`, or `bemio.io.wamit.read` functions, respectively.
|
|
22
|
+
wavenumber: bool
|
|
23
|
+
If True, the coordinate 'wavenumber' will be added to the output dataset.
|
|
24
|
+
wavelength: bool
|
|
25
|
+
If True, the coordinate 'wavelength' will be added to the output dataset.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
dofs = np.array(['Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw'])
|
|
30
|
+
for i in range(bemio_obj.body[0].num_bodies):
|
|
31
|
+
difr_dict = []
|
|
32
|
+
rad_dict = []
|
|
33
|
+
|
|
34
|
+
rho = bemio_obj.body[0].rho
|
|
35
|
+
g = bemio_obj.body[0].g
|
|
36
|
+
|
|
37
|
+
if bemio_obj.body[i].water_depth == 'infinite':
|
|
38
|
+
bemio_obj.body[i].water_depth = np.inf
|
|
39
|
+
|
|
40
|
+
from_wamit = (bemio_obj.body[i].bem_code == 'WAMIT') # WAMIT coefficients need to be dimensionalized
|
|
41
|
+
|
|
42
|
+
for omega_idx, omega in enumerate(np.sort(bemio_obj.body[i].w)):
|
|
43
|
+
|
|
44
|
+
# DiffractionProblem variable equivalents
|
|
45
|
+
for dir_idx, dir in enumerate(bemio_obj.body[i].wave_dir):
|
|
46
|
+
temp_dict = {}
|
|
47
|
+
temp_dict['body_name'] = bemio_obj.body[i].name
|
|
48
|
+
temp_dict['water_depth'] = bemio_obj.body[i].water_depth
|
|
49
|
+
temp_dict['omega'] = omega
|
|
50
|
+
temp_dict['freq'] = omega/(2*np.pi)
|
|
51
|
+
temp_dict['period'] = 2*np.pi/omega
|
|
52
|
+
temp_dict['rho'] = rho
|
|
53
|
+
temp_dict['g'] = g
|
|
54
|
+
temp_dict['kind'] = "DiffractionResult"
|
|
55
|
+
temp_dict['forward_speed'] = 0.0
|
|
56
|
+
temp_dict['free_surface'] = 0.0
|
|
57
|
+
temp_dict['wave_direction'] = np.radians(dir)
|
|
58
|
+
temp_dict['influenced_dof'] = dofs
|
|
59
|
+
|
|
60
|
+
if wavenumber or wavelength:
|
|
61
|
+
if temp_dict['water_depth'] == np.inf or omega**2*temp_dict['water_depth']/temp_dict['g'] > 20:
|
|
62
|
+
k = omega**2/temp_dict['g']
|
|
63
|
+
else:
|
|
64
|
+
k = newton(lambda x: x*np.tanh(x) - omega**2*temp_dict['water_depth']/temp_dict['g'], x0=1.0)/temp_dict['water_depth']
|
|
65
|
+
|
|
66
|
+
if wavenumber:
|
|
67
|
+
temp_dict['wavenumber'] = k
|
|
68
|
+
|
|
69
|
+
if wavelength:
|
|
70
|
+
if k == 0.0:
|
|
71
|
+
temp_dict['wavelength'] = np.inf
|
|
72
|
+
else:
|
|
73
|
+
temp_dict['wavelength'] = 2*np.pi/k
|
|
74
|
+
|
|
75
|
+
Fexc = np.empty(shape=bemio_obj.body[i].ex.re[:, dir_idx, omega_idx].shape, dtype=np.complex128)
|
|
76
|
+
if from_wamit:
|
|
77
|
+
Fexc.real = bemio_obj.body[i].ex.re[:, dir_idx, omega_idx] * rho * g
|
|
78
|
+
Fexc.imag = bemio_obj.body[i].ex.im[:, dir_idx, omega_idx] * rho * g
|
|
79
|
+
else:
|
|
80
|
+
Fexc.real = bemio_obj.body[i].ex.re[:, dir_idx, omega_idx]
|
|
81
|
+
Fexc.imag = bemio_obj.body[i].ex.im[:, dir_idx, omega_idx]
|
|
82
|
+
temp_dict['diffraction_force'] = Fexc.flatten()
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
Fexc_fk = np.empty(shape=bemio_obj.body[i].ex.fk.re[:, dir_idx, omega_idx].shape, dtype=np.complex128)
|
|
86
|
+
if from_wamit:
|
|
87
|
+
Fexc_fk.real = bemio_obj.body[i].ex.fk.re[:, dir_idx, omega_idx] * rho * g
|
|
88
|
+
Fexc_fk.imag = bemio_obj.body[i].ex.fk.im[:, dir_idx, omega_idx] * rho * g
|
|
89
|
+
else:
|
|
90
|
+
Fexc_fk.real = bemio_obj.body[i].ex.fk.re[:, dir_idx, omega_idx]
|
|
91
|
+
Fexc_fk.imag = bemio_obj.body[i].ex.fk.im[:, dir_idx, omega_idx]
|
|
92
|
+
temp_dict['Froude_Krylov_force'] = Fexc_fk.flatten()
|
|
93
|
+
|
|
94
|
+
except AttributeError:
|
|
95
|
+
# LOG.warning('\tNo Froude-Krylov forces found for ' + bemio_obj.body[i].name + ' at ' + str(dir) + \
|
|
96
|
+
# ' degrees (omega = ' + str(omega) + '), replacing with zeros.')
|
|
97
|
+
temp_dict['Froude_Krylov_force'] = np.zeros((bemio_obj.body[i].ex.re[:, dir_idx, omega_idx].size,), dtype=np.complex128)
|
|
98
|
+
|
|
99
|
+
difr_dict.append(temp_dict)
|
|
100
|
+
|
|
101
|
+
# RadiationProblem + Hydrostatics variable equivalents
|
|
102
|
+
for radiating_dof_idx, radiating_dof in enumerate(dofs):
|
|
103
|
+
temp_dict = {}
|
|
104
|
+
temp_dict['body_name'] = bemio_obj.body[i].name
|
|
105
|
+
temp_dict['water_depth'] = bemio_obj.body[i].water_depth
|
|
106
|
+
temp_dict['omega'] = omega
|
|
107
|
+
temp_dict['freq'] = omega/(2*np.pi)
|
|
108
|
+
temp_dict['rho'] = rho
|
|
109
|
+
temp_dict['g'] = g
|
|
110
|
+
temp_dict['kind'] = "RadiationResult"
|
|
111
|
+
temp_dict['free_surface'] = 0.0
|
|
112
|
+
temp_dict['forward_speed'] = 0.0
|
|
113
|
+
temp_dict['wave_direction'] = 0.0
|
|
114
|
+
temp_dict['influenced_dof'] = dofs
|
|
115
|
+
temp_dict['radiating_dof'] = radiating_dof
|
|
116
|
+
temp_dict['added_mass'] = bemio_obj.body[i].am.all[radiating_dof_idx, :, omega_idx].flatten()
|
|
117
|
+
temp_dict['radiation_damping'] = bemio_obj.body[i].rd.all[radiating_dof_idx, :, omega_idx].flatten()
|
|
118
|
+
|
|
119
|
+
if from_wamit:
|
|
120
|
+
temp_dict['added_mass'] = temp_dict['added_mass'] * rho
|
|
121
|
+
temp_dict['radiation_damping'] = temp_dict['radiation_damping'] * rho * omega
|
|
122
|
+
|
|
123
|
+
if wavenumber or wavelength:
|
|
124
|
+
if temp_dict['water_depth'] == np.inf or omega**2*temp_dict['water_depth']/temp_dict['g'] > 20:
|
|
125
|
+
k = omega**2/temp_dict['g']
|
|
126
|
+
else:
|
|
127
|
+
k = newton(lambda x: x*np.tanh(x) - omega**2*temp_dict['water_depth']/temp_dict['g'], x0=1.0)/temp_dict['water_depth']
|
|
128
|
+
|
|
129
|
+
if wavenumber:
|
|
130
|
+
temp_dict['wavenumber'] = k
|
|
131
|
+
|
|
132
|
+
if wavelength:
|
|
133
|
+
if k == 0.0:
|
|
134
|
+
temp_dict['wavelength'] = np.inf
|
|
135
|
+
else:
|
|
136
|
+
temp_dict['wavelength'] = 2*np.pi/k
|
|
137
|
+
|
|
138
|
+
rad_dict.append(temp_dict)
|
|
139
|
+
|
|
140
|
+
df = pd.concat([
|
|
141
|
+
pd.DataFrame.from_dict(difr_dict).explode(['influenced_dof', 'diffraction_force', 'Froude_Krylov_force']),
|
|
142
|
+
pd.DataFrame.from_dict(rad_dict).explode(['influenced_dof', 'added_mass', 'radiation_damping'])
|
|
143
|
+
])
|
|
144
|
+
df = df.astype({'added_mass': np.float64, 'radiation_damping': np.float64, 'diffraction_force': np.complex128, 'Froude_Krylov_force': np.complex128})
|
|
145
|
+
|
|
146
|
+
all_dofs_in_order = ['Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw']
|
|
147
|
+
inf_dof_cat = pd.CategoricalDtype(categories=all_dofs_in_order)
|
|
148
|
+
df["influenced_dof"] = df["influenced_dof"].astype(inf_dof_cat)
|
|
149
|
+
if 'added_mass' in df.columns:
|
|
150
|
+
rad_dof_cat = pd.CategoricalDtype(categories=all_dofs_in_order)
|
|
151
|
+
df["radiating_dof"] = df["radiating_dof"].astype(rad_dof_cat)
|
|
152
|
+
|
|
153
|
+
return df
|
capytaine/io/legacy.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Import or export Nemoh.cal files for backward compatibility with Nemoh 2."""
|
|
2
|
+
# Copyright (C) 2017-2019 Matthieu Ancellin
|
|
3
|
+
# See LICENSE file at <https://github.com/mancellin/capytaine>
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from capytaine.bem.solver import BEMSolver
|
|
11
|
+
from capytaine.io.xarray import assemble_dataset
|
|
12
|
+
from capytaine.meshes.io import load_mesh
|
|
13
|
+
from capytaine.bodies.bodies import FloatingBody
|
|
14
|
+
from capytaine.bem.problems_and_results import DiffractionProblem, RadiationProblem
|
|
15
|
+
|
|
16
|
+
LOG = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def import_cal_file(filepath):
|
|
20
|
+
"""Read a Nemoh.cal file and return a list of problems."""
|
|
21
|
+
|
|
22
|
+
with open(filepath, 'r') as cal_file:
|
|
23
|
+
|
|
24
|
+
cal_file.readline() # Unused line.
|
|
25
|
+
rho = float(cal_file.readline().split()[0])
|
|
26
|
+
g = float(cal_file.readline().split()[0])
|
|
27
|
+
water_depth = float(cal_file.readline().split()[0])
|
|
28
|
+
if water_depth == 0.0:
|
|
29
|
+
water_depth = np.inf
|
|
30
|
+
xeff, yeff = (float(x) for x in cal_file.readline().split()[0:2])
|
|
31
|
+
|
|
32
|
+
bodies = []
|
|
33
|
+
|
|
34
|
+
cal_file.readline() # Unused line.
|
|
35
|
+
nb_bodies = int(cal_file.readline().split()[0])
|
|
36
|
+
for _ in range(nb_bodies):
|
|
37
|
+
cal_file.readline() # Unused line.
|
|
38
|
+
mesh_file = cal_file.readline().split()[0].strip()
|
|
39
|
+
mesh_file = os.path.join(os.path.dirname(filepath), mesh_file) # mesh path are relative to Nemoh.cal
|
|
40
|
+
cal_file.readline() # Number of points, number of panels (unused)
|
|
41
|
+
|
|
42
|
+
if os.path.splitext(mesh_file)[1] == '.py':
|
|
43
|
+
from importlib.util import spec_from_file_location, module_from_spec
|
|
44
|
+
spec = spec_from_file_location("body_initialization_module", mesh_file)
|
|
45
|
+
body_initialization = module_from_spec(spec)
|
|
46
|
+
spec.loader.exec_module(body_initialization)
|
|
47
|
+
body = body_initialization.body
|
|
48
|
+
else:
|
|
49
|
+
body = FloatingBody(mesh=load_mesh(mesh_file, file_format='nemoh'))
|
|
50
|
+
|
|
51
|
+
nb_dofs = int(cal_file.readline().split()[0])
|
|
52
|
+
for i_dof in range(nb_dofs):
|
|
53
|
+
dof_data = cal_file.readline().split()
|
|
54
|
+
if int(dof_data[0]) == 1:
|
|
55
|
+
direction = np.array([float(x) for x in dof_data[1:4]])
|
|
56
|
+
body.add_translation_dof(direction=direction)
|
|
57
|
+
elif int(dof_data[0]) == 2:
|
|
58
|
+
direction = np.array([float(x) for x in dof_data[1:4]])
|
|
59
|
+
center_of_mass = np.array([float(x) for x in dof_data[4:7]])
|
|
60
|
+
body.add_rotation_dof(direction=direction, rotation_center=center_of_mass)
|
|
61
|
+
|
|
62
|
+
nb_forces = int(cal_file.readline().split()[0])
|
|
63
|
+
for i_force in range(nb_forces):
|
|
64
|
+
force_data = cal_file.readline().split()
|
|
65
|
+
if int(force_data[0]) == 1:
|
|
66
|
+
direction = np.array([float(x) for x in force_data[1:4]])
|
|
67
|
+
elif int(force_data[0]) == 2:
|
|
68
|
+
direction = np.array([float(x) for x in force_data[1:4]])
|
|
69
|
+
center_of_mass = np.array([float(x) for x in force_data[4:7]])
|
|
70
|
+
# TODO: use the generalized forces.
|
|
71
|
+
|
|
72
|
+
nb_additional_lines = int(cal_file.readline().split()[0])
|
|
73
|
+
for _ in range(nb_additional_lines):
|
|
74
|
+
cal_file.readline() # The additional lines are just ignored.
|
|
75
|
+
|
|
76
|
+
bodies.append(body)
|
|
77
|
+
|
|
78
|
+
if nb_bodies > 1:
|
|
79
|
+
bodies = FloatingBody.join_bodies(*bodies)
|
|
80
|
+
else:
|
|
81
|
+
bodies = bodies[0]
|
|
82
|
+
|
|
83
|
+
cal_file.readline() # Unused line.
|
|
84
|
+
frequency_data_string_without_comment = cal_file.readline().split('!')[0]
|
|
85
|
+
frequency_data = frequency_data_string_without_comment.split()
|
|
86
|
+
if len(frequency_data) == 3: # Nemoh v2 format
|
|
87
|
+
omega_range = np.linspace(float(frequency_data[1]), float(frequency_data[2]), int(frequency_data[0]))
|
|
88
|
+
else:
|
|
89
|
+
type_of_frequency_data = int(frequency_data[0])
|
|
90
|
+
if type_of_frequency_data == 1: # angular frequency
|
|
91
|
+
omega_range = np.linspace(float(frequency_data[2]), float(frequency_data[3]), int(frequency_data[1]))
|
|
92
|
+
elif type_of_frequency_data == 2: # frequency
|
|
93
|
+
omega_range = 2*np.pi*np.linspace(float(frequency_data[2]), float(frequency_data[3]), int(frequency_data[1]))
|
|
94
|
+
elif type_of_frequency_data == 3: # period
|
|
95
|
+
omega_range = 2*np.pi/np.linspace(float(frequency_data[2]), float(frequency_data[3]), int(frequency_data[1]))
|
|
96
|
+
else:
|
|
97
|
+
raise ValueError(f"Cannot parse the frequency data \"{frequency_data_string_without_comment}\" in {filepath}.")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
direction_data = cal_file.readline().split()
|
|
101
|
+
direction_range = np.linspace(float(direction_data[1]), float(direction_data[2]), int(direction_data[0]))
|
|
102
|
+
direction_range = np.pi/180*direction_range # conversion from degrees to radians.
|
|
103
|
+
|
|
104
|
+
# The options below are not implemented yet.
|
|
105
|
+
|
|
106
|
+
cal_file.readline() # Unused line.
|
|
107
|
+
irf_data = cal_file.readline()
|
|
108
|
+
show_pressure = cal_file.readline().split()[0] == "1"
|
|
109
|
+
kochin_data = cal_file.readline().split()
|
|
110
|
+
kochin_range = np.linspace(float(kochin_data[1]), float(kochin_data[2]), int(kochin_data[0]))
|
|
111
|
+
free_surface_data = cal_file.readline().split()
|
|
112
|
+
|
|
113
|
+
# Generate Capytaine's problem objects
|
|
114
|
+
env_args = dict(body=bodies, rho=rho, water_depth=water_depth, g=g)
|
|
115
|
+
problems = []
|
|
116
|
+
for omega in omega_range:
|
|
117
|
+
for direction in direction_range:
|
|
118
|
+
problems.append(DiffractionProblem(wave_direction=direction, omega=omega, **env_args))
|
|
119
|
+
for dof in bodies.dofs:
|
|
120
|
+
problems.append(RadiationProblem(radiating_dof=dof, omega=omega, **env_args))
|
|
121
|
+
|
|
122
|
+
return problems
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def write_dataset_as_tecplot_files(results_directory, data):
|
|
126
|
+
"""Write some of the data from a xarray dataset into legacy tecplot file outputs."""
|
|
127
|
+
|
|
128
|
+
if 'added_mass' in data:
|
|
129
|
+
with open(os.path.join(results_directory, 'RadiationCoefficients.tec'), 'w') as fi:
|
|
130
|
+
for i in range(len(data['radiating_dof'])+1):
|
|
131
|
+
fi.write(f'...\n')
|
|
132
|
+
for dof in data.radiating_dof:
|
|
133
|
+
fi.write(f'{dof.values}\n')
|
|
134
|
+
for o in data.omega:
|
|
135
|
+
fi.write(f' {o.values:e} ')
|
|
136
|
+
for dof2 in data.influenced_dof:
|
|
137
|
+
fi.write(f"{data['added_mass'].sel(omega=o, radiating_dof=dof, influenced_dof=dof2).values:e}")
|
|
138
|
+
fi.write(' ')
|
|
139
|
+
fi.write(f"{data['radiation_damping'].sel(omega=o, radiating_dof=dof, influenced_dof=dof2).values:e}")
|
|
140
|
+
fi.write(' ')
|
|
141
|
+
fi.write('\n')
|
|
142
|
+
|
|
143
|
+
if 'diffraction_force' in data:
|
|
144
|
+
data['excitation_force'] = data['Froude_Krylov_force'] + data['diffraction_force']
|
|
145
|
+
with open(os.path.join(results_directory, 'ExcitationForce.tec'), 'w') as fi:
|
|
146
|
+
for i in range(len(data.influenced_dof)+1):
|
|
147
|
+
fi.write(f'...\n')
|
|
148
|
+
for wave_direction in data.wave_direction.values:
|
|
149
|
+
fi.write(f'angle={wave_direction}\n')
|
|
150
|
+
for o in data.omega.values:
|
|
151
|
+
fi.write(f' {o:e} ')
|
|
152
|
+
for dof in data.influenced_dof:
|
|
153
|
+
val = data['excitation_force'].sel(omega=o, wave_direction=wave_direction, influenced_dof=dof).values
|
|
154
|
+
fi.write(f'{np.abs(val):e}')
|
|
155
|
+
fi.write(' ')
|
|
156
|
+
fi.write(f'{np.angle(val):e}')
|
|
157
|
+
fi.write(' ')
|
|
158
|
+
fi.write('\n')
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _hydrostatics_writer(hydrostatics_file_path, kh_file_path, body):
|
|
162
|
+
"""Write the Hydrostatics.dat and KH.dat files"""
|
|
163
|
+
with open(hydrostatics_file_path, 'w') as hf:
|
|
164
|
+
for j in range(3):
|
|
165
|
+
line = f'XF = {body.center_of_buoyancy[j]:7.4f} - XG = {body.center_of_mass[j]:7.4f} \n'
|
|
166
|
+
hf.write(line)
|
|
167
|
+
line = f'Displacement = {body.volume:1.6E}'
|
|
168
|
+
hf.write(line)
|
|
169
|
+
hf.close()
|
|
170
|
+
np.savetxt(kh_file_path, body.hydrostatic_stiffness.values, fmt='%1.6E')
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def export_hydrostatics(hydrostatics_directory, bodies):
|
|
174
|
+
"""Export rigid body hydrostatics in Nemoh's format (KH.dat and Hydrostatics.dat).
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
hydrostatics_directory: string
|
|
179
|
+
Path to the directory in which the data will be written (two files per body)
|
|
180
|
+
bodies: FloatingBody or list of FloatingBody
|
|
181
|
+
The body or the list of bodies. Each body is assumed to be a single
|
|
182
|
+
rigid body with 6 dofs. Each FloatingBody object is expected to have an
|
|
183
|
+
`inertia_matrix` and a `hydrostatic_stiffness` parameter.
|
|
184
|
+
|
|
185
|
+
Return
|
|
186
|
+
------
|
|
187
|
+
None
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
if os.path.isdir(hydrostatics_directory):
|
|
191
|
+
LOG.warning(f"""Exporting problem in already existing directory: {hydrostatics_directory}
|
|
192
|
+
You might be overwriting existing files!""")
|
|
193
|
+
else:
|
|
194
|
+
os.makedirs(hydrostatics_directory)
|
|
195
|
+
|
|
196
|
+
if isinstance(bodies, FloatingBody):
|
|
197
|
+
bodies = [bodies]
|
|
198
|
+
|
|
199
|
+
hydrostatics_file_name = "Hydrostatics.dat"
|
|
200
|
+
kh_file_name = "KH.dat"
|
|
201
|
+
|
|
202
|
+
body_count = len(bodies)
|
|
203
|
+
if body_count == 1:
|
|
204
|
+
body = bodies[0]
|
|
205
|
+
hydrostatics_file_path = os.path.join(hydrostatics_directory, hydrostatics_file_name)
|
|
206
|
+
kh_file_path = os.path.join(hydrostatics_directory, kh_file_name)
|
|
207
|
+
_hydrostatics_writer(hydrostatics_file_path, kh_file_path, body)
|
|
208
|
+
else:
|
|
209
|
+
for (i, body) in enumerate(bodies):
|
|
210
|
+
hydrostatics_file_path = os.path.join(hydrostatics_directory, f"Hydrostatics_{i}.dat")
|
|
211
|
+
kh_file_path = os.path.join(hydrostatics_directory, f"KH_{i}.dat")
|
|
212
|
+
_hydrostatics_writer(hydrostatics_file_path, kh_file_path, body)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def run_cal_file(paramfile):
|
|
216
|
+
problems = import_cal_file(paramfile)
|
|
217
|
+
solver = BEMSolver()
|
|
218
|
+
results = solver.solve_all(problems)
|
|
219
|
+
data = assemble_dataset(results)
|
|
220
|
+
|
|
221
|
+
results_directory = os.path.join(os.path.dirname(paramfile), 'results')
|
|
222
|
+
try:
|
|
223
|
+
os.mkdir(results_directory)
|
|
224
|
+
except FileExistsError:
|
|
225
|
+
LOG.warning(f"The output directory ({results_directory}) already exists. You might be overwriting existing data.")
|
|
226
|
+
|
|
227
|
+
LOG.info("Write results in legacy tecplot format.")
|
|
228
|
+
write_dataset_as_tecplot_files(results_directory, data)
|