capytaine 2.3.1__cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_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 (93) hide show
  1. capytaine/__about__.py +16 -0
  2. capytaine/__init__.py +36 -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.cpython-312-x86_64-linux-gnu.so +0 -0
  32. capytaine/green_functions/libs/Delhommeau_float64.cpython-312-x86_64-linux-gnu.so +0 -0
  33. capytaine/green_functions/libs/__init__.py +0 -0
  34. capytaine/io/__init__.py +0 -0
  35. capytaine/io/bemio.py +153 -0
  36. capytaine/io/legacy.py +328 -0
  37. capytaine/io/mesh_loaders.py +1086 -0
  38. capytaine/io/mesh_writers.py +692 -0
  39. capytaine/io/meshio.py +38 -0
  40. capytaine/io/wamit.py +479 -0
  41. capytaine/io/xarray.py +668 -0
  42. capytaine/matrices/__init__.py +16 -0
  43. capytaine/matrices/block.py +592 -0
  44. capytaine/matrices/block_toeplitz.py +325 -0
  45. capytaine/matrices/builders.py +89 -0
  46. capytaine/matrices/linear_solvers.py +232 -0
  47. capytaine/matrices/low_rank.py +395 -0
  48. capytaine/meshes/__init__.py +6 -0
  49. capytaine/meshes/clipper.py +465 -0
  50. capytaine/meshes/collections.py +342 -0
  51. capytaine/meshes/geometry.py +409 -0
  52. capytaine/meshes/mesh_like_protocol.py +37 -0
  53. capytaine/meshes/meshes.py +890 -0
  54. capytaine/meshes/predefined/__init__.py +6 -0
  55. capytaine/meshes/predefined/cylinders.py +314 -0
  56. capytaine/meshes/predefined/rectangles.py +261 -0
  57. capytaine/meshes/predefined/spheres.py +62 -0
  58. capytaine/meshes/properties.py +276 -0
  59. capytaine/meshes/quadratures.py +80 -0
  60. capytaine/meshes/quality.py +448 -0
  61. capytaine/meshes/surface_integrals.py +63 -0
  62. capytaine/meshes/symmetric.py +462 -0
  63. capytaine/post_pro/__init__.py +6 -0
  64. capytaine/post_pro/free_surfaces.py +88 -0
  65. capytaine/post_pro/impedance.py +92 -0
  66. capytaine/post_pro/kochin.py +54 -0
  67. capytaine/post_pro/rao.py +60 -0
  68. capytaine/tools/__init__.py +0 -0
  69. capytaine/tools/cache_on_disk.py +26 -0
  70. capytaine/tools/deprecation_handling.py +18 -0
  71. capytaine/tools/lists_of_points.py +52 -0
  72. capytaine/tools/lru_cache.py +49 -0
  73. capytaine/tools/optional_imports.py +27 -0
  74. capytaine/tools/prony_decomposition.py +150 -0
  75. capytaine/tools/symbolic_multiplication.py +149 -0
  76. capytaine/tools/timer.py +66 -0
  77. capytaine/ui/__init__.py +0 -0
  78. capytaine/ui/cli.py +28 -0
  79. capytaine/ui/rich.py +5 -0
  80. capytaine/ui/vtk/__init__.py +3 -0
  81. capytaine/ui/vtk/animation.py +329 -0
  82. capytaine/ui/vtk/body_viewer.py +28 -0
  83. capytaine/ui/vtk/helpers.py +82 -0
  84. capytaine/ui/vtk/mesh_viewer.py +461 -0
  85. capytaine-2.3.1.dist-info/LICENSE +674 -0
  86. capytaine-2.3.1.dist-info/METADATA +750 -0
  87. capytaine-2.3.1.dist-info/RECORD +93 -0
  88. capytaine-2.3.1.dist-info/WHEEL +6 -0
  89. capytaine-2.3.1.dist-info/entry_points.txt +3 -0
  90. capytaine.libs/libgfortran-83c28eba.so.5.0.0 +0 -0
  91. capytaine.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
  92. capytaine.libs/libmvec-2-583a17db.28.so +0 -0
  93. capytaine.libs/libquadmath-2284e583.so.0.0.0 +0 -0
@@ -0,0 +1,409 @@
1
+ """Tools to describe geometric objects in 3D.
2
+ Based on meshmagick <https://github.com/LHEEA/meshmagick> by François Rongère.
3
+ """
4
+ # Copyright (C) 2017-2019 Matthieu Ancellin, based on the work of François Rongère
5
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
6
+
7
+ from abc import ABC, abstractmethod
8
+ from capytaine.tools.deprecation_handling import _get_water_depth
9
+
10
+ import numpy as np
11
+
12
+ e_x = np.array((1, 0, 0))
13
+ e_y = np.array((0, 1, 0))
14
+ e_z = np.array((0, 0, 1))
15
+
16
+
17
+ ###########################################
18
+ # DECORATOR FOR INPLACE TRANSFORMATIONS #
19
+ ###########################################
20
+
21
+ def inplace_transformation(inplace_function):
22
+ """Decorator for methods transforming 3D objects:
23
+ * Add the optional argument `inplace` to return a new object instead of doing the transformation in place.
24
+ * If the object has properties cached in an "__internals__" dict, they are deleted.
25
+ """
26
+ def enhanced_inplace_function(self, *args, inplace=True, name=None, **kwargs):
27
+ if not inplace:
28
+ object3d = self.copy(name=name)
29
+ else:
30
+ object3d = self
31
+ inplace_function(object3d, *args, **kwargs)
32
+ if hasattr(object3d, '__internals__'):
33
+ object3d.__internals__.clear()
34
+ return object3d
35
+ return enhanced_inplace_function
36
+
37
+
38
+ ##############################
39
+ # ABSTRACT 3D OBJECT CLASS #
40
+ ##############################
41
+
42
+ class Abstract3DObject(ABC):
43
+ """Abstract class for 3d objects that can be transformed in 3d.
44
+ The child classes have to define `mirror`, `rotate` and `translate`,
45
+ then more routines such as `translate_x` and `translated` are automatically available."""
46
+
47
+ @abstractmethod
48
+ def translate(self, vector):
49
+ pass
50
+
51
+ @abstractmethod
52
+ def rotate(self, axis, angle):
53
+ pass
54
+
55
+ @abstractmethod
56
+ def mirror(self, plane):
57
+ pass
58
+
59
+ @inplace_transformation
60
+ def translate_x(self, tx):
61
+ return self.translate((tx, 0., 0.))
62
+
63
+ @inplace_transformation
64
+ def translate_y(self, ty):
65
+ return self.translate((0., ty, 0.))
66
+
67
+ @inplace_transformation
68
+ def translate_z(self, tz):
69
+ return self.translate((0., 0., tz))
70
+
71
+ @inplace_transformation
72
+ def translate_point_to_point(self, point_a, point_b):
73
+ return self.translate(np.asarray(point_b) - np.asarray(point_a))
74
+
75
+ @inplace_transformation
76
+ def rotate_x(self, thetax):
77
+ return self.rotate(Ox_axis, thetax)
78
+
79
+ @inplace_transformation
80
+ def rotate_y(self, thetay):
81
+ return self.rotate(Oy_axis, thetay)
82
+
83
+ @inplace_transformation
84
+ def rotate_z(self, thetaz):
85
+ return self.rotate(Oz_axis, thetaz)
86
+
87
+ @inplace_transformation
88
+ def rotate_around_center_to_align_vectors(self, center, vec1, vec2):
89
+ """Rotate self such that if vec1 is in self, then it will point in the same direction as vec2."""
90
+ vec1 = np.asarray(vec1)
91
+ vec2 = np.asarray(vec2)
92
+ if parallel_vectors_with_same_direction(vec1, vec2):
93
+ return self
94
+ else:
95
+ if parallel_vectors(vec1, vec2):
96
+ if parallel_vectors(vec1, e_x):
97
+ axis = Axis(vector=np.cross(vec1, e_y), point=center)
98
+ else:
99
+ axis = Axis(vector=np.cross(vec1, e_x), point=center)
100
+ return self.rotate(axis, np.pi)
101
+ else:
102
+ axis = Axis(vector=np.cross(vec1, vec2), point=center)
103
+ return self.rotate(axis, np.arccos(np.dot(vec1, vec2)))
104
+
105
+ def translated(self, *args, **kwargs):
106
+ return self.translate(*args, inplace=False, **kwargs)
107
+
108
+ def rotated(self, *args, **kwargs):
109
+ return self.rotate(*args, inplace=False, **kwargs)
110
+
111
+ def mirrored(self, *args, **kwargs):
112
+ return self.mirror(*args, inplace=False, **kwargs)
113
+
114
+ def translated_x(self, *args, **kwargs):
115
+ return self.translate_x(*args, inplace=False, **kwargs)
116
+
117
+ def translated_y(self, *args, **kwargs):
118
+ return self.translate_y(*args, inplace=False, **kwargs)
119
+
120
+ def translated_z(self, *args, **kwargs):
121
+ return self.translate_z(*args, inplace=False, **kwargs)
122
+
123
+ def translated_point_to_point(self, *args, **kwargs):
124
+ return self.translate_point_to_point(*args, inplace=False, **kwargs)
125
+
126
+ def rotated_x(self, *args, **kwargs):
127
+ return self.rotate_x(*args, inplace=False, **kwargs)
128
+
129
+ def rotated_y(self, *args, **kwargs):
130
+ return self.rotate_y(*args, inplace=False, **kwargs)
131
+
132
+ def rotated_z(self, *args, **kwargs):
133
+ return self.rotate_z(*args, inplace=False, **kwargs)
134
+
135
+ def rotated_around_center_to_align_vectors(self, *args, **kwargs):
136
+ return self.rotate_around_center_to_align_vectors(*args, inplace=False, **kwargs)
137
+
138
+
139
+ class ClippableMixin(ABC):
140
+ """Abstract base class for object that can be clipped.
141
+ The child classes should implement a `clip` method, then this abstract
142
+ class will append the new methods `clipped`, `keep_immersed_part` and
143
+ `immersed_part`, all based on `clip`.
144
+ """
145
+
146
+ @abstractmethod
147
+ def clip(self, plane):
148
+ pass
149
+
150
+ def clipped(self, plane, **kwargs):
151
+ # Same API as for the other transformations
152
+ return self.clip(plane, inplace=False, **kwargs)
153
+
154
+ @inplace_transformation
155
+ def keep_immersed_part(self, free_surface=0.0, *, sea_bottom=None, water_depth=None):
156
+ self.clip(Plane(normal=(0, 0, 1), point=(0, 0, free_surface)))
157
+ water_depth = _get_water_depth(free_surface, water_depth, sea_bottom,
158
+ default_water_depth=np.inf)
159
+ if water_depth < np.inf:
160
+ self.clip(Plane(normal=(0, 0, -1), point=(0, 0, free_surface-water_depth)))
161
+ return self
162
+
163
+ def immersed_part(self, free_surface=0.0, *, sea_bottom=None, water_depth=None):
164
+ return self.keep_immersed_part(free_surface, inplace=False, name=self.name,
165
+ sea_bottom=sea_bottom, water_depth=water_depth)
166
+
167
+
168
+ ######################
169
+ # HELPER FUNCTIONS #
170
+ ######################
171
+
172
+ def orthogonal_vectors(vec1, vec2) -> bool:
173
+ return np.linalg.norm(vec1 @ vec2) < 1e-6
174
+
175
+
176
+ def parallel_vectors(vec1, vec2) -> bool:
177
+ return np.linalg.norm(np.cross(vec1, vec2)) < 1e-6
178
+
179
+
180
+ def parallel_vectors_with_same_direction(vec1, vec2) -> bool:
181
+ return parallel_vectors(vec1, vec2) and np.dot(vec1, vec2) > 0
182
+
183
+
184
+ ################
185
+ # AXIS CLASS #
186
+ ################
187
+
188
+ class Axis(Abstract3DObject):
189
+ def __init__(self, vector=(1, 0, 0), point=(0, 0, 0)):
190
+ assert len(vector) == 3, "Vector of an axis should be given as a 3-ple of values."
191
+ assert len(point) == 3, "Point of an axis should be given as a 3-ple of values."
192
+ vector = np.array(vector, float)
193
+ self.vector = vector / np.linalg.norm(vector)
194
+ self.point = np.array(point, float)
195
+
196
+ def __repr__(self):
197
+ return f"Axis(vector={self.vector}, point={self.point})"
198
+
199
+ def __contains__(self, other_point):
200
+ if len(other_point) == 3:
201
+ other_point = np.asarray(other_point, dtype=float)
202
+ return parallel_vectors(other_point - self.point, self.vector)
203
+ else:
204
+ raise NotImplementedError
205
+
206
+ def __eq__(self, other):
207
+ if isinstance(self, Axis):
208
+ return (self is other) or (self.point in other and parallel_vectors(self.vector, other.vector))
209
+ else:
210
+ return NotImplemented
211
+
212
+ def is_orthogonal_to(self, other):
213
+ if isinstance(other, Plane):
214
+ return parallel_vectors(self.vector, other.normal)
215
+ elif len(other) == 3: # The other is supposed to be a vector given as a 3-ple
216
+ return orthogonal_vectors(self.vector, other)
217
+ else:
218
+ raise NotImplementedError
219
+
220
+ def is_parallel_to(self, other):
221
+ if isinstance(other, Plane):
222
+ return orthogonal_vectors(self.vector, other.normal)
223
+ elif isinstance(other, Axis):
224
+ return parallel_vectors(self.vector, other.vector)
225
+ elif len(other) == 3: # The other is supposed to be a vector given as a 3-ple
226
+ return parallel_vectors(self.vector, other)
227
+ else:
228
+ raise NotImplementedError
229
+
230
+ def angle_with_respect_to(self, other_axis: 'Axis') -> float:
231
+ """Angle between two axes."""
232
+ return np.arccos(np.dot(self.vector, other_axis.vector))
233
+
234
+ ################################
235
+ # Transformation of the axis #
236
+ ################################
237
+
238
+ def copy(self, name=None):
239
+ return Axis(vector=self.vector.copy(), point=self.point.copy())
240
+
241
+ @inplace_transformation
242
+ def translate(self, vector):
243
+ self.point += vector
244
+ return self
245
+
246
+ @inplace_transformation
247
+ def rotate(self, axis, angle):
248
+ rot_matrix = axis.rotation_matrix(angle)
249
+ self.point = rot_matrix @ (self.point - axis.point) + axis.point
250
+ self.vector = rot_matrix @ self.vector
251
+ return self
252
+
253
+ @inplace_transformation
254
+ def mirror(self, plane):
255
+ self.point -= 2 * (self.point @ plane.normal - plane.c) * plane.normal
256
+ self.vector -= 2 * (self.vector @ plane.normal) * plane.normal
257
+ return self
258
+
259
+ ###########
260
+ # Other #
261
+ ###########
262
+
263
+ def rotation_matrix(self, theta):
264
+ """Rotation matrix around the vector according to Rodrigues' formula."""
265
+ ux, uy, uz = self.vector
266
+ W = np.array([[0, -uz, uy],
267
+ [uz, 0, -ux],
268
+ [-uy, ux, 0]])
269
+ return np.identity(3) + np.sin(theta)*W + 2*np.sin(theta/2)**2 * (W @ W)
270
+
271
+ def rotate_vectors(self, vectors, angle):
272
+ vectors = np.asarray(vectors)
273
+ return (self.rotation_matrix(angle) @ vectors.T).T
274
+
275
+ def rotate_points(self, points, angle):
276
+ points = np.asarray(points)
277
+ return self.rotate_vectors(points - self.point, angle) + self.point
278
+
279
+
280
+ Ox_axis = Axis(vector=e_x, point=(0, 0, 0))
281
+ Oy_axis = Axis(vector=e_y, point=(0, 0, 0))
282
+ Oz_axis = Axis(vector=e_z, point=(0, 0, 0))
283
+
284
+
285
+ #################
286
+ # PLANE CLASS #
287
+ #################
288
+
289
+ class Plane(Abstract3DObject):
290
+ """3D plane, oriented by the direction of their normal."""
291
+ def __init__(self, normal=(0.0, 0.0, 1.0), point=(0.0, 0.0, 0.0)):
292
+ normal = np.asarray(normal, dtype=float)
293
+ self.normal = normal / np.linalg.norm(normal)
294
+ self.point = np.asarray(point, dtype=float)
295
+
296
+ def __repr__(self):
297
+ return f"Plane(normal={self.normal}, point={self.point})"
298
+
299
+ def __contains__(self, other):
300
+ if isinstance(other, Axis):
301
+ return other.point in self and orthogonal_vectors(self.normal, other.vector)
302
+ elif len(other) == 3:
303
+ return orthogonal_vectors(other - self.point, self.normal)
304
+ else:
305
+ raise NotImplementedError
306
+
307
+ def __eq__(self, other):
308
+ """Plane are considered equal only when their normal are pointing in the same direction."""
309
+ if isinstance(other, Plane):
310
+ return ((self is other) or
311
+ (other.point in self and parallel_vectors_with_same_direction(self.normal, other.normal)))
312
+ else:
313
+ return NotImplemented
314
+
315
+ def is_orthogonal_to(self, other):
316
+ if isinstance(other, Axis):
317
+ return parallel_vectors(self.normal, other.vector)
318
+ elif isinstance(other, Plane):
319
+ return orthogonal_vectors(self.normal, other.normal)
320
+ elif len(other) == 3: # The other is supposed to be a vector given as a 3-ple
321
+ return parallel_vectors(self.normal, other)
322
+ else:
323
+ raise NotImplementedError
324
+
325
+ @property
326
+ def c(self):
327
+ """Distance from plane to origin."""
328
+ return np.linalg.norm(self.normal @ self.point)
329
+
330
+ @property
331
+ def s(self):
332
+ """Distance from origin to plane along the normal"""
333
+ return np.dot(self.normal, self.point)
334
+
335
+ #################################
336
+ # Transformation of the plane #
337
+ #################################
338
+
339
+ def copy(self, name=None):
340
+ return Plane(normal=self.normal.copy(), point=self.point.copy())
341
+
342
+ @inplace_transformation
343
+ def translate(self, vector):
344
+ self.point = self.point + np.asarray(vector)
345
+ return self
346
+
347
+ @inplace_transformation
348
+ def rotate(self, axis, angle):
349
+ rot_matrix = axis.rotation_matrix(angle)
350
+ self.point = rot_matrix @ self.point
351
+ self.normal = rot_matrix @ self.normal
352
+ return self
353
+
354
+ @inplace_transformation
355
+ def mirror(self, plane):
356
+ self.point -= 2 * (self.point @ plane.normal - plane.c) * plane.normal
357
+ self.normal -= 2 * (self.normal @ plane.normal) * plane.normal
358
+ return self
359
+
360
+ ###########
361
+ # Other #
362
+ ###########
363
+
364
+ def distance_to_point(self, points):
365
+ """
366
+ Return the orthogonal distance of points with respect to the plane.
367
+ The distance is counted positively on one side of the plane and negatively on the other.
368
+
369
+ Parameters
370
+ ----------
371
+ points : ndarray
372
+ Array of points coordinates
373
+
374
+ Returns
375
+ -------
376
+ dist : ndarray
377
+ Array of distances of points with respect to the plane
378
+ """
379
+ return np.dot(points, self.normal) - np.dot(self.point, self.normal)
380
+
381
+ def get_edge_intersection(self, p0, p1):
382
+ """
383
+ Returns the coordinates of the intersection point between the plane and the edge P0P1.
384
+
385
+ Parameters
386
+ ----------
387
+ p0 : ndarray
388
+ Coordinates of point p0
389
+ p1 : ndarray
390
+ Coordinates of point P1
391
+
392
+ Returns
393
+ -------
394
+ I : ndarray
395
+ Coordinates of intersection point
396
+ """
397
+ assert len(p0) == 3 and len(p1) == 3
398
+
399
+ p0n = np.dot(p0, self.normal)
400
+ p1n = np.dot(p1, self.normal)
401
+ t = (p0n - self.s) / (p0n - p1n)
402
+ if t < 0. or t > 1.:
403
+ raise RuntimeError('Intersection is outside the edge')
404
+ return (1-t) * p0 + t * p1
405
+
406
+
407
+ yOz_Plane = Plane(normal=e_x, point=(0, 0, 0))
408
+ xOz_Plane = Plane(normal=e_y, point=(0, 0, 0))
409
+ xOy_Plane = Plane(normal=e_z, point=(0, 0, 0))
@@ -0,0 +1,37 @@
1
+ from typing import Tuple, Protocol, runtime_checkable
2
+ from numpy.typing import ArrayLike
3
+
4
+
5
+ @runtime_checkable
6
+ class MeshLike(Protocol):
7
+ """Minimal API that a class describing a mesh should implement to be
8
+ usable with the rest of Capytaine.
9
+
10
+ The goal is two-fold:
11
+ 1. Use at runtime to identify a mesh for functions that behaves
12
+ differently depending on the type of the input (e.g. Delhommeau().evaluate).
13
+ 2. Use as documentation for third-party mesh implementation.
14
+
15
+ In the future, it could also be used for static typing.
16
+ """
17
+ vertices: ArrayLike
18
+ faces: ArrayLike
19
+ nb_vertices: int
20
+ nb_faces: int
21
+ faces_centers: ArrayLike
22
+ faces_normals: ArrayLike
23
+ faces_areas: ArrayLike
24
+ faces_radiuses: ArrayLike
25
+ quadrature_points: Tuple[ArrayLike, ArrayLike]
26
+
27
+ def __short_str__(self) -> str:
28
+ ...
29
+
30
+ def extract_faces(self, faces_id):
31
+ ...
32
+
33
+ def join_meshes(*meshes, return_mask):
34
+ ...
35
+
36
+ def with_normal_vector_going_down(self, **kwargs):
37
+ ...