capytaine 3.0.0a1__cp312-cp312-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-312-darwin.so +0 -0
  17. capytaine/green_functions/Delhommeau_float64.cpython-312-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,353 @@
1
+ # Copyright 2025 Mews Labs
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import importlib
16
+ from typing import Optional, List
17
+
18
+ import numpy as np
19
+
20
+ from capytaine import __version__
21
+ from capytaine.tools.optional_imports import import_optional_dependency
22
+
23
+
24
+ def show_3d(mesh, *, backend=None, **kwargs):
25
+ """Dispatch the 3D viewing to one of the available backends below."""
26
+ backends_functions = {
27
+ "pyvista": show_pyvista,
28
+ "matplotlib": show_matplotlib,
29
+ }
30
+ if backend is not None:
31
+ if backend in backends_functions:
32
+ return backends_functions[backend](mesh, **kwargs)
33
+ else:
34
+ raise NotImplementedError(f"Backend '{backend}' is not implemented.")
35
+ else:
36
+ for backend in backends_functions:
37
+ try:
38
+ return backends_functions[backend](mesh, **kwargs)
39
+ except (NotImplementedError, ImportError):
40
+ pass
41
+ raise NotImplementedError(f"No compatible backend found to show the mesh {mesh}"
42
+ "Consider installing `matplotlib` or `pyvista`.")
43
+
44
+
45
+ def show_pyvista(
46
+ mesh,
47
+ *,
48
+ ghost_meshes=None,
49
+ plotter=None,
50
+ normal_vectors=False,
51
+ display_free_surface=True,
52
+ water_depth=np.inf,
53
+ color_field=None,
54
+ cbar_label="",
55
+ **kwargs
56
+ ) -> Optional["pv.Plotter"]: # noqa: F821
57
+ """
58
+ Visualize the mesh using PyVista.
59
+
60
+ PyVista default keyboards controls: https://docs.pyvista.org/api/plotting/plotting
61
+
62
+ Parameters
63
+ ----------
64
+ mesh : Mesh
65
+ The mesh object to visualize.
66
+ ghost_meshes: List[Mesh], optional
67
+ Additional meshes, shown in transparency
68
+ plotter: pv.Plotter, optional
69
+ If provided, use this PyVista plotter and return it at the end.
70
+ Otherwise a new one is created and the 3D view is displayed at the end.
71
+ normal_vectors: bool, optional
72
+ If True, display normal vector (default: True)
73
+ display_free_surface: bool, optional
74
+ If True, display free surface and if `water_depth` is finite display the sea bottom.
75
+ (default: True)
76
+ water_depth: float, optional
77
+ Where to display the sea bottom if `display_free_surface` is True
78
+ color_field: array of shape (nb_faces, ), optional
79
+ Scalar field to be plot on the mesh.
80
+ cmap: matplotlib colormap, optional
81
+ Colormap to use for scalar field plotting.
82
+ cbar_label: string, optional
83
+ Label for colorbar show color field scale
84
+ kwargs : additional optional arguments
85
+ Additional arguments passed to PyVista's add_mesh methods for customization (e.g. mesh color).
86
+ """
87
+ pv = import_optional_dependency("pyvista")
88
+
89
+ all_meshes_in_scene: List[Mesh] = [mesh] if ghost_meshes is None else [mesh, *ghost_meshes]
90
+ pv_meshes = [m.export_to_pyvista() for m in all_meshes_in_scene]
91
+
92
+ if color_field is not None and isinstance(color_field, np.ndarray):
93
+ acc_faces = 0
94
+ # Split the content of color_fields into the meshes in the scene
95
+ for m in pv_meshes:
96
+ m.cell_data["color_field"] = color_field[acc_faces:acc_faces+m.n_cells]
97
+ acc_faces = acc_faces + m.n_cells
98
+
99
+ if plotter is None:
100
+ default_plotter = True
101
+ plotter = pv.Plotter()
102
+ else:
103
+ default_plotter = False
104
+
105
+ if color_field is not None:
106
+ kwargs.setdefault("scalars", "color_field")
107
+ kwargs.setdefault("scalar_bar_args", {"title": cbar_label})
108
+ plotter.add_mesh(pv_meshes[0], name="hull", show_edges=True, **kwargs)
109
+
110
+ for i_ghost, g_mesh in enumerate(pv_meshes[1:]):
111
+ plotter.add_mesh(
112
+ g_mesh,
113
+ name=f"symmetric_hull_{i_ghost}",
114
+ opacity=0.4,
115
+ show_edges=False,
116
+ **kwargs
117
+ )
118
+
119
+ # NORMALS
120
+ def show_normals():
121
+ mini = mesh.vertices.min()
122
+ maxi = mesh.vertices.max()
123
+ plotter.add_arrows(
124
+ mesh.faces_centers,
125
+ mesh.faces_normals,
126
+ name="normals",
127
+ mag=0.04*(maxi-mini),
128
+ show_scalar_bar=False
129
+ )
130
+
131
+ def toggle_normals():
132
+ nonlocal normal_vectors
133
+ if normal_vectors:
134
+ normal_vectors = False
135
+ plotter.remove_actor('normals')
136
+ else:
137
+ normal_vectors = True
138
+ show_normals()
139
+
140
+ if normal_vectors:
141
+ show_normals()
142
+ plotter.add_key_event("n", lambda : toggle_normals())
143
+
144
+ scene_min = np.min([m.vertices[:, :].min(axis=0) for m in all_meshes_in_scene], axis=0)
145
+ scene_max = np.max([m.vertices[:, :].max(axis=0) for m in all_meshes_in_scene], axis=0)
146
+
147
+ # FREE SURFACE
148
+ def show_free_surface():
149
+ center = (scene_min[:2] + scene_max[:2]) / 2
150
+ diam = 1.1*(scene_max[:2] - scene_min[:2])
151
+ plane = pv.Plane(center=(*center, 0), direction=(0, 0, 1), i_size=diam[0], j_size=diam[1])
152
+ plotter.add_mesh(plane, color="blue", opacity=0.5, name="display_free_surface")
153
+ if water_depth != np.inf:
154
+ plane = pv.Plane(center=(*center, -water_depth), direction=(0, 0, 1), i_size=diam[0], j_size=diam[1])
155
+ plotter.add_mesh(plane, color="brown", opacity=0.5, name="display_sea_bottom")
156
+
157
+
158
+ def toggle_free_surface():
159
+ nonlocal display_free_surface
160
+ if display_free_surface:
161
+ display_free_surface = False
162
+ plotter.remove_actor('display_free_surface')
163
+ if water_depth != np.inf:
164
+ plotter.remove_actor('display_sea_bottom')
165
+ else:
166
+ display_free_surface = True
167
+ show_free_surface()
168
+
169
+ if display_free_surface:
170
+ show_free_surface()
171
+
172
+ plotter.add_key_event("h", lambda : toggle_free_surface())
173
+
174
+ # BOUNDS
175
+ def show_bounds():
176
+ plotter.show_bounds(grid='back', location='outer', n_xlabels=2, n_ylabels=2, n_zlabels=2)
177
+
178
+ bounds = True
179
+ show_bounds()
180
+ def toggle_bounds():
181
+ nonlocal bounds
182
+ if bounds:
183
+ plotter.remove_bounds_axes()
184
+ bounds = False
185
+ else:
186
+ show_bounds()
187
+ plotter.update()
188
+ bounds = True
189
+
190
+
191
+ plotter.add_key_event("b", lambda: toggle_bounds())
192
+
193
+ plotter.add_key_event("T", lambda : plotter.view_xy())
194
+ plotter.add_key_event("B", lambda : plotter.view_xy(negative=True))
195
+ plotter.add_key_event("S", lambda : plotter.view_xz())
196
+ plotter.add_key_event("P", lambda : plotter.view_xz(negative=True))
197
+ plotter.add_key_event("F", lambda : plotter.view_yz())
198
+ plotter.add_key_event("R", lambda : plotter.view_yz(negative=True))
199
+
200
+ view_clipping = {'x': 0, 'y': 0} # 0 = no clipping, +1 clipping one side, -1 clipping other side
201
+ def clipped_mesh():
202
+ nonlocal view_clipping
203
+ clipped_pv_mesh = pv_meshes[0]
204
+ for dir in ['x', 'y']:
205
+ if view_clipping[dir] == 1:
206
+ clipped_pv_mesh = clipped_pv_mesh.clip(dir)
207
+ elif view_clipping[dir] == -1:
208
+ clipped_pv_mesh = clipped_pv_mesh.clip("-" + dir)
209
+ return clipped_pv_mesh
210
+
211
+ def toggle_view_clipping(dir):
212
+ nonlocal view_clipping
213
+ if view_clipping[dir] == 0:
214
+ view_clipping[dir] = +1
215
+ elif view_clipping[dir] == +1:
216
+ view_clipping[dir] = -1
217
+ else:
218
+ view_clipping[dir] = 0
219
+ plotter.add_mesh(clipped_mesh(), name="hull", show_edges=True, **kwargs)
220
+
221
+ plotter.add_key_event("X", lambda : toggle_view_clipping("x"))
222
+ plotter.add_key_event("Y", lambda : toggle_view_clipping("y"))
223
+
224
+ plotter.add_text(
225
+ f"Capytaine version {__version__}\n\n"
226
+ """Keyboard controls:
227
+ b: toggle scale and bounding box
228
+ h: toggle free surface (and sea bottom if water depth was given)
229
+ n: toggle normal vectors
230
+ T,B,P,S,F,R: view [T]op, [B]ottom, [P]ort, [S]tarboard, [F]ront, [R]ear
231
+ X, Y: toggle displaying clipped mesh in x or y direction
232
+ q: exit
233
+ """,
234
+ position="upper_left",
235
+ font_size=10
236
+ )
237
+ plotter.show_axes() # xyz in bottom left corner
238
+
239
+ if default_plotter:
240
+ plotter.show()
241
+ else:
242
+ return plotter
243
+
244
+
245
+ def show_matplotlib(
246
+ mesh,
247
+ *,
248
+ ghost_meshes=None,
249
+ ax=None,
250
+ bounding_box=None,
251
+ normal_vectors=False,
252
+ scale_normal_vector=None,
253
+ color_field=None,
254
+ cmap=None,
255
+ cbar_label=None,
256
+ **kwargs
257
+ ):
258
+ """
259
+ Visualize the mesh using Matplotlib.
260
+
261
+ Parameters
262
+ ----------
263
+ mesh : Mesh
264
+ The mesh object to visualize.
265
+ ghost_meshes: List[Mesh], optional
266
+ Additional meshes. In the matplotlib viewer, they are just merged with the main mesh.
267
+ ax: matplotlib axis
268
+ The 3d axis in which to plot the mesh. If not provided, create a new one.
269
+ bounding_box: tuple[tuple[int]], optional
270
+ Min and max coordinates values to display in each three dimensions.
271
+ normal_vectors: bool, optional
272
+ If True, display normal vector.
273
+ scale_normal_vector: array of shape (nb_faces, ), optional
274
+ Scale separately each of the normal vectors.
275
+ color_field: array of shape (nb_faces, ), optional
276
+ Scalar field to be plot on the mesh (optional).
277
+ cmap: matplotlib colormap, optional
278
+ Colormap to use for scalar field plotting.
279
+ cbar_label: string, optional
280
+ Label for colorbar show color field scale
281
+
282
+ Other parameters are passed to Poly3DCollection.
283
+ """
284
+ matplotlib = import_optional_dependency("matplotlib")
285
+ plt = importlib.import_module("matplotlib.pyplot")
286
+ cm = importlib.import_module("matplotlib.cm")
287
+
288
+ mpl_toolkits = import_optional_dependency("mpl_toolkits", package_name="matplotlib")
289
+ Poly3DCollection = mpl_toolkits.mplot3d.art3d.Poly3DCollection
290
+
291
+ default_axis = ax is None
292
+ if default_axis:
293
+ fig = plt.figure(layout="constrained")
294
+ ax = fig.add_subplot(111, projection="3d")
295
+ ax.set_box_aspect([1, 1, 1]) # Equal aspect ratio
296
+
297
+ all_meshes_in_scene: List[Mesh] = [mesh] if ghost_meshes is None else [mesh, *ghost_meshes]
298
+
299
+ faces = []
300
+ for m in all_meshes_in_scene:
301
+ for face in m.faces:
302
+ vertices = [m.vertices[int(index_vertex), :] for index_vertex in face]
303
+ faces.append(vertices)
304
+
305
+ if color_field is None:
306
+ if 'facecolors' not in kwargs:
307
+ kwargs['facecolors'] = "yellow"
308
+ else:
309
+ if cmap is None:
310
+ cmap = matplotlib.colormaps['coolwarm']
311
+ m = cm.ScalarMappable(cmap=cmap)
312
+ m.set_array([min(color_field), max(color_field)])
313
+ m.set_clim(vmin=min(color_field), vmax=max(color_field))
314
+ colors = m.to_rgba(color_field)
315
+ kwargs['facecolors'] = colors
316
+ kwargs.setdefault("edgecolor", "k")
317
+
318
+ ax.add_collection3d(Poly3DCollection(faces, **kwargs))
319
+
320
+ if color_field is not None:
321
+ cbar = plt.colorbar(m, ax=ax)
322
+ if cbar_label is not None:
323
+ cbar.set_label(cbar_label)
324
+
325
+ # Plot normal vectors.
326
+ if normal_vectors:
327
+ if scale_normal_vector is not None:
328
+ vectors = (scale_normal_vector * mesh.faces_normals.T).T
329
+ else:
330
+ vectors = mesh.faces_normals
331
+ ax.quiver(*zip(*mesh.faces_centers), *zip(*vectors), length=0.2)
332
+
333
+ ax.set_xlabel("x")
334
+ ax.set_ylabel("y")
335
+ ax.set_zlabel("z")
336
+
337
+ if bounding_box is None:
338
+ # auto cube around mesh
339
+ mini = mesh.vertices.min(axis=0)
340
+ maxi = mesh.vertices.max(axis=0)
341
+ center = (mini + maxi) / 2
342
+ radius = (maxi - mini).max() / 2
343
+ ax.set_xlim(center[0] - radius, center[0] + radius)
344
+ ax.set_ylim(center[1] - radius, center[1] + radius)
345
+ ax.set_zlim(center[2] - radius, center[2] + radius)
346
+ else:
347
+ (xmin, xmax), (ymin, ymax), (zmin, zmax) = bounding_box
348
+ ax.set_xlim(xmin, xmax)
349
+ ax.set_ylim(ymin, ymax)
350
+ ax.set_zlim(zmin, zmax)
351
+
352
+ if default_axis:
353
+ plt.show()
@@ -0,0 +1,6 @@
1
+ # Copyright (C) 2017-2019 Matthieu Ancellin
2
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
3
+
4
+ from capytaine.post_pro.rao import rao
5
+ from capytaine.post_pro.impedance import impedance, rao_transfer_function
6
+ from capytaine.post_pro.kochin import compute_kochin
@@ -0,0 +1,85 @@
1
+ """This module implements objects describing a mesh on which the free surface elevation will be computed."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import logging
6
+ from itertools import product
7
+
8
+ import numpy as np
9
+
10
+ from capytaine.meshes.meshes import Mesh
11
+
12
+ LOG = logging.getLogger(__name__)
13
+
14
+
15
+ class FreeSurface():
16
+ """A cartesian mesh on which the free surface elevation will be computed.
17
+
18
+ Has a :code:`mesh` attribute to behave kind of like FloatingBody when
19
+ building of the influence matrix.
20
+
21
+ Parameters
22
+ ----------
23
+ x_range: Tuple[float, float], optional
24
+ extreme values of the mesh in the x direction
25
+ nx: int, optional
26
+ number of cells in the x direction
27
+ y_range: Tuple[float, float], optional
28
+ extreme values of the mesh in the y direction
29
+ ny: int, optional
30
+ number of cells in the y direction
31
+ name: string, optional
32
+ a name for the free surface object
33
+
34
+
35
+ .. todo:: Generalize to non-cartesian meshes.
36
+ In particular, it could be of interest to build meshes having the
37
+ same symmetry as a given floating body to speed up the
38
+ construction of the influence matrix.
39
+
40
+ .. seealso::
41
+
42
+ :meth:`~capytaine.bem.nemoh.Nemoh.get_free_surface_elevation`
43
+ The main function requiring a FreeSurface object.
44
+ """
45
+ def __init__(self, x_range=(-50.0, 50.0), nx=10, y_range=(-50.0, 50.0), ny=10, name=None):
46
+ self.x_range = x_range
47
+ self.nx = nx
48
+ self.y_range = y_range
49
+ self.ny = ny
50
+
51
+ self.name = name
52
+
53
+ self.mesh = self._generate_mesh()
54
+
55
+ def _generate_mesh(self):
56
+ """Generate a 2D cartesian mesh."""
57
+ nodes = np.zeros(((self.nx+1)*(self.ny+1), 3), dtype=float)
58
+ panels = np.zeros((self.nx*self.ny, 4), dtype=int)
59
+
60
+ X = np.linspace(*self.x_range, self.nx+1)
61
+ Y = np.linspace(*self.y_range, self.ny+1)
62
+ for i, (x, y, z) in enumerate(product(X, Y, [0.0])):
63
+ nodes[i, :] = x, y, z
64
+
65
+ for k, (i, j) in enumerate(product(range(0, self.nx), range(0, self.ny))):
66
+ panels[k, :] = (j+i*(self.ny+1),
67
+ (j+1)+i*(self.ny+1),
68
+ (j+1)+(i+1)*(self.ny+1),
69
+ j+(i+1)*(self.ny+1))
70
+
71
+ return Mesh(nodes, panels, name=f"{self.name}_mesh")
72
+
73
+ @property
74
+ def area(self):
75
+ """The total area covered by the mesh."""
76
+ return (np.abs(self.x_range[1] - self.x_range[0])
77
+ * np.abs(self.y_range[1] - self.y_range[0]))
78
+
79
+ def incoming_waves(self, problem: "DiffractionProblem") -> np.ndarray:
80
+ """Free surface elevation of the undisturbed incoming waves
81
+ for a given diffraction problem.
82
+ Kept for legacy, but not recommended for use.
83
+ """
84
+ from capytaine.bem.airy_waves import airy_waves_free_surface_elevation
85
+ return airy_waves_free_surface_elevation(self, problem)
@@ -0,0 +1,92 @@
1
+ """Computation of the impendance matrix."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import logging
6
+
7
+ LOG = logging.getLogger(__name__)
8
+
9
+
10
+ def rao_transfer_function(dataset, dissipation=None, stiffness=None):
11
+ """Complex-valued matrix used for the computation of the RAO.
12
+
13
+ Parameters
14
+ ----------
15
+ dataset: xarray Dataset
16
+ The hydrodynamical dataset.
17
+ This function supposes that variables named 'inertia_matrix' and 'hydrostatic_stiffness' are in the dataset.
18
+ Other variables can be computed by Capytaine, by those two should be manually added to the dataset.
19
+ dissipation: array, optional
20
+ An optional dissipation matrix (e.g. Power Take Off) to be included in the transfer function.
21
+ Default: none.
22
+ stiffness: array, optional
23
+ An optional stiffness matrix (e.g. mooring stiffness) to be included in the transfer function.
24
+ Default: none.
25
+
26
+ Returns
27
+ -------
28
+ xarray DataArray
29
+ The matrix as an array depending of omega and the degrees of freedom.
30
+ """
31
+
32
+ if not hasattr(dataset, 'inertia_matrix'):
33
+ raise AttributeError('Computing the impedance matrix requires an `inertia_matrix` matrix to be defined in the hydrodynamical dataset')
34
+
35
+ if not hasattr(dataset, 'hydrostatic_stiffness'):
36
+ raise AttributeError('Computing the impedance matrix requires an `hydrostatic_stiffness` matrix to be defined in the hydrodynamical dataset')
37
+
38
+ if 'encounter_omega' in dataset.coords:
39
+ omega = dataset.coords['encounter_omega']
40
+ else:
41
+ omega = dataset.coords['omega']
42
+
43
+ # ASSEMBLE MATRICES
44
+ H = (-omega**2*(dataset['inertia_matrix'] + dataset['added_mass'])
45
+ - 1j*omega*dataset['radiation_damping']
46
+ + dataset['hydrostatic_stiffness'])
47
+
48
+ if dissipation is not None:
49
+ H = H - 1j*omega*dissipation
50
+
51
+ if stiffness is not None:
52
+ H = H + stiffness
53
+
54
+ return H
55
+
56
+
57
+ def impedance(dataset, dissipation=None, stiffness=None):
58
+ """Complex-valued mechanical impedance matrix.
59
+ See Falnes for more theoretical details::
60
+
61
+ @book{falnes2002ocean,
62
+ title={Ocean Waves and Oscillating Systems: Linear Interactions Including Wave-Energy Extraction},
63
+ author={Falnes, J.},
64
+ isbn={9781139431934},
65
+ url={https://books.google.com/books?id=bl1FyQjCklgC},
66
+ year={2002},
67
+ publisher={Cambridge University Press}
68
+ }
69
+
70
+ Parameters
71
+ ----------
72
+ dataset: xarray Dataset
73
+ The hydrodynamical dataset.
74
+ This function supposes that variables named 'inertia_matrix' and 'hydrostatic_stiffness' are in the dataset.
75
+ Other variables can be computed by Capytaine, by those two should be manually added to the dataset.
76
+ dissipation: array, optional
77
+ An optional dissipation matrix (e.g. Power Take Off) to be included in the impedance.
78
+ Default: none.
79
+ stiffness: array, optional
80
+ An optional stiffness matrix (e.g. mooring stiffness) to be included in the impedance.
81
+ Default: none.
82
+
83
+ Returns
84
+ -------
85
+ xarray DataArray
86
+ The impedance as an array depending of omega and the degrees of freedom.
87
+ """
88
+ if 'encounter_omega' in dataset.coords:
89
+ omega = dataset.coords['encounter_omega']
90
+ else:
91
+ omega = dataset.coords['omega']
92
+ return 1/(-1j * omega) * rao_transfer_function(dataset, dissipation, stiffness)
@@ -0,0 +1,54 @@
1
+ """Computation of the Kochin function."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
4
+
5
+ import logging
6
+ import numpy as np
7
+
8
+ LOG = logging.getLogger(__name__)
9
+
10
+ def compute_kochin(result, theta, ref_point=(0.0, 0.0)):
11
+ """Compute the far field coefficient
12
+
13
+ Parameters
14
+ ----------
15
+ result: LinearPotentialFlowResult
16
+ solved potential flow problem
17
+ theta: float or 1-dim array of floats
18
+ angles at which the coefficient is computed
19
+ ref_point: couple of float, optional
20
+ point of reference around which the far field coefficient is computed
21
+
22
+ Returns
23
+ -------
24
+ H: same type as theta
25
+ values of the Kochin function
26
+ """
27
+
28
+ if result.forward_speed != 0.0:
29
+ LOG.warning("Kochin functions with forward speed have never been validated.")
30
+
31
+ if result.sources is None:
32
+ raise ValueError(f"""The values of the sources of {result} cannot been found.
33
+ They have not been stored by the solver either because the direct method has been used or the option keep_details=True have not been set.
34
+ Please re-run the resolution with `method='indirect'` and `keep_details=True`.""")
35
+
36
+ k = result.wavenumber
37
+ h = result.water_depth
38
+
39
+ # omega_bar.shape = (nb_faces_including_lid, 2) @ (2, nb_theta)
40
+ omega_bar = (result.body.mesh_including_lid.faces_centers[:, 0:2] - ref_point) @ (np.cos(theta), np.sin(theta))
41
+
42
+ if 0 <= k*h < 20:
43
+ cih = np.cosh(k*(result.body.mesh_including_lid.faces_centers[:, 2]+h))/np.cosh(k*h)
44
+ else:
45
+ cih = np.exp(k*result.body.mesh_including_lid.faces_centers[:, 2])
46
+
47
+ # cih.shape = (nb_faces_including_lid,)
48
+ # omega_bar.T.shape = (nb_theta, nb_faces_including_lid)
49
+ # result.body.mesh.faces_areas.shape = (nb_faces_including_lid,)
50
+ zs = cih * np.exp(-1j * k * omega_bar.T) * result.body.mesh_including_lid.faces_areas
51
+
52
+ # zs.shape = (nb_theta, nb_faces_including_lid)
53
+ # result.sources.shape = (nb_faces_including_lid,)
54
+ return zs @ result.sources/(4*np.pi)
@@ -0,0 +1,60 @@
1
+ """Experimental function to compute the Response Amplitude Operator."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import logging
6
+
7
+ import numpy as np
8
+ import xarray as xr
9
+ from capytaine.post_pro.impedance import rao_transfer_function
10
+
11
+ LOG = logging.getLogger(__name__)
12
+
13
+
14
+ def rao(dataset, wave_direction=None, dissipation=None, stiffness=None):
15
+ """Response Amplitude Operator.
16
+
17
+ Parameters
18
+ ----------
19
+ dataset: xarray Dataset
20
+ The hydrodynamical dataset.
21
+ This function supposes that variables named 'inertia_matrix' and 'hydrostatic_stiffness' are in the dataset.
22
+ Other variables can be computed by Capytaine, by those two should be manually added to the dataset.
23
+ wave_direction: float, optional
24
+ Select a wave directions for the computation. (Not recommended, kept for legacy.)
25
+ Default: all wave directions in the dataset.
26
+ dissipation: array, optional
27
+ An optional dissipation matrix (e.g. Power Take Off) to be included in the RAO.
28
+ Default: none.
29
+ stiffness: array, optional
30
+ An optional stiffness matrix (e.g. mooring stiffness) to be included in the RAO.
31
+ Default: none.
32
+
33
+ Returns
34
+ -------
35
+ xarray DataArray
36
+ The RAO as an array depending of omega and the degree of freedom.
37
+ """
38
+
39
+ # ASSEMBLE MATRICES
40
+ H = rao_transfer_function(dataset, dissipation, stiffness)
41
+ fex = dataset.excitation_force
42
+
43
+ LOG.info("Compute RAO.")
44
+
45
+ # SOLVE LINEAR SYSTEMS
46
+ # Match dimensions of the arrays to be sure to solve the right systems.
47
+ H, fex = xr.broadcast(H, fex, exclude=["radiating_dof", "influenced_dof"])
48
+ H = H.transpose(..., 'radiating_dof', 'influenced_dof')
49
+ fex = fex.transpose(..., 'influenced_dof')
50
+
51
+ if wave_direction is not None: # Legacy behavior for backward compatibility
52
+ H = H.sel(wave_direction=wave_direction)
53
+ fex = fex.sel(wave_direction=wave_direction)
54
+
55
+ # Solve and add coordinates
56
+ rao_dims = [d for d in H.dims if d != 'influenced_dof']
57
+ rao_coords = {c: H.coords[c] for c in H.coords if c != 'influenced_dof'}
58
+ rao = xr.DataArray(np.linalg.solve(H.values, fex.values[..., np.newaxis])[..., 0], coords=rao_coords, dims=rao_dims)
59
+
60
+ return rao
File without changes