capytaine 2.3.1__cp314-cp314-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 (97) 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 +111 -0
  5. capytaine/bem/engines.py +441 -0
  6. capytaine/bem/problems_and_results.py +600 -0
  7. capytaine/bem/solver.py +594 -0
  8. capytaine/bodies/__init__.py +4 -0
  9. capytaine/bodies/bodies.py +1221 -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 +111 -0
  14. capytaine/bodies/predefined/spheres.py +70 -0
  15. capytaine/green_functions/FinGreen3D/.gitignore +1 -0
  16. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
  17. capytaine/green_functions/FinGreen3D/LICENSE +165 -0
  18. capytaine/green_functions/FinGreen3D/Makefile +16 -0
  19. capytaine/green_functions/FinGreen3D/README.md +24 -0
  20. capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
  21. capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
  22. capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
  23. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
  24. capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
  25. capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
  26. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
  27. capytaine/green_functions/__init__.py +2 -0
  28. capytaine/green_functions/abstract_green_function.py +64 -0
  29. capytaine/green_functions/delhommeau.py +507 -0
  30. capytaine/green_functions/hams.py +204 -0
  31. capytaine/green_functions/libs/Delhommeau_float32.cp314-win_amd64.dll.a +0 -0
  32. capytaine/green_functions/libs/Delhommeau_float32.cp314-win_amd64.pyd +0 -0
  33. capytaine/green_functions/libs/Delhommeau_float64.cp314-win_amd64.dll.a +0 -0
  34. capytaine/green_functions/libs/Delhommeau_float64.cp314-win_amd64.pyd +0 -0
  35. capytaine/green_functions/libs/__init__.py +0 -0
  36. capytaine/io/__init__.py +0 -0
  37. capytaine/io/bemio.py +153 -0
  38. capytaine/io/legacy.py +328 -0
  39. capytaine/io/mesh_loaders.py +1086 -0
  40. capytaine/io/mesh_writers.py +692 -0
  41. capytaine/io/meshio.py +38 -0
  42. capytaine/io/wamit.py +479 -0
  43. capytaine/io/xarray.py +668 -0
  44. capytaine/matrices/__init__.py +16 -0
  45. capytaine/matrices/block.py +592 -0
  46. capytaine/matrices/block_toeplitz.py +325 -0
  47. capytaine/matrices/builders.py +89 -0
  48. capytaine/matrices/linear_solvers.py +232 -0
  49. capytaine/matrices/low_rank.py +395 -0
  50. capytaine/meshes/__init__.py +6 -0
  51. capytaine/meshes/clipper.py +465 -0
  52. capytaine/meshes/collections.py +342 -0
  53. capytaine/meshes/geometry.py +409 -0
  54. capytaine/meshes/mesh_like_protocol.py +37 -0
  55. capytaine/meshes/meshes.py +890 -0
  56. capytaine/meshes/predefined/__init__.py +6 -0
  57. capytaine/meshes/predefined/cylinders.py +314 -0
  58. capytaine/meshes/predefined/rectangles.py +261 -0
  59. capytaine/meshes/predefined/spheres.py +62 -0
  60. capytaine/meshes/properties.py +276 -0
  61. capytaine/meshes/quadratures.py +80 -0
  62. capytaine/meshes/quality.py +448 -0
  63. capytaine/meshes/surface_integrals.py +63 -0
  64. capytaine/meshes/symmetric.py +462 -0
  65. capytaine/post_pro/__init__.py +6 -0
  66. capytaine/post_pro/free_surfaces.py +88 -0
  67. capytaine/post_pro/impedance.py +92 -0
  68. capytaine/post_pro/kochin.py +54 -0
  69. capytaine/post_pro/rao.py +60 -0
  70. capytaine/tools/__init__.py +0 -0
  71. capytaine/tools/cache_on_disk.py +26 -0
  72. capytaine/tools/deprecation_handling.py +18 -0
  73. capytaine/tools/lists_of_points.py +52 -0
  74. capytaine/tools/lru_cache.py +49 -0
  75. capytaine/tools/optional_imports.py +27 -0
  76. capytaine/tools/prony_decomposition.py +150 -0
  77. capytaine/tools/symbolic_multiplication.py +149 -0
  78. capytaine/tools/timer.py +66 -0
  79. capytaine/ui/__init__.py +0 -0
  80. capytaine/ui/cli.py +28 -0
  81. capytaine/ui/rich.py +5 -0
  82. capytaine/ui/vtk/__init__.py +3 -0
  83. capytaine/ui/vtk/animation.py +329 -0
  84. capytaine/ui/vtk/body_viewer.py +28 -0
  85. capytaine/ui/vtk/helpers.py +82 -0
  86. capytaine/ui/vtk/mesh_viewer.py +461 -0
  87. capytaine-2.3.1.dist-info/DELVEWHEEL +2 -0
  88. capytaine-2.3.1.dist-info/LICENSE +674 -0
  89. capytaine-2.3.1.dist-info/METADATA +750 -0
  90. capytaine-2.3.1.dist-info/RECORD +97 -0
  91. capytaine-2.3.1.dist-info/WHEEL +4 -0
  92. capytaine-2.3.1.dist-info/entry_points.txt +3 -0
  93. capytaine.libs/libgcc_s_seh-1.dll +0 -0
  94. capytaine.libs/libgfortran-5.dll +0 -0
  95. capytaine.libs/libgomp-1.dll +0 -0
  96. capytaine.libs/libquadmath-0.dll +0 -0
  97. capytaine.libs/libwinpthread-1.dll +0 -0
@@ -0,0 +1,462 @@
1
+ """Special meshes with symmetries, useful to speed up the computations."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import logging
6
+ import reprlib
7
+ from typing import Union, Callable, Iterable
8
+
9
+ import numpy as np
10
+
11
+ from capytaine.meshes.meshes import Mesh
12
+ from capytaine.meshes.collections import CollectionOfMeshes
13
+ from capytaine.meshes.geometry import Axis, Plane, xOy_Plane, Oz_axis, inplace_transformation
14
+
15
+ LOG = logging.getLogger(__name__)
16
+
17
+
18
+ class SymmetricMesh(CollectionOfMeshes):
19
+ def __repr__(self):
20
+ reprer = reprlib.Repr()
21
+ reprer.maxstring = 90
22
+ reprer.maxother = 90
23
+ slice_name = reprer.repr(self._meshes[0])
24
+ if self.name is not None:
25
+ return f"{self.__class__.__name__}({slice_name}, name={self.name})"
26
+ else:
27
+ return f"{self.__class__.__name__}({slice_name})"
28
+
29
+
30
+ class ReflectionSymmetricMesh(SymmetricMesh):
31
+ """A mesh with one vertical symmetry plane.
32
+
33
+ Parameters
34
+ ----------
35
+ half : Mesh or CollectionOfMeshes
36
+ a mesh describing half of the body
37
+ plane : Plane
38
+ the symmetry plane across which the half body is mirrored
39
+ name :str, optional
40
+ a name for the mesh
41
+ """
42
+
43
+ def __init__(self, half: Union[Mesh, CollectionOfMeshes], plane: Plane, name=None):
44
+ assert isinstance(half, Mesh) or isinstance(half, CollectionOfMeshes)
45
+ assert isinstance(plane, Plane)
46
+ assert plane.normal[2] == 0, "Only vertical reflection planes are supported in ReflectionSymmetry classes."
47
+
48
+ other_half = half.mirrored(plane, name=f"mirrored_of_{half.name}")
49
+
50
+ if name is None:
51
+ name = f"reflection_of_{half.name}"
52
+
53
+ self.plane = plane.copy()
54
+
55
+ super().__init__((half, other_half), name=name)
56
+
57
+ if self.name is not None:
58
+ LOG.debug(f"New mirror symmetric mesh: {self.name}.")
59
+ else:
60
+ LOG.debug(f"New mirror symmetric mesh.")
61
+
62
+ def __str__(self):
63
+ return f"{self.__class__.__name__}({self.half.__short_str__()}, plane={self.plane}, name=\"{self.name}\")"
64
+
65
+ def __repr__(self):
66
+ return f"{self.__class__.__name__}({self.half}, plane={self.plane}, name=\"{self.name}\")"
67
+
68
+ def __rich_repr__(self):
69
+ yield self.half
70
+ yield "plane", self.plane
71
+ yield "name", self.name
72
+
73
+ @property
74
+ def half(self):
75
+ return self[0]
76
+
77
+ def tree_view(self, fold_symmetry=True, **kwargs):
78
+ if fold_symmetry:
79
+ return (self.__short_str__() + '\n' + ' ├─' + self.half.tree_view().replace('\n', '\n │ ') + '\n'
80
+ + f" └─mirrored copy of the above {self.half.__short_str__()}")
81
+ else:
82
+ return CollectionOfMeshes.tree_view(self, **kwargs)
83
+
84
+ def __deepcopy__(self, *args):
85
+ return ReflectionSymmetricMesh(self.half.copy(), self.plane, name=self.name)
86
+
87
+ def join_meshes(*meshes, name=None, return_masks=False):
88
+ assert all(isinstance(mesh, ReflectionSymmetricMesh) for mesh in meshes), \
89
+ "Only meshes with the same symmetry can be joined together."
90
+ assert all(meshes[0].plane == mesh.plane for mesh in meshes), \
91
+ "Only reflection symmetric meshes with the same reflection plane can be joined together."
92
+ if not return_masks:
93
+ name = name=f"half_of_{name}" if name is not None else None
94
+ half_mesh = meshes[0].half.join_meshes(
95
+ *(mesh.half for mesh in meshes[1:]),
96
+ name=name, return_masks=False
97
+ )
98
+ return ReflectionSymmetricMesh(half_mesh, plane=meshes[0].plane, name=name)
99
+ else:
100
+ name = name=f"half_of_{name}" if name is not None else None
101
+ half_mesh, half_masks = meshes[0].half.join_meshes(
102
+ *(mesh.half for mesh in meshes[1:]),
103
+ name=name, return_masks=True
104
+ )
105
+ masks = [np.concatenate([half_mask, half_mask]) for half_mask in half_masks]
106
+ joined = ReflectionSymmetricMesh(half_mesh, plane=meshes[0].plane, name=name)
107
+ return joined, masks
108
+
109
+ @inplace_transformation
110
+ def translate(self, vector):
111
+ self.plane.translate(vector)
112
+ CollectionOfMeshes.translate(self, vector)
113
+ return self
114
+
115
+ @inplace_transformation
116
+ def rotate(self, axis: Axis, angle: float):
117
+ self.plane.rotate(axis, angle)
118
+ CollectionOfMeshes.rotate(self, axis, angle)
119
+ return self
120
+
121
+ @inplace_transformation
122
+ def mirror(self, plane: Plane):
123
+ self.plane.mirror(plane)
124
+ CollectionOfMeshes.mirror(self, plane)
125
+ return self
126
+
127
+ def generate_lid(self, z=0.0, faces_max_radius=None, name=None):
128
+ if name is None:
129
+ name = "lid for {}".format(self.name)
130
+ return ReflectionSymmetricMesh(self.half.generate_lid(z, faces_max_radius), self.plane, name=name)
131
+
132
+ def extract_lid(self, plane=xOy_Plane):
133
+ hull, lid = self.half.extract_lid(plane)
134
+ return ReflectionSymmetricMesh(hull, self.plane), ReflectionSymmetricMesh(lid, self.plane)
135
+
136
+
137
+ class TranslationalSymmetricMesh(SymmetricMesh):
138
+ """A mesh with a repeating pattern by translation.
139
+
140
+ Parameters
141
+ ----------
142
+ mesh_slice : Mesh or CollectionOfMeshes
143
+ the pattern that will be repeated to form the whole body
144
+ translation : array(3)
145
+ the vector of the translation
146
+ nb_repetitions : int, optional
147
+ the number of repetitions of the pattern (excluding the original one, default: 1)
148
+ name : str, optional
149
+ a name for the mesh
150
+ """
151
+
152
+ def __init__(self, mesh_slice: Union[Mesh, CollectionOfMeshes], translation, nb_repetitions=1, name=None):
153
+ assert isinstance(mesh_slice, Mesh) or isinstance(mesh_slice, CollectionOfMeshes)
154
+ assert isinstance(nb_repetitions, int)
155
+ assert nb_repetitions >= 1
156
+
157
+ translation = np.asarray(translation).copy()
158
+ assert translation.shape == (3,)
159
+ assert translation[2] == 0 # Only horizontal translation are supported.
160
+
161
+ slices = [mesh_slice]
162
+ for i in range(1, nb_repetitions+1):
163
+ slices.append(mesh_slice.translated(vector=i*translation, name=f"repetition_{i}_of_{mesh_slice.name}"))
164
+
165
+ if name is None:
166
+ name = f"translation_of_{mesh_slice.name}"
167
+
168
+ self.translation = translation
169
+
170
+ super().__init__(slices, name=name)
171
+
172
+ if self.name is not None:
173
+ LOG.debug(f"New translation symmetric mesh: {self.name}.")
174
+ else:
175
+ LOG.debug(f"New translation symmetric mesh.")
176
+
177
+ @property
178
+ def first_slice(self):
179
+ return self[0]
180
+
181
+ def __str__(self):
182
+ return f"{self.__class__.__name__}({self.first_slice.__short_str__()}, translation={self.translation}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
183
+
184
+ def __repr__(self):
185
+ return f"{self.__class__.__name__}({self.first_slice}, translation={self.translation}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
186
+
187
+ def __rich_repr__(self):
188
+ yield self.first_slice
189
+ yield "translation", self.translation
190
+ yield "nb_repetitions", len(self)-1
191
+ yield "name", self.name
192
+
193
+ def tree_view(self, fold_symmetry=True, **kwargs):
194
+ if fold_symmetry:
195
+ return (self.__short_str__() + '\n' + ' ├─' + self.first_slice.tree_view().replace('\n', '\n │ ') + '\n'
196
+ + f" └─{len(self)-1} translated copies of the above {self.first_slice.__short_str__()}")
197
+ else:
198
+ return CollectionOfMeshes.tree_view(self, **kwargs)
199
+
200
+ def __deepcopy__(self, *args):
201
+ return TranslationalSymmetricMesh(self.first_slice.copy(), self.translation, nb_repetitions=len(self) - 1, name=self.name)
202
+
203
+ @inplace_transformation
204
+ def translate(self, vector):
205
+ CollectionOfMeshes.translate(self, vector)
206
+ return self
207
+
208
+ @inplace_transformation
209
+ def rotate(self, axis: Axis, angle: float):
210
+ self.translation = axis.rotate_vectors([self.translation], angle)[0, :]
211
+ CollectionOfMeshes.rotate(self, axis, angle)
212
+ return self
213
+
214
+ @inplace_transformation
215
+ def mirror(self, plane: Plane):
216
+ self.translation -= 2 * (self.translation @ plane.normal) * plane.normal
217
+ CollectionOfMeshes.mirror(self, plane)
218
+ return self
219
+
220
+ def join_meshes(*meshes, name=None, return_masks=False):
221
+ assert all(isinstance(mesh, TranslationalSymmetricMesh) for mesh in meshes), \
222
+ "Only meshes with the same symmetry can be joined together."
223
+ assert all(np.allclose(meshes[0].translation, mesh.translation) for mesh in meshes), \
224
+ "Only translation symmetric meshes with the same translation vector can be joined together."
225
+ assert all(len(meshes[0]) == len(mesh) for mesh in meshes), \
226
+ "Only symmetric meshes with the same number of elements can be joined together."
227
+ if not return_masks:
228
+ strip_name = f"strip_of_{name}" if name is not None else None
229
+ mesh_strip = meshes[0].first_slice.join_meshes(
230
+ *(mesh.first_slice for mesh in meshes[1:]),
231
+ name=strip_name,
232
+ return_masks=False
233
+ )
234
+ return TranslationalSymmetricMesh(
235
+ mesh_strip,
236
+ translation=meshes[0].translation,
237
+ nb_repetitions=len(meshes[0]) - 1,
238
+ name=name
239
+ )
240
+ else:
241
+ strip_name = f"strip_of_{name}" if name is not None else None
242
+ mesh_strip, strip_masks = meshes[0].first_slice.join_meshes(
243
+ *(mesh.first_slice for mesh in meshes[1:]),
244
+ name=strip_name,
245
+ return_masks=True
246
+ )
247
+ joined = TranslationalSymmetricMesh(
248
+ mesh_strip,
249
+ translation=meshes[0].translation,
250
+ nb_repetitions=len(meshes[0]) - 1,
251
+ name=name
252
+ )
253
+ masks = [np.concatenate([
254
+ strip_mask for _ in range(len(meshes[0]))
255
+ ]) for strip_mask in strip_masks]
256
+ return joined, masks
257
+
258
+
259
+ def build_regular_array_of_meshes(base_mesh, distance, nb_bodies):
260
+ """Create an array of objects using TranslationalSymmetries.
261
+
262
+ Parameters
263
+ ----------
264
+ base_mesh : Mesh or CollectionOfMeshes or SymmetricMesh
265
+ The mesh to duplicate to create the array
266
+ distance : float
267
+ Center-to-center distance between objects in the array
268
+ nb_bodies : couple of ints
269
+ Number of objects in the x and y directions.
270
+
271
+ Returns
272
+ -------
273
+ TranslationalSymmetricMesh
274
+ """
275
+ if nb_bodies[0] == 1:
276
+ line = base_mesh
277
+ else:
278
+ line = TranslationalSymmetricMesh(base_mesh, translation=(distance, 0.0, 0.0), nb_repetitions=nb_bodies[0] - 1,
279
+ name=f'line_of_{base_mesh.name}')
280
+ if nb_bodies[1] == 1:
281
+ array = line
282
+ else:
283
+ array = TranslationalSymmetricMesh(line, translation=(0.0, distance, 0.0), nb_repetitions=nb_bodies[1] - 1,
284
+ name=f'array_of_{base_mesh.name}')
285
+ return array
286
+
287
+
288
+ class AxialSymmetricMesh(SymmetricMesh):
289
+ """A mesh with a repeating pattern by rotation.
290
+
291
+ Parameters
292
+ ----------
293
+ mesh_slice : Mesh or CollectionOfMeshes
294
+ the pattern that will be repeated to form the whole body
295
+ axis : Axis, optional
296
+ symmetry axis
297
+ nb_repetitions : int, optional
298
+ the number of repetitions of the pattern (excluding the original one, default: 1)
299
+ name : str, optional
300
+ a name for the mesh
301
+ """
302
+ def __init__(self, mesh_slice: Union[Mesh, CollectionOfMeshes], axis: Axis=Oz_axis, nb_repetitions: int=1, name=None):
303
+ assert isinstance(mesh_slice, Mesh) or isinstance(mesh_slice, CollectionOfMeshes)
304
+ assert isinstance(nb_repetitions, int)
305
+ assert nb_repetitions >= 1
306
+ assert isinstance(axis, Axis)
307
+
308
+ slices = [mesh_slice]
309
+ for i in range(1, nb_repetitions+1):
310
+ slices.append(mesh_slice.rotated(axis, angle=2*i*np.pi/(nb_repetitions+1),
311
+ name=f"rotation_{i}_of_{mesh_slice.name}"))
312
+
313
+ if name is None:
314
+ name = f"rotation_of_{mesh_slice.name}"
315
+
316
+ self.axis = axis.copy()
317
+
318
+ super().__init__(slices, name=name)
319
+
320
+ if not axis.is_parallel_to(Oz_axis):
321
+ LOG.warning(f"{self.name} is an axi-symmetric mesh along a non vertical axis.")
322
+
323
+ if self.name is not None:
324
+ LOG.debug(f"New rotation symmetric mesh: {self.name}.")
325
+ else:
326
+ LOG.debug(f"New rotation symmetric mesh.")
327
+
328
+ @staticmethod
329
+ def from_profile(profile: Union[Callable, Iterable[float]],
330
+ z_range: Iterable[float]=np.linspace(-5, 0, 20),
331
+ axis: Axis=Oz_axis,
332
+ nphi: int=20,
333
+ name=None):
334
+ """Return a floating body using the axial symmetry.
335
+ The shape of the body can be defined either with a function defining the profile as [f(z), 0, z] for z in z_range.
336
+ Alternatively, the profile can be defined as a list of points.
337
+ The number of vertices along the vertical direction is len(z_range) in the first case and profile.shape[0] in the second case.
338
+
339
+ Parameters
340
+ ----------
341
+ profile : function(float → float) or array(N, 3)
342
+ define the shape of the body either as a function or a list of points.
343
+ z_range: array(N), optional
344
+ used only if the profile is defined as a function.
345
+ axis : Axis
346
+ symmetry axis
347
+ nphi : int, optional
348
+ number of vertical slices forming the body
349
+ name : str, optional
350
+ name of the generated body (optional)
351
+
352
+ Returns
353
+ -------
354
+ AxialSymmetricMesh
355
+ the generated mesh
356
+ """
357
+
358
+ if name is None:
359
+ name = "axisymmetric_mesh"
360
+
361
+ if callable(profile):
362
+ z_range = np.asarray(z_range)
363
+ x_values = [profile(z) for z in z_range]
364
+ profile_array = np.stack([x_values, np.zeros(len(z_range)), z_range]).T
365
+ else:
366
+ profile_array = np.asarray(profile)
367
+
368
+ assert len(profile_array.shape) == 2
369
+ assert profile_array.shape[1] == 3
370
+
371
+ n = profile_array.shape[0]
372
+ angle = 2 * np.pi / nphi
373
+
374
+ nodes_slice = np.concatenate([profile_array, axis.rotate_points(profile_array, angle)])
375
+ faces_slice = np.array([[i, i+n, i+n+1, i+1] for i in range(n-1)])
376
+ body_slice = Mesh(nodes_slice, faces_slice, name=f"slice_of_{name}")
377
+ body_slice.merge_duplicates()
378
+ body_slice.heal_triangles()
379
+
380
+ return AxialSymmetricMesh(body_slice, axis=axis, nb_repetitions=nphi - 1, name=name)
381
+
382
+ @property
383
+ def first_slice(self):
384
+ return self[0]
385
+
386
+ def __str__(self):
387
+ return f"{self.__class__.__name__}({self.first_slice.__short_str__()}, axis={self.axis}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
388
+
389
+ def __repr__(self):
390
+ return f"{self.__class__.__name__}({self.first_slice}, axis={self.axis}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
391
+
392
+ def __rich_repr__(self):
393
+ yield self.first_slice
394
+ yield "axis", self.axis
395
+ yield "nb_repetitions", len(self)-1
396
+ yield "name", self.name
397
+
398
+ def tree_view(self, fold_symmetry=True, **kwargs):
399
+ if fold_symmetry:
400
+ return (self.__short_str__() + '\n' + ' ├─' + self.first_slice.tree_view().replace('\n', '\n │ ') + '\n'
401
+ + f" └─{len(self)-1} rotated copies of the above {self.first_slice.__short_str__()}")
402
+ else:
403
+ return CollectionOfMeshes.tree_view(self, **kwargs)
404
+
405
+ def __deepcopy__(self, *args):
406
+ return AxialSymmetricMesh(self.first_slice.copy(), axis=self.axis.copy(), nb_repetitions=len(self) - 1, name=self.name)
407
+
408
+ def join_meshes(*meshes, name=None, return_masks=False):
409
+ assert all(isinstance(mesh, AxialSymmetricMesh) for mesh in meshes), \
410
+ "Only meshes with the same symmetry can be joined together."
411
+ assert all(meshes[0].axis == mesh.axis for mesh in meshes), \
412
+ "Only axisymmetric meshes with the same symmetry axis can be joined together."
413
+ assert all(len(meshes[0]) == len(mesh) for mesh in meshes), \
414
+ "Only axisymmetric meshes with the same number of elements can be joined together."
415
+ if not return_masks:
416
+ slice_name = f"slice_of_{name}" if name is not None else None
417
+ mesh_slice = meshes[0].first_slice.join_meshes(
418
+ *(mesh.first_slice for mesh in meshes[1:]),
419
+ name=slice_name,
420
+ return_masks=False
421
+ )
422
+ return AxialSymmetricMesh(
423
+ mesh_slice,
424
+ axis=meshes[0].axis,
425
+ nb_repetitions=len(meshes[0]) - 1,
426
+ name=name
427
+ )
428
+ else:
429
+ slice_name = f"slice_of_{name}" if name is not None else None
430
+ mesh_slice, slice_masks = meshes[0].first_slice.join_meshes(
431
+ *(mesh.first_slice for mesh in meshes[1:]),
432
+ name=slice_name,
433
+ return_masks=True
434
+ )
435
+ joined = AxialSymmetricMesh(
436
+ mesh_slice,
437
+ axis=meshes[0].axis,
438
+ nb_repetitions=len(meshes[0]) - 1,
439
+ name=name
440
+ )
441
+ masks = [np.concatenate([
442
+ slice_mask for _ in range(len(meshes[0]))
443
+ ]) for slice_mask in slice_masks]
444
+ return joined, masks
445
+
446
+ @inplace_transformation
447
+ def translate(self, vector):
448
+ self.axis.translate(vector)
449
+ CollectionOfMeshes.translate(self, vector)
450
+ return self
451
+
452
+ @inplace_transformation
453
+ def rotate(self, other_axis: Axis, angle: float):
454
+ self.axis.rotate(other_axis, angle)
455
+ CollectionOfMeshes.rotate(self, other_axis, angle)
456
+ return self
457
+
458
+ @inplace_transformation
459
+ def mirror(self, plane: Plane):
460
+ self.axis.mirror(plane)
461
+ CollectionOfMeshes.mirror(self, plane)
462
+ return self
@@ -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,88 @@
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
+ if name is None:
52
+ self.name = f"free_surface_{next(Mesh._ids)}"
53
+ else:
54
+ self.name = name
55
+
56
+ self.mesh = self._generate_mesh()
57
+
58
+ def _generate_mesh(self):
59
+ """Generate a 2D cartesian mesh."""
60
+ nodes = np.zeros(((self.nx+1)*(self.ny+1), 3), dtype=float)
61
+ panels = np.zeros((self.nx*self.ny, 4), dtype=int)
62
+
63
+ X = np.linspace(*self.x_range, self.nx+1)
64
+ Y = np.linspace(*self.y_range, self.ny+1)
65
+ for i, (x, y, z) in enumerate(product(X, Y, [0.0])):
66
+ nodes[i, :] = x, y, z
67
+
68
+ for k, (i, j) in enumerate(product(range(0, self.nx), range(0, self.ny))):
69
+ panels[k, :] = (j+i*(self.ny+1),
70
+ (j+1)+i*(self.ny+1),
71
+ (j+1)+(i+1)*(self.ny+1),
72
+ j+(i+1)*(self.ny+1))
73
+
74
+ return Mesh(nodes, panels, name=f"{self.name}_mesh")
75
+
76
+ @property
77
+ def area(self):
78
+ """The total area covered by the mesh."""
79
+ return (np.abs(self.x_range[1] - self.x_range[0])
80
+ * np.abs(self.y_range[1] - self.y_range[0]))
81
+
82
+ def incoming_waves(self, problem: "DiffractionProblem") -> np.ndarray:
83
+ """Free surface elevation of the undisturbed incoming waves
84
+ for a given diffraction problem.
85
+ Kept for legacy, but not recommended for use.
86
+ """
87
+ from capytaine.bem.airy_waves import airy_waves_free_surface_elevation
88
+ 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)