capytaine 3.0.0a1__cp314-cp314-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-314-darwin.so +0 -0
- capytaine/green_functions/Delhommeau_float64.cpython-314-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 +6 -0
- 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
|