capytaine 2.1__cp312-cp312-win_amd64.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 (85) hide show
  1. capytaine/__about__.py +16 -0
  2. capytaine/__init__.py +48 -0
  3. capytaine/bem/__init__.py +0 -0
  4. capytaine/bem/airy_waves.py +106 -0
  5. capytaine/bem/engines.py +441 -0
  6. capytaine/bem/problems_and_results.py +540 -0
  7. capytaine/bem/solver.py +463 -0
  8. capytaine/bodies/__init__.py +4 -0
  9. capytaine/bodies/bodies.py +1084 -0
  10. capytaine/bodies/dofs.py +19 -0
  11. capytaine/bodies/predefined/__init__.py +6 -0
  12. capytaine/bodies/predefined/cylinders.py +151 -0
  13. capytaine/bodies/predefined/rectangles.py +109 -0
  14. capytaine/bodies/predefined/spheres.py +70 -0
  15. capytaine/green_functions/__init__.py +2 -0
  16. capytaine/green_functions/abstract_green_function.py +12 -0
  17. capytaine/green_functions/delhommeau.py +380 -0
  18. capytaine/green_functions/libs/Delhommeau_float32.cp312-win_amd64.dll.a +0 -0
  19. capytaine/green_functions/libs/Delhommeau_float32.cp312-win_amd64.pyd +0 -0
  20. capytaine/green_functions/libs/Delhommeau_float64.cp312-win_amd64.dll.a +0 -0
  21. capytaine/green_functions/libs/Delhommeau_float64.cp312-win_amd64.pyd +0 -0
  22. capytaine/green_functions/libs/XieDelhommeau_float32.cp312-win_amd64.dll.a +0 -0
  23. capytaine/green_functions/libs/XieDelhommeau_float32.cp312-win_amd64.pyd +0 -0
  24. capytaine/green_functions/libs/XieDelhommeau_float64.cp312-win_amd64.dll.a +0 -0
  25. capytaine/green_functions/libs/XieDelhommeau_float64.cp312-win_amd64.pyd +0 -0
  26. capytaine/green_functions/libs/__init__.py +0 -0
  27. capytaine/io/__init__.py +0 -0
  28. capytaine/io/bemio.py +141 -0
  29. capytaine/io/legacy.py +328 -0
  30. capytaine/io/mesh_loaders.py +1061 -0
  31. capytaine/io/mesh_writers.py +692 -0
  32. capytaine/io/meshio.py +35 -0
  33. capytaine/io/xarray.py +514 -0
  34. capytaine/matrices/__init__.py +16 -0
  35. capytaine/matrices/block.py +590 -0
  36. capytaine/matrices/block_toeplitz.py +325 -0
  37. capytaine/matrices/builders.py +89 -0
  38. capytaine/matrices/linear_solvers.py +233 -0
  39. capytaine/matrices/low_rank.py +393 -0
  40. capytaine/meshes/__init__.py +6 -0
  41. capytaine/meshes/clipper.py +464 -0
  42. capytaine/meshes/collections.py +313 -0
  43. capytaine/meshes/geometry.py +409 -0
  44. capytaine/meshes/meshes.py +746 -0
  45. capytaine/meshes/predefined/__init__.py +6 -0
  46. capytaine/meshes/predefined/cylinders.py +314 -0
  47. capytaine/meshes/predefined/rectangles.py +261 -0
  48. capytaine/meshes/predefined/spheres.py +62 -0
  49. capytaine/meshes/properties.py +199 -0
  50. capytaine/meshes/quadratures.py +80 -0
  51. capytaine/meshes/quality.py +448 -0
  52. capytaine/meshes/surface_integrals.py +63 -0
  53. capytaine/meshes/symmetric.py +383 -0
  54. capytaine/post_pro/__init__.py +6 -0
  55. capytaine/post_pro/free_surfaces.py +88 -0
  56. capytaine/post_pro/impedance.py +92 -0
  57. capytaine/post_pro/kochin.py +54 -0
  58. capytaine/post_pro/rao.py +60 -0
  59. capytaine/tools/__init__.py +0 -0
  60. capytaine/tools/cache_on_disk.py +24 -0
  61. capytaine/tools/deprecation_handling.py +18 -0
  62. capytaine/tools/lists_of_points.py +52 -0
  63. capytaine/tools/lru_cache.py +49 -0
  64. capytaine/tools/optional_imports.py +27 -0
  65. capytaine/tools/prony_decomposition.py +94 -0
  66. capytaine/tools/symbolic_multiplication.py +104 -0
  67. capytaine/ui/__init__.py +0 -0
  68. capytaine/ui/cli.py +28 -0
  69. capytaine/ui/rich.py +5 -0
  70. capytaine/ui/vtk/__init__.py +3 -0
  71. capytaine/ui/vtk/animation.py +329 -0
  72. capytaine/ui/vtk/body_viewer.py +26 -0
  73. capytaine/ui/vtk/helpers.py +82 -0
  74. capytaine/ui/vtk/mesh_viewer.py +461 -0
  75. capytaine-2.1.dist-info/DELVEWHEEL +2 -0
  76. capytaine-2.1.dist-info/LICENSE +674 -0
  77. capytaine-2.1.dist-info/METADATA +756 -0
  78. capytaine-2.1.dist-info/RECORD +85 -0
  79. capytaine-2.1.dist-info/WHEEL +4 -0
  80. capytaine-2.1.dist-info/entry_points.txt +3 -0
  81. capytaine.libs/libgcc_s_seh-1.dll +0 -0
  82. capytaine.libs/libgfortran-5.dll +0 -0
  83. capytaine.libs/libgomp-1.dll +0 -0
  84. capytaine.libs/libquadmath-0.dll +0 -0
  85. capytaine.libs/libwinpthread-1.dll +0 -0
@@ -0,0 +1,380 @@
1
+ """Variants of Delhommeau's method for the computation of the Green function."""
2
+ # Copyright (C) 2017-2024 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
4
+
5
+ import os
6
+ import logging
7
+ from functools import lru_cache
8
+ from importlib import import_module
9
+
10
+ import numpy as np
11
+
12
+ from capytaine.meshes.meshes import Mesh
13
+ from capytaine.meshes.collections import CollectionOfMeshes
14
+ from capytaine.tools.prony_decomposition import exponential_decomposition, error_exponential_decomposition
15
+ from capytaine.tools.cache_on_disk import cache_directory
16
+
17
+ from capytaine.green_functions.abstract_green_function import AbstractGreenFunction
18
+
19
+ LOG = logging.getLogger(__name__)
20
+
21
+ _default_parameters = dict(
22
+ tabulation_nr=676,
23
+ tabulation_rmax=100.0,
24
+ tabulation_nz=372,
25
+ tabulation_zmin=-251.0,
26
+ tabulation_nb_integration_points=1000,
27
+ tabulation_method="scaled_nemoh3",
28
+ finite_depth_prony_decomposition_method="fortran",
29
+ floating_point_precision="float64"
30
+ )
31
+
32
+
33
+ class Delhommeau(AbstractGreenFunction):
34
+ """The Green function as implemented in Aquadyn and Nemoh.
35
+
36
+ Parameters
37
+ ----------
38
+ tabulation_nr: int, optional
39
+ Number of tabulation points for horizontal coordinate.
40
+ If 0 is given, no tabulation is used at all.
41
+ Default: 676
42
+ tabulation_rmax: float, optional
43
+ Maximum value of r range for the tabulation. (Minimum is zero.)
44
+ Only used with the :code:`"scaled_nemoh3"` method.
45
+ Default: 100.0
46
+ tabulation_nz: int, optional
47
+ Number of tabulation points for vertical coordinate.
48
+ If 0 is given, no tabulation is used at all.
49
+ Default: 372
50
+ tabulation_zmin: float, optional
51
+ Minimum value of z range for the tabulation. (Maximum is zero.)
52
+ Only used with the :code:`"scaled_nemoh3"` method.
53
+ Default: -251.0
54
+ tabulation_nb_integration_points: int, optional
55
+ Number of points for the numerical integration w.r.t. :math:`theta` of
56
+ Delhommeau's integrals
57
+ Default: 1000
58
+ tabulation_method: string, optional
59
+ Either :code:`"legacy"` or :code:`"scaled_nemoh3"`, which are the two
60
+ methods currently implemented.
61
+ Default: :code:`"scaled_nemoh3"`
62
+ finite_depth_prony_decomposition_method: string, optional
63
+ The implementation of the Prony decomposition used to compute the
64
+ finite water_depth Green function. Accepted values: :code:`'fortran'`
65
+ for Nemoh's implementation (by default), :code:`'python'` for an
66
+ experimental Python implementation.
67
+ See :func:`find_best_exponential_decomposition`.
68
+ floating_point_precision: string, optional
69
+ Either :code:`'float32'` for single precision computations or
70
+ :code:`'float64'` for double precision computations.
71
+ Default: :code:`'float64'`.
72
+
73
+ Attributes
74
+ ----------
75
+ fortran_core:
76
+ Compiled Fortran module with functions used to compute the Green
77
+ function.
78
+ tabulation_method_index: int
79
+ Integer passed to Fortran code to describe which method is used.
80
+ tabulated_r_range: numpy.array of shape (tabulation_nr,) and type floating_point_precision
81
+ tabulated_z_range: numpy.array of shape (tabulation_nz,) and type floating_point_precision
82
+ Coordinates of the tabulation points.
83
+ tabulated_integrals: numpy.array of shape (tabulation_nr, tabulation_nz, 2, 2) and type floating_point_precision
84
+ Tabulated Delhommeau integrals.
85
+ """
86
+
87
+ fortran_core_basename = "Delhommeau"
88
+
89
+
90
+ def __init__(self, *,
91
+ tabulation_nr=_default_parameters["tabulation_nr"],
92
+ tabulation_rmax=_default_parameters["tabulation_rmax"],
93
+ tabulation_nz=_default_parameters["tabulation_nz"],
94
+ tabulation_zmin=_default_parameters["tabulation_zmin"],
95
+ tabulation_nb_integration_points=_default_parameters["tabulation_nb_integration_points"],
96
+ tabulation_method=_default_parameters["tabulation_method"],
97
+ finite_depth_prony_decomposition_method=_default_parameters["finite_depth_prony_decomposition_method"],
98
+ floating_point_precision=_default_parameters["floating_point_precision"],
99
+ ):
100
+
101
+ self.floating_point_precision = floating_point_precision
102
+
103
+ self.fortran_core = import_module(f"capytaine.green_functions.libs.{self.fortran_core_basename}_{self.floating_point_precision}")
104
+
105
+ self.tabulation_method = tabulation_method
106
+ fortran_indices_for_methods = {
107
+ 'legacy': self.fortran_core.delhommeau_integrals.legacy_method,
108
+ 'scaled_nemoh3': self.fortran_core.delhommeau_integrals.scaled_nemoh3_method,
109
+ }
110
+ self.tabulation_method_index = fortran_indices_for_methods[tabulation_method]
111
+
112
+ self._create_or_load_tabulation(tabulation_nr, tabulation_rmax, tabulation_nz, tabulation_zmin, tabulation_nb_integration_points)
113
+
114
+ self.finite_depth_prony_decomposition_method = finite_depth_prony_decomposition_method
115
+
116
+ self.exportable_settings = {
117
+ 'green_function': self.__class__.__name__,
118
+ 'tabulation_nr': tabulation_nr,
119
+ 'tabulation_rmax': tabulation_rmax,
120
+ 'tabulation_nz': tabulation_nz,
121
+ 'tabulation_zmin': tabulation_zmin,
122
+ 'tabulation_nb_integration_points': tabulation_nb_integration_points,
123
+ 'tabulation_method': tabulation_method,
124
+ 'finite_depth_prony_decomposition_method': finite_depth_prony_decomposition_method,
125
+ 'floating_point_precision': floating_point_precision,
126
+ }
127
+
128
+ self._hash = hash(self.exportable_settings.values())
129
+
130
+ def __hash__(self):
131
+ return self._hash
132
+
133
+ def __str__(self):
134
+ # Print only the non-default values.
135
+ to_be_printed = []
136
+ for name, value in self.exportable_settings.items():
137
+ if name in _default_parameters and value != _default_parameters[name]:
138
+ to_be_printed.append(f"{name}={repr(value)}")
139
+ return f"{self.__class__.__name__}({', '.join(to_be_printed)})"
140
+
141
+ def __repr__(self):
142
+ # Same as __str__ except all values are printed even when they are the
143
+ # default value.
144
+ to_be_printed = []
145
+ for name, value in self.exportable_settings.items():
146
+ if name in _default_parameters:
147
+ to_be_printed.append(f"{name}={repr(value)}")
148
+ return f"{self.__class__.__name__}({', '.join(to_be_printed)})"
149
+
150
+ def _repr_pretty_(self, p, cycle):
151
+ p.text(self.__repr__())
152
+
153
+ def _create_or_load_tabulation(self, tabulation_nr, tabulation_rmax,
154
+ tabulation_nz, tabulation_zmin,
155
+ tabulation_nb_integration_points):
156
+ """This method either:
157
+ - loads an existing tabulation saved on disk
158
+ - generates a new tabulation with the data provided as argument and save it on disk.
159
+ """
160
+
161
+ # Normalize inputs
162
+ tabulation_rmax = float(tabulation_rmax)
163
+ tabulation_zmin = float(tabulation_zmin)
164
+
165
+ filename = "tabulation_{}_{}_{}_{}_{}_{}_{}_{}.npz".format(
166
+ self.fortran_core_basename, self.floating_point_precision,
167
+ self.tabulation_method,
168
+ tabulation_nr, tabulation_rmax, tabulation_nz, tabulation_zmin,
169
+ tabulation_nb_integration_points
170
+ )
171
+ filepath = os.path.join(cache_directory(), filename)
172
+
173
+ if os.path.exists(filepath):
174
+ LOG.info("Loading tabulation from %s", filepath)
175
+ loaded_arrays = np.load(filepath)
176
+ self.tabulated_r_range = loaded_arrays["r_range"]
177
+ self.tabulated_z_range = loaded_arrays["z_range"]
178
+ self.tabulated_integrals = loaded_arrays["values"]
179
+
180
+ else:
181
+ LOG.warning("Precomputing tabulation, it may take a few seconds.")
182
+ self.tabulated_r_range = self.fortran_core.delhommeau_integrals.default_r_spacing(
183
+ tabulation_nr, tabulation_rmax, self.tabulation_method_index
184
+ )
185
+ self.tabulated_z_range = self.fortran_core.delhommeau_integrals.default_z_spacing(
186
+ tabulation_nz, tabulation_zmin, self.tabulation_method_index
187
+ )
188
+ self.tabulated_integrals = self.fortran_core.delhommeau_integrals.construct_tabulation(
189
+ self.tabulated_r_range, self.tabulated_z_range, tabulation_nb_integration_points
190
+ )
191
+ LOG.debug("Saving tabulation in %s", filepath)
192
+ np.savez_compressed(
193
+ filepath, r_range=self.tabulated_r_range, z_range=self.tabulated_z_range,
194
+ values=self.tabulated_integrals
195
+ )
196
+
197
+ @lru_cache(maxsize=128)
198
+ def find_best_exponential_decomposition(self, dimensionless_omega, dimensionless_wavenumber):
199
+ """Compute the decomposition of a part of the finite water_depth Green function as a sum of exponential functions.
200
+
201
+ Two implementations are available: the legacy Fortran implementation from Nemoh and a newer one written in Python.
202
+ For some still unexplained reasons, the two implementations do not always give the exact same result.
203
+ Until the problem is better understood, the Fortran implementation is the default one, to ensure consistency with Nemoh.
204
+ The Fortran version is also significantly faster...
205
+
206
+ Results are cached.
207
+
208
+ Parameters
209
+ ----------
210
+ dimensionless_omega: float
211
+ dimensionless angular frequency: :math:`kh \\tanh (kh) = \\omega^2 h/g`
212
+ dimensionless_wavenumber: float
213
+ dimensionless wavenumber: :math:`kh`
214
+ method: string, optional
215
+ the implementation that should be used to compute the Prony decomposition
216
+
217
+ Returns
218
+ -------
219
+ Tuple[np.ndarray, np.ndarray]
220
+ the amplitude and growth rates of the exponentials
221
+ """
222
+
223
+ LOG.debug(f"\tCompute Prony decomposition in finite water_depth Green function "
224
+ f"for dimless_omega=%.2e and dimless_wavenumber=%.2e",
225
+ dimensionless_omega, dimensionless_wavenumber)
226
+
227
+ if self.finite_depth_prony_decomposition_method.lower() == 'python':
228
+ # The function that will be approximated.
229
+ @np.vectorize
230
+ def f(x):
231
+ return self.fortran_core.initialize_green_wave.ff(x, dimensionless_omega, dimensionless_wavenumber)
232
+
233
+ # Try different increasing number of exponentials
234
+ for n_exp in range(4, 31, 2):
235
+
236
+ # The coefficients are computed on a resolution of 4*n_exp+1 ...
237
+ X = np.linspace(-0.1, 20.0, 4*n_exp+1)
238
+ a, lamda = exponential_decomposition(X, f(X), n_exp)
239
+
240
+ # ... and they are evaluated on a finer discretization.
241
+ X = np.linspace(-0.1, 20.0, 8*n_exp+1)
242
+ if error_exponential_decomposition(X, f(X), a, lamda) < 1e-4:
243
+ break
244
+
245
+ else:
246
+ LOG.warning("No suitable exponential decomposition has been found"
247
+ "for dimless_omega=%.2e and dimless_wavenumber=%.2e",
248
+ dimensionless_omega, dimensionless_wavenumber)
249
+
250
+ elif self.finite_depth_prony_decomposition_method.lower() == 'fortran':
251
+ lamda, a, nexp = self.fortran_core.old_prony_decomposition.lisc(dimensionless_omega, dimensionless_wavenumber)
252
+ lamda = lamda[:nexp]
253
+ a = a[:nexp]
254
+
255
+ else:
256
+ raise ValueError("Unrecognized method name for the Prony decomposition.")
257
+
258
+ # Add one more exponential function (actually a constant).
259
+ # It is not clear where it comes from exactly in the theory...
260
+ a = np.concatenate([a, np.array([2])])
261
+ lamda = np.concatenate([lamda, np.array([0.0])])
262
+
263
+ return a, lamda
264
+
265
+ def evaluate(self, mesh1, mesh2, free_surface=0.0, water_depth=np.inf, wavenumber=1.0, adjoint_double_layer=True, early_dot_product=True):
266
+ r"""The main method of the class, called by the engine to assemble the influence matrices.
267
+
268
+ Parameters
269
+ ----------
270
+ mesh1: Mesh or CollectionOfMeshes or list of points
271
+ mesh of the receiving body (where the potential is measured)
272
+ if only S is wanted or early_dot_product is False, then only a list of points as an array of shape (n, 3) can be passed.
273
+ mesh2: Mesh or CollectionOfMeshes
274
+ mesh of the source body (over which the source distribution is integrated)
275
+ free_surface: float, optional
276
+ position of the free surface (default: :math:`z = 0`)
277
+ water_depth: float, optional
278
+ constant depth of water (default: :math:`+\infty`)
279
+ wavenumber: float, optional
280
+ wavenumber (default: 1.0)
281
+ adjoint_double_layer: bool, optional
282
+ compute double layer for direct method (F) or adjoint double layer for indirect method (T) matrices (default: True)
283
+ early_dot_product: boolean, optional
284
+ if False, return K as a (n, m, 3) array storing ∫∇G
285
+ if True, return K as a (n, m) array storing ∫∇G·n
286
+
287
+ Returns
288
+ -------
289
+ tuple of numpy arrays
290
+ the matrices :math:`S` and :math:`K`
291
+ """
292
+
293
+ wavenumber = float(wavenumber)
294
+
295
+ if free_surface == np.inf: # No free surface, only a single Rankine source term
296
+
297
+ a_exp, lamda_exp = np.empty(1), np.empty(1) # Dummy arrays that won't actually be used by the fortran code.
298
+
299
+ coeffs = np.array((1.0, 0.0, 0.0))
300
+
301
+ elif water_depth == np.inf:
302
+
303
+ a_exp, lamda_exp = np.empty(1), np.empty(1) # Idem
304
+
305
+ if wavenumber == 0.0:
306
+ coeffs = np.array((1.0, 1.0, 0.0))
307
+ elif wavenumber == np.inf:
308
+ coeffs = np.array((1.0, -1.0, 0.0))
309
+ else:
310
+ coeffs = np.array((1.0, 1.0, 1.0))
311
+
312
+ else: # Finite water_depth
313
+ if wavenumber == 0.0 or wavenumber == np.inf:
314
+ raise NotImplementedError("Zero or infinite frequencies not implemented for finite depth.")
315
+ else:
316
+ a_exp, lamda_exp = self.find_best_exponential_decomposition(
317
+ wavenumber*water_depth*np.tanh(wavenumber*water_depth),
318
+ wavenumber*water_depth,
319
+ )
320
+ coeffs = np.array((1.0, 1.0, 1.0))
321
+
322
+ if isinstance(mesh1, Mesh) or isinstance(mesh1, CollectionOfMeshes):
323
+ collocation_points = mesh1.faces_centers
324
+ nb_collocation_points = mesh1.nb_faces
325
+ if ( adjoint_double_layer == False ):
326
+ early_dot_product_normals = np.zeros((nb_collocation_points, 3)) # Should not be used
327
+ else:
328
+ early_dot_product_normals = mesh1.faces_normals
329
+ elif isinstance(mesh1, np.ndarray) and mesh1.ndim ==2 and mesh1.shape[1] == 3:
330
+ # This is used when computing potential or velocity at given points in postprocessing
331
+ collocation_points = mesh1
332
+ nb_collocation_points = mesh1.shape[0]
333
+ early_dot_product_normals = np.zeros((nb_collocation_points, 3)) # Should not be used
334
+ if ( adjoint_double_layer == False ):
335
+ raise NotImplementedError("Using a list of points as collocation points is not supported in computing adjoint double layer matrices.")
336
+ else:
337
+ raise ValueError(f"Unrecognized input for {self.__class__.__name__}.evaluate")
338
+
339
+ if self.floating_point_precision == "float32":
340
+ dtype = "complex64"
341
+ elif self.floating_point_precision == "float64":
342
+ dtype = "complex128"
343
+ else:
344
+ raise NotImplementedError
345
+
346
+ S = np.empty((nb_collocation_points, mesh2.nb_faces), order="F", dtype=dtype)
347
+ K = np.empty((nb_collocation_points, mesh2.nb_faces, 1 if early_dot_product else 3), order="F", dtype=dtype)
348
+
349
+ # Main call to Fortran code
350
+ self.fortran_core.matrices.build_matrices(
351
+ collocation_points, early_dot_product_normals,
352
+ mesh2.vertices, mesh2.faces + 1,
353
+ mesh2.faces_centers, mesh2.faces_normals,
354
+ mesh2.faces_areas, mesh2.faces_radiuses,
355
+ *mesh2.quadrature_points,
356
+ wavenumber, water_depth,
357
+ coeffs,
358
+ self.tabulation_method_index, self.tabulated_r_range, self.tabulated_z_range, self.tabulated_integrals,
359
+ lamda_exp, a_exp,
360
+ mesh1 is mesh2, adjoint_double_layer,
361
+ S, K
362
+ )
363
+
364
+ if np.any(np.isnan(S)) or np.any(np.isnan(K)):
365
+ raise RuntimeError("Green function returned a NaN in the interaction matrix.\n"
366
+ "It could be due to overlapping panels.")
367
+
368
+ if early_dot_product: K = K.reshape((nb_collocation_points, mesh2.nb_faces))
369
+
370
+ return S, K
371
+
372
+ ################################
373
+
374
+ class XieDelhommeau(Delhommeau):
375
+ """Variant of Nemoh's Green function, more accurate near the free surface.
376
+
377
+ Same arguments and methods as :class:`Delhommeau`.
378
+ """
379
+
380
+ fortran_core_basename = "XieDelhommeau"
File without changes
File without changes
capytaine/io/bemio.py ADDED
@@ -0,0 +1,141 @@
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
+ if bemio_obj.body[i].bem_code == 'WAMIT': # WAMIT coefficients need to be dimensionalized
41
+ from_wamit = True
42
+
43
+ for omega_idx, omega in enumerate(np.sort(bemio_obj.body[i].w)):
44
+
45
+ # DiffractionProblem variable equivalents
46
+ for dir_idx, dir in enumerate(bemio_obj.body[i].wave_dir):
47
+ temp_dict = {}
48
+ temp_dict['body_name'] = bemio_obj.body[i].name
49
+ temp_dict['water_depth'] = bemio_obj.body[i].water_depth
50
+ temp_dict['omega'] = omega
51
+ temp_dict['period'] = 2*np.pi/omega
52
+ temp_dict['rho'] = rho
53
+ temp_dict['g'] = g
54
+ temp_dict['forward_speed'] = 0.0
55
+ temp_dict['wave_direction'] = np.radians(dir)
56
+ temp_dict['influenced_dof'] = dofs
57
+
58
+ if wavenumber or wavelength:
59
+ if temp_dict['water_depth'] == np.inf or omega**2*temp_dict['water_depth']/temp_dict['g'] > 20:
60
+ k = omega**2/temp_dict['g']
61
+ else:
62
+ k = newton(lambda x: x*np.tanh(x) - omega**2*temp_dict['water_depth']/temp_dict['g'], x0=1.0)/temp_dict['water_depth']
63
+
64
+ if wavenumber:
65
+ temp_dict['wavenumber'] = k
66
+
67
+ if wavelength:
68
+ if k == 0.0:
69
+ temp_dict['wavelength'] = np.inf
70
+ else:
71
+ temp_dict['wavelength'] = 2*np.pi/k
72
+
73
+ Fexc = np.empty(shape=bemio_obj.body[i].ex.re[:, dir_idx, omega_idx].shape, dtype=np.complex128)
74
+ if from_wamit:
75
+ Fexc.real = bemio_obj.body[i].ex.re[:, dir_idx, omega_idx] * rho * g
76
+ Fexc.imag = bemio_obj.body[i].ex.im[:, dir_idx, omega_idx] * rho * g
77
+ else:
78
+ Fexc.real = bemio_obj.body[i].ex.re[:, dir_idx, omega_idx]
79
+ Fexc.imag = bemio_obj.body[i].ex.im[:, dir_idx, omega_idx]
80
+ temp_dict['diffraction_force'] = Fexc.flatten()
81
+
82
+ try:
83
+ Fexc_fk = np.empty(shape=bemio_obj.body[i].ex.fk.re[:, dir_idx, omega_idx].shape, dtype=np.complex128)
84
+ if from_wamit:
85
+ Fexc_fk.real = bemio_obj.body[i].ex.fk.re[:, dir_idx, omega_idx] * rho * g
86
+ Fexc_fk.imag = bemio_obj.body[i].ex.fk.im[:, dir_idx, omega_idx] * rho * g
87
+ else:
88
+ Fexc_fk.real = bemio_obj.body[i].ex.fk.re[:, dir_idx, omega_idx]
89
+ Fexc_fk.imag = bemio_obj.body[i].ex.fk.im[:, dir_idx, omega_idx]
90
+ temp_dict['Froude_Krylov_force'] = Fexc_fk.flatten()
91
+
92
+ except AttributeError:
93
+ # LOG.warning('\tNo Froude-Krylov forces found for ' + bemio_obj.body[i].name + ' at ' + str(dir) + \
94
+ # ' degrees (omega = ' + str(omega) + '), replacing with zeros.')
95
+ temp_dict['Froude_Krylov_force'] = np.zeros((bemio_obj.body[i].ex.re[:, dir_idx, omega_idx].size,), dtype=np.complex128)
96
+
97
+ difr_dict.append(temp_dict)
98
+
99
+ # RadiationProblem + Hydrostatics variable equivalents
100
+ for radiating_dof_idx, radiating_dof in enumerate(dofs):
101
+ temp_dict = {}
102
+ temp_dict['body_name'] = bemio_obj.body[i].name
103
+ temp_dict['water_depth'] = bemio_obj.body[i].water_depth
104
+ temp_dict['omega'] = omega
105
+ temp_dict['rho'] = rho
106
+ temp_dict['g'] = g
107
+ temp_dict['forward_speed'] = 0.0
108
+ temp_dict['wave_direction'] = 0.0
109
+ temp_dict['influenced_dof'] = dofs
110
+ temp_dict['radiating_dof'] = radiating_dof
111
+ temp_dict['added_mass'] = bemio_obj.body[i].am.all[radiating_dof_idx, :, omega_idx].flatten()
112
+ temp_dict['radiation_damping'] = bemio_obj.body[i].rd.all[radiating_dof_idx, :, omega_idx].flatten()
113
+
114
+ if from_wamit:
115
+ temp_dict['added_mass'] = temp_dict['added_mass'] * rho
116
+ temp_dict['radiation_damping'] = temp_dict['radiation_damping'] * rho * omega
117
+
118
+ if wavenumber or wavelength:
119
+ if temp_dict['water_depth'] == np.inf or omega**2*temp_dict['water_depth']/temp_dict['g'] > 20:
120
+ k = omega**2/temp_dict['g']
121
+ else:
122
+ k = newton(lambda x: x*np.tanh(x) - omega**2*temp_dict['water_depth']/temp_dict['g'], x0=1.0)/temp_dict['water_depth']
123
+
124
+ if wavenumber:
125
+ temp_dict['wavenumber'] = k
126
+
127
+ if wavelength:
128
+ if k == 0.0:
129
+ temp_dict['wavelength'] = np.inf
130
+ else:
131
+ temp_dict['wavelength'] = 2*np.pi/k
132
+
133
+ rad_dict.append(temp_dict)
134
+
135
+ df = pd.concat([
136
+ pd.DataFrame.from_dict(difr_dict).explode(['influenced_dof', 'diffraction_force', 'Froude_Krylov_force']),
137
+ pd.DataFrame.from_dict(rad_dict).explode(['influenced_dof', 'added_mass', 'radiation_damping'])
138
+ ])
139
+ df = df.astype({'added_mass': np.float64, 'radiation_damping': np.float64, 'diffraction_force': np.complex128, 'Froude_Krylov_force': np.complex128})
140
+
141
+ return df