capytaine 3.0.0a1__cp313-cp313-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.
Files changed (65) hide show
  1. capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. capytaine/.dylibs/libgfortran.5.dylib +0 -0
  3. capytaine/.dylibs/libquadmath.0.dylib +0 -0
  4. capytaine/__about__.py +21 -0
  5. capytaine/__init__.py +32 -0
  6. capytaine/bem/__init__.py +0 -0
  7. capytaine/bem/airy_waves.py +111 -0
  8. capytaine/bem/engines.py +321 -0
  9. capytaine/bem/problems_and_results.py +601 -0
  10. capytaine/bem/solver.py +718 -0
  11. capytaine/bodies/__init__.py +4 -0
  12. capytaine/bodies/bodies.py +630 -0
  13. capytaine/bodies/dofs.py +146 -0
  14. capytaine/bodies/hydrostatics.py +540 -0
  15. capytaine/bodies/multibodies.py +216 -0
  16. capytaine/green_functions/Delhommeau_float32.cpython-313-darwin.so +0 -0
  17. capytaine/green_functions/Delhommeau_float64.cpython-313-darwin.so +0 -0
  18. capytaine/green_functions/__init__.py +2 -0
  19. capytaine/green_functions/abstract_green_function.py +64 -0
  20. capytaine/green_functions/delhommeau.py +522 -0
  21. capytaine/green_functions/hams.py +210 -0
  22. capytaine/io/__init__.py +0 -0
  23. capytaine/io/bemio.py +153 -0
  24. capytaine/io/legacy.py +228 -0
  25. capytaine/io/wamit.py +479 -0
  26. capytaine/io/xarray.py +673 -0
  27. capytaine/meshes/__init__.py +2 -0
  28. capytaine/meshes/abstract_meshes.py +375 -0
  29. capytaine/meshes/clean.py +302 -0
  30. capytaine/meshes/clip.py +347 -0
  31. capytaine/meshes/export.py +89 -0
  32. capytaine/meshes/geometry.py +259 -0
  33. capytaine/meshes/io.py +433 -0
  34. capytaine/meshes/meshes.py +826 -0
  35. capytaine/meshes/predefined/__init__.py +6 -0
  36. capytaine/meshes/predefined/cylinders.py +280 -0
  37. capytaine/meshes/predefined/rectangles.py +202 -0
  38. capytaine/meshes/predefined/spheres.py +55 -0
  39. capytaine/meshes/quality.py +159 -0
  40. capytaine/meshes/surface_integrals.py +82 -0
  41. capytaine/meshes/symmetric_meshes.py +641 -0
  42. capytaine/meshes/visualization.py +353 -0
  43. capytaine/post_pro/__init__.py +6 -0
  44. capytaine/post_pro/free_surfaces.py +85 -0
  45. capytaine/post_pro/impedance.py +92 -0
  46. capytaine/post_pro/kochin.py +54 -0
  47. capytaine/post_pro/rao.py +60 -0
  48. capytaine/tools/__init__.py +0 -0
  49. capytaine/tools/block_circulant_matrices.py +275 -0
  50. capytaine/tools/cache_on_disk.py +26 -0
  51. capytaine/tools/deprecation_handling.py +18 -0
  52. capytaine/tools/lists_of_points.py +52 -0
  53. capytaine/tools/memory_monitor.py +45 -0
  54. capytaine/tools/optional_imports.py +27 -0
  55. capytaine/tools/prony_decomposition.py +150 -0
  56. capytaine/tools/symbolic_multiplication.py +161 -0
  57. capytaine/tools/timer.py +90 -0
  58. capytaine/ui/__init__.py +0 -0
  59. capytaine/ui/cli.py +28 -0
  60. capytaine/ui/rich.py +5 -0
  61. capytaine-3.0.0a1.dist-info/LICENSE +674 -0
  62. capytaine-3.0.0a1.dist-info/METADATA +755 -0
  63. capytaine-3.0.0a1.dist-info/RECORD +65 -0
  64. capytaine-3.0.0a1.dist-info/WHEEL +6 -0
  65. 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)
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)