capytaine 2.3.1__cp313-cp313-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-313-x86_64-linux-gnu.so +0 -0
  32. capytaine/green_functions/libs/Delhommeau_float64.cpython-313-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,342 @@
1
+ """A set of meshes that can be used as a Mesh."""
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 chain, accumulate
7
+ from functools import lru_cache
8
+ from typing import Iterable, Union
9
+
10
+ import numpy as np
11
+
12
+ from capytaine.meshes.geometry import Abstract3DObject, ClippableMixin, inplace_transformation
13
+ from capytaine.meshes.surface_integrals import SurfaceIntegralsMixin
14
+ from capytaine.meshes.meshes import Mesh
15
+
16
+ LOG = logging.getLogger(__name__)
17
+
18
+
19
+ class CollectionOfMeshes(ClippableMixin, SurfaceIntegralsMixin, Abstract3DObject):
20
+ """A tuple of meshes.
21
+ It gives access to all the vertices of all the sub-meshes as if it were a mesh itself.
22
+ Collections can be nested to store meshes in a tree structure.
23
+
24
+ Parameters
25
+ ----------
26
+ meshes: Iterable of Mesh or CollectionOfMeshes
27
+ meshes in the collection
28
+ name : str, optional
29
+ a name for the collection
30
+ """
31
+
32
+ def __init__(self, meshes: Iterable[Union[Mesh, 'CollectionOfMeshes']], name=None):
33
+
34
+ self._meshes = tuple(meshes)
35
+
36
+ for mesh in self._meshes:
37
+ assert isinstance(mesh, Mesh) or isinstance(mesh, CollectionOfMeshes)
38
+
39
+ if name is None:
40
+ self.name = f'collection_of_meshes_{next(Mesh._ids)}'
41
+ else:
42
+ self.name = str(name)
43
+
44
+ LOG.debug(f"New collection of meshes: {repr(self)}")
45
+
46
+ def __short_str__(self):
47
+ return (f"{self.__class__.__name__}(..., name=\"{self.name}\")")
48
+
49
+ def __str__(self):
50
+ if len(self._meshes) < 3:
51
+ meshes_str = ', '.join(m.__short_str__() for m in self._meshes)
52
+ else:
53
+ meshes_str = self._meshes[0].__short_str__() + ", ..., " + self._meshes[-1].__short_str__()
54
+ return f"{self.__class__.__name__}([{meshes_str}], name=\"{self.name}\")"
55
+
56
+ def __repr__(self):
57
+ return f"{self.__class__.__name__}({', '.join(str(m) for m in self._meshes)}, name=\"{self.name}\")"
58
+
59
+ def _repr_pretty_(self, p, cycle):
60
+ p.text(self.__str__())
61
+
62
+ def __rich_repr__(self):
63
+ class WrappedString:
64
+ def __init__(self, s):
65
+ self.s = s
66
+ def __repr__(self):
67
+ return self.s
68
+ for m in self._meshes:
69
+ yield m
70
+ yield "name", self.name
71
+
72
+ def __iter__(self):
73
+ return iter(self._meshes)
74
+
75
+ def __len__(self):
76
+ return len(self._meshes)
77
+
78
+ def __getitem__(self, item):
79
+ return self._meshes.__getitem__(item)
80
+
81
+ def __eq__(self, other):
82
+ if isinstance(other, CollectionOfMeshes):
83
+ return self._meshes == other._meshes
84
+ else:
85
+ return NotImplemented
86
+
87
+ def __hash__(self):
88
+ return hash(self._meshes)
89
+
90
+ def tree_view(self, **kwargs):
91
+ body_tree_views = []
92
+ for i, mesh in enumerate(self):
93
+ tree_view = mesh.tree_view(**kwargs)
94
+ if i == len(self)-1:
95
+ prefix = ' └─'
96
+ shift = ' '
97
+ else:
98
+ prefix = ' ├─'
99
+ shift = ' │ '
100
+ body_tree_views.append(prefix + tree_view.replace('\n', '\n' + shift))
101
+
102
+ return self.__short_str__() + '\n' + '\n'.join(body_tree_views)
103
+
104
+ def path_to_leaf(self):
105
+ """
106
+ Builds a list of lists of paths from the collection corresponding to the
107
+ root of the tree to the submeshes corresponding to the leaves
108
+ """
109
+ ptl = []
110
+ for i, mesh in enumerate(self):
111
+ for path in mesh.path_to_leaf():
112
+ ptl.append([i] + path)
113
+ return ptl
114
+
115
+ def copy(self, name=None):
116
+ from copy import deepcopy
117
+ new_mesh = deepcopy(self)
118
+ if name is not None:
119
+ new_mesh.name = name
120
+ return new_mesh
121
+
122
+ @inplace_transformation
123
+ def heal_mesh(self, closed_mesh=False):
124
+ for mesh in self:
125
+ mesh.heal_mesh(closed_mesh=closed_mesh)
126
+
127
+ @inplace_transformation
128
+ def with_normal_vector_going_down(self):
129
+ for mesh in self:
130
+ mesh.with_normal_vector_going_down()
131
+
132
+ ##############
133
+ # Properties #
134
+ ##############
135
+
136
+ @property
137
+ def nb_submeshes(self):
138
+ return len(self)
139
+
140
+ @property
141
+ def nb_vertices(self):
142
+ return sum(mesh.nb_vertices for mesh in self)
143
+
144
+ @property
145
+ def nb_faces(self):
146
+ return sum(mesh.nb_faces for mesh in self)
147
+
148
+ @property
149
+ def volume(self):
150
+ return sum(mesh.volume for mesh in self)
151
+
152
+ @property
153
+ def vertices(self):
154
+ return np.concatenate([mesh.vertices for mesh in self])
155
+
156
+ @property
157
+ def faces(self):
158
+ """Return the indices of the vertices forming each of the faces. For the
159
+ later submeshes, the indices of the vertices has to be shifted to
160
+ correspond to their index in the concatenated array self.vertices.
161
+ """
162
+ nb_vertices = accumulate(chain([0], (mesh.nb_vertices for mesh in self[:-1])))
163
+ return np.concatenate([mesh.faces + nbv for mesh, nbv in zip(self, nb_vertices)])
164
+
165
+ @property
166
+ def faces_normals(self):
167
+ return np.concatenate([mesh.faces_normals for mesh in self])
168
+
169
+ @property
170
+ def faces_areas(self):
171
+ return np.concatenate([mesh.faces_areas for mesh in self])
172
+
173
+ @property
174
+ def faces_centers(self):
175
+ return np.concatenate([mesh.faces_centers for mesh in self])
176
+
177
+ @property
178
+ def faces_radiuses(self):
179
+ return np.concatenate([mesh.faces_radiuses for mesh in self])
180
+
181
+ @property
182
+ def quadrature_points(self):
183
+ quad_submeshes = [mesh.quadrature_points for mesh in self]
184
+ return (
185
+ np.concatenate([quad[0] for quad in quad_submeshes]), # Points
186
+ np.concatenate([quad[1] for quad in quad_submeshes]) # Weights
187
+ )
188
+
189
+ @property
190
+ def quadrature_method(self):
191
+ methods_submeshes = [mesh.quadrature_method for mesh in self]
192
+ if len(set(methods_submeshes)) == 1:
193
+ return methods_submeshes[0] # All the same methods
194
+ else:
195
+ return "Mixed quadrature method"
196
+
197
+ def compute_quadrature(self, method):
198
+ for mesh in self:
199
+ mesh.compute_quadrature(method)
200
+
201
+ @property
202
+ def center_of_mass_of_nodes(self):
203
+ return sum([mesh.nb_vertices*mesh.center_of_mass_of_nodes for mesh in self])/self.nb_vertices
204
+
205
+ @property
206
+ @lru_cache(maxsize=1024)
207
+ def diameter_of_nodes(self):
208
+ return self.merged().diameter_of_nodes # TODO: improve implementation
209
+
210
+ def indices_of_mesh(self, mesh_index: int) -> slice:
211
+ """Return the indices of the faces for the sub-mesh given as argument."""
212
+ start = sum((mesh.nb_faces for mesh in self[:mesh_index])) # Number of faces in previous meshes
213
+ return slice(start, start + self[mesh_index].nb_faces)
214
+
215
+ def submesh_containing_face(self, id_face):
216
+ total_faces = 0
217
+ for id_mesh in range(self.nb_submeshes):
218
+ total_faces += self[id_mesh].nb_faces
219
+ if id_face < total_faces:
220
+ return id_mesh, id_face - (total_faces - self[id_mesh].nb_faces)
221
+
222
+ ##################
223
+ # Transformation #
224
+ ##################
225
+
226
+ def join_meshes(*meshes, name=None, return_masks=False):
227
+ coll = CollectionOfMeshes(meshes, name=name)
228
+ if return_masks:
229
+ masks = []
230
+ for i_mesh in range(len(meshes)):
231
+ mask = np.full((coll.nb_faces,), False)
232
+ mask[coll.indices_of_mesh(i_mesh)] = True
233
+ masks.append(mask)
234
+ return coll, masks
235
+ return coll
236
+
237
+ def __add__(self, mesh_to_add):
238
+ return self.join_meshes(mesh_to_add)
239
+
240
+ def merged(self, name=None) -> Mesh:
241
+ """Merge the sub-meshes and return a full mesh.
242
+ If the collection contains other collections, they are merged recursively.
243
+ Optionally, a new name can be given to the resulting mesh."""
244
+ if name is None:
245
+ name = self.name
246
+ merged = Mesh(self.vertices, self.faces, name=name)
247
+ merged.merge_duplicates()
248
+ merged.heal_triangles()
249
+ return merged
250
+
251
+ def extract_one_face(self, id_face):
252
+ id_mesh, relative_id_face = self.submesh_containing_face(id_face)
253
+ mesh = self[id_mesh]
254
+
255
+ extracted_mesh = mesh.extract_one_face(relative_id_face)
256
+
257
+ if hasattr(mesh, '__internals__'):
258
+ for prop in mesh.__internals__:
259
+ if prop[:4] == "face":
260
+ extracted_mesh.__internals__[prop] = mesh.__internals__[prop][[relative_id_face]]
261
+
262
+ return extracted_mesh
263
+
264
+ def extract_faces(self, *args, **kwargs):
265
+ return self.merged().extract_faces(*args, **kwargs)
266
+
267
+ def sliced_by_plane(self, plane):
268
+ return CollectionOfMeshes([mesh.sliced_by_plane(plane) for mesh in self], name=self.name)
269
+
270
+ @inplace_transformation
271
+ def translate(self, vector):
272
+ for mesh in self:
273
+ mesh.translate(vector)
274
+
275
+ @inplace_transformation
276
+ def rotate(self, axis, angle):
277
+ for mesh in self:
278
+ mesh.rotate(axis, angle)
279
+
280
+ @inplace_transformation
281
+ def mirror(self, plane):
282
+ for mesh in self:
283
+ mesh.mirror(plane)
284
+
285
+ @inplace_transformation
286
+ def clip(self, plane):
287
+ self._clipping_data = {'faces_ids': []}
288
+ faces_shifts = list(accumulate(chain([0], (mesh.nb_faces for mesh in self[:-1]))))
289
+ for mesh, faces_shift in zip(self, faces_shifts):
290
+ mesh.clip(plane)
291
+ self._clipping_data['faces_ids'].extend([i + faces_shift for i in mesh._clipping_data['faces_ids']])
292
+ self._clipping_data['faces_ids'] = np.asarray(self._clipping_data['faces_ids'])
293
+ self.prune_empty_meshes()
294
+
295
+ def symmetrized(self, plane):
296
+ from capytaine.meshes.symmetric import ReflectionSymmetricMesh
297
+ half = self.clipped(plane, name=f"{self.name}_half")
298
+ return ReflectionSymmetricMesh(half, plane=plane, name=f"symmetrized_of_{self.name}")
299
+
300
+ @inplace_transformation
301
+ def prune_empty_meshes(self):
302
+ """Remove empty meshes from the collection."""
303
+ remaining_meshes = tuple(mesh for mesh in self if mesh.nb_faces > 0 and mesh.nb_vertices > 0)
304
+ if len(remaining_meshes) == 0:
305
+ self._meshes = (Mesh(name="empty_mesh"),)
306
+ else:
307
+ self._meshes = remaining_meshes
308
+
309
+ @property
310
+ def axis_aligned_bbox(self):
311
+ """Get the axis aligned bounding box of the mesh.
312
+
313
+ Returns
314
+ -------
315
+ tuple
316
+ (xmin, xmax, ymin, ymax, zmin, zmax)
317
+ """
318
+ if self.nb_vertices > 0:
319
+ x, y, z = self.vertices.T
320
+ return (x.min(), x.max(),
321
+ y.min(), y.max(),
322
+ z.min(), z.max())
323
+ else:
324
+ return tuple(np.zeros(6))
325
+
326
+ def show(self, **kwargs):
327
+ from capytaine.ui.vtk.mesh_viewer import MeshViewer
328
+
329
+ viewer = MeshViewer()
330
+ for mesh in self:
331
+ viewer.add_mesh(mesh.merged(), **kwargs)
332
+ viewer.show()
333
+ viewer.finalize()
334
+
335
+ def show_matplotlib(self, *args, **kwargs):
336
+ self.merged().show_matplotlib(*args, **kwargs)
337
+
338
+ def lowest_lid_position(self, *args, **kwargs):
339
+ return self.merged().lowest_lid_position(*args, **kwargs)
340
+
341
+ def generate_lid(self, *args, **kwargs):
342
+ return self.merged().generate_lid(*args, **kwargs)