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,276 @@
1
+ """Helper functions to compute some properties of the mesh.
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 functools import reduce
8
+ from itertools import chain
9
+ import numpy as np
10
+ from typing import List
11
+ from numpy.typing import NDArray
12
+
13
+
14
+ def compute_faces_properties(mesh):
15
+ """Compute the faces properties of the mesh"""
16
+
17
+ # faces_areas, faces_normals, faces_centers = mm.get_all_faces_properties(mesh._vertices, mesh._faces)
18
+ nf = mesh.nb_faces
19
+
20
+ # triangle_mask = _faces[:, 0] == _faces[:, -1]
21
+ # nb_triangles = np.sum(triangle_mask)
22
+ # quads_mask = np.invert(triangle_mask)
23
+ # nb_quads = nf - nb_triangles
24
+
25
+ faces_areas = np.zeros(nf, dtype=float)
26
+ faces_normals = np.zeros((nf, 3), dtype=float)
27
+ faces_centers = np.zeros((nf, 3), dtype=float)
28
+
29
+ # Collectively dealing with triangles
30
+ # triangles = _faces[triangle_mask]
31
+ triangles_id = mesh.triangles_ids
32
+ triangles = mesh._faces[triangles_id]
33
+
34
+ triangles_normals = np.cross(mesh._vertices[triangles[:, 1]] - mesh._vertices[triangles[:, 0]],
35
+ mesh._vertices[triangles[:, 2]] - mesh._vertices[triangles[:, 0]])
36
+ triangles_normals_norm = np.linalg.norm(triangles_normals, axis=1)
37
+
38
+ degenerate_triangle = np.abs(triangles_normals_norm) < 1e-12
39
+ triangles_id = triangles_id[~degenerate_triangle]
40
+ triangles_normals = triangles_normals[~degenerate_triangle, :]
41
+ triangles_normals_norm = triangles_normals_norm[~degenerate_triangle]
42
+ triangles = triangles[~degenerate_triangle, :]
43
+ # Now, continue the computations without the degenerate triangles
44
+
45
+ faces_normals[triangles_id] = triangles_normals / triangles_normals_norm[:, np.newaxis]
46
+ faces_areas[triangles_id] = triangles_normals_norm / 2.
47
+ faces_centers[triangles_id] = np.sum(mesh._vertices[triangles[:, :3]], axis=1) / 3.
48
+
49
+ # Collectively dealing with quads
50
+ quads_id = mesh.quadrangles_ids
51
+ quads = mesh._faces[quads_id]
52
+ # quads = _faces[quads_mask]
53
+
54
+ quads_normals = np.cross(mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]],
55
+ mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 1]])
56
+
57
+ quads_normals_norm = np.linalg.norm(quads_normals, axis=1)
58
+
59
+ degenerate_quad = np.abs(quads_normals_norm) < 1e-12
60
+ quads_id = quads_id[~degenerate_quad]
61
+ quads_normals = quads_normals[~degenerate_quad]
62
+ quads_normals_norm = quads_normals_norm[~degenerate_quad]
63
+ quads = quads[~degenerate_quad, :]
64
+ # Now, continue the computations without the degenerate quads
65
+
66
+ faces_normals[quads_id] = quads_normals / quads_normals_norm[:, np.newaxis]
67
+
68
+ a1 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 1]] - mesh._vertices[quads[:, 0]],
69
+ mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
70
+ a2 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 0]],
71
+ mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
72
+ faces_areas[quads_id] = a1 + a2
73
+
74
+ c1 = np.sum(mesh._vertices[quads[:, :3]], axis=1) / 3.
75
+ c2 = (np.sum(mesh._vertices[quads[:, 2:4]], axis=1) + mesh._vertices[quads[:, 0]]) / 3.
76
+
77
+ faces_centers[quads_id] = (np.array(([a1, ] * 3)).T * c1 + np.array(([a2, ] * 3)).T * c2)
78
+ faces_centers[quads_id] /= np.array(([faces_areas[quads_id], ] * 3)).T
79
+
80
+ faces_radiuses = compute_radiuses(mesh, faces_centers)
81
+
82
+ return {'faces_areas': faces_areas,
83
+ 'faces_normals': faces_normals,
84
+ 'faces_centers': faces_centers,
85
+ 'faces_radiuses': faces_radiuses,
86
+ }
87
+
88
+
89
+ def compute_radiuses(mesh, faces_centers):
90
+ """Compute the radiuses of the faces of the mesh.
91
+
92
+ The radius is defined here as the maximal distance between the center
93
+ of mass of a cell and one of its points."""
94
+
95
+ # Coordinates of all the vertices grouped by face
96
+ faces_vertices = mesh.vertices[mesh.faces, :]
97
+ # faces_vertices.shape == (nb_faces, 4, 3)
98
+
99
+ # Reorder the axes for array broadcasting below
100
+ faces_vertices = np.moveaxis(faces_vertices, 0, 1)
101
+ # faces_vertices.shape == (4, nb_faces, 3)
102
+
103
+ # Get all the vectors between the center of faces and their vertices.
104
+ radial_vector = faces_centers - faces_vertices
105
+ # radial_vector.shape == (4, nb_faces, 3)
106
+
107
+ # Keep the maximum length
108
+ faces_radiuses = np.max(np.linalg.norm(radial_vector, axis=2), axis=0)
109
+ # faces_radiuses.shape = (nb_faces)
110
+
111
+ return faces_radiuses
112
+
113
+
114
+ def compute_connectivity(mesh):
115
+ """Compute the connectivities of the mesh.
116
+
117
+ It concerns further connectivity than simple faces/vertices connectivities. It computes the vertices / vertices, vertices / faces and faces / faces connectivities.
118
+
119
+ Note
120
+ ----
121
+ * Note that if the mesh is not conformal, the algorithm may not perform correctly
122
+
123
+ TODO: The computation of boundaries should be in the future computed with help of VTK
124
+ """
125
+
126
+ nv = mesh.nb_vertices
127
+ nf = mesh.nb_faces
128
+
129
+ mesh_closed = True
130
+
131
+ # Building connectivities
132
+
133
+ # Establishing v_v and v_f connectivities
134
+ v_v = dict([(i, set()) for i in range(nv)])
135
+ v_f = dict([(i, set()) for i in range(nv)])
136
+ for (iface, face) in enumerate(mesh._faces):
137
+ if face[0] == face[-1]:
138
+ face_w = face[:3]
139
+ else:
140
+ face_w = face
141
+ for (index, iV) in enumerate(face_w):
142
+ v_f[iV].add(iface)
143
+ v_v[face_w[index - 1]].add(iV)
144
+ v_v[iV].add(face_w[index - 1])
145
+
146
+ # Connectivity f_f
147
+ boundary_edges = dict()
148
+
149
+ f_f = dict([(i, set()) for i in range(nf)])
150
+ for ivertex in range(nv):
151
+ set1 = v_f[ivertex]
152
+ for iadj_v in v_v[ivertex]:
153
+ set2 = v_f[iadj_v]
154
+ intersection = list(set1 & set2)
155
+ if len(intersection) == 2:
156
+ f_f[intersection[0]].add(intersection[1])
157
+ f_f[intersection[1]].add(intersection[0])
158
+
159
+ elif len(intersection) == 1:
160
+ boundary_face = mesh._faces[intersection[0]]
161
+
162
+ if boundary_face[0] == boundary_face[-1]:
163
+ boundary_face = boundary_face[:3]
164
+ ids = np.where((boundary_face == ivertex) + (boundary_face == iadj_v))[0]
165
+
166
+ if ids[1] != ids[0]+1:
167
+ i_v_orig, i_v_target = boundary_face[ids]
168
+ else:
169
+ i_v_target, i_v_orig = boundary_face[ids]
170
+
171
+ boundary_edges[i_v_orig] = i_v_target
172
+ else:
173
+ raise RuntimeError('Unexpected error while computing mesh connectivities')
174
+
175
+ # Computing boundaries
176
+ boundaries = list()
177
+ # TODO: calculer des boundaries fermees et ouvertes (closed_boundaries et open_boundaries) et mettre dans dict
178
+ while True:
179
+ try:
180
+ boundary = list()
181
+ i_v0_init, i_v1 = boundary_edges.popitem()
182
+ boundary.append(i_v0_init)
183
+ boundary.append(i_v1)
184
+ i_v0 = i_v1
185
+
186
+ while True:
187
+ try:
188
+ i_v1 = boundary_edges.pop(i_v0)
189
+ boundary.append(i_v1)
190
+ i_v0 = i_v1
191
+ except KeyError:
192
+ if boundary[0] != boundary[-1]:
193
+ print('Boundary is not closed !!!')
194
+ else:
195
+ boundaries.append(boundary)
196
+ break
197
+ except KeyError:
198
+ break
199
+
200
+ return {'v_v': v_v,
201
+ 'v_f': v_f,
202
+ 'f_f': f_f,
203
+ 'boundaries': boundaries}
204
+
205
+ def faces_in_group(faces: NDArray[np.integer], group: NDArray[np.integer]) -> NDArray[np.bool_]:
206
+ """Identification of faces with vertices within group.
207
+
208
+ Parameters
209
+ ----------
210
+ faces : NDArray[np.integer]
211
+ Mesh faces. Expecting a numpy array of shape N_faces x N_vertices_per_face.
212
+ group : NDArray[np.integer]
213
+ Group of connected vertices
214
+
215
+ Returns
216
+ -------
217
+ NDArray[np.bool]
218
+ Mask of faces containing vertices from the group
219
+ """
220
+ return np.any(np.isin(faces, group), axis=1)
221
+
222
+ def clustering(faces: NDArray[np.integer]) -> List[NDArray[np.integer]]:
223
+ """Clustering of vertices per connected faces.
224
+
225
+ Parameters
226
+ ----------
227
+ faces : NDArray[np.integer]
228
+ Mesh faces. Expecting a numpy array of shape N_faces x N_vertices_per_face.
229
+
230
+ Returns
231
+ -------
232
+ list[NDArray[np.integer]]
233
+ Groups of connected vertices.
234
+ """
235
+ vert_groups: list[NDArray[np.integer]] = []
236
+ mask = np.ones(faces.shape[0], dtype=bool)
237
+ while np.any(mask):
238
+ # Consider faces whose vertices are not already identified in a group.
239
+ # Start new group by considering first face
240
+ remaining_faces = faces[mask]
241
+ group = remaining_faces[0]
242
+ rem_mask = np.ones(remaining_faces.shape[0], dtype=bool)
243
+ # Iterative update of vertices group. Output final result to frozenset
244
+ while not np.allclose(new:=faces_in_group(remaining_faces, group), rem_mask):
245
+ group = np.unique(remaining_faces[new])
246
+ rem_mask = new
247
+ else:
248
+ group = np.unique(remaining_faces[new])
249
+ vert_groups.append(group)
250
+ # Identify faces that have no vertices in current groups
251
+ mask = ~reduce(np.logical_or, [faces_in_group(faces, group) for group in vert_groups])
252
+ return vert_groups
253
+
254
+
255
+ def connected_components(mesh):
256
+ """Returns a list of meshes that each corresponds to the a connected component in the original mesh.
257
+ Assumes the mesh is mostly conformal without duplicate vertices.
258
+ """
259
+ # Get connected vertices
260
+ vertices_components = clustering(mesh.faces)
261
+ # Verification
262
+ if sum(len(group) for group in vertices_components) != len(set(chain.from_iterable(vertices_components))):
263
+ raise ValueError("Error in connected components clustering. Some elements are duplicated")
264
+ # The components are found. The rest is just about retrieving the faces in each components.
265
+ faces_components = [np.argwhere(faces_in_group(mesh.faces, group)) for group in vertices_components]
266
+ components = [mesh.extract_faces(f) for f in faces_components]
267
+ return components
268
+
269
+
270
+ def connected_components_of_waterline(mesh, z=0.0):
271
+ if np.any(mesh.vertices[:, 2] > z + 1e-8):
272
+ mesh = mesh.immersed_part(free_surface=z)
273
+ fs_vertices_indices = np.where(np.isclose(mesh.vertices[:, 2], z))[0]
274
+ fs_faces_indices = np.where(np.any(np.isin(mesh.faces, fs_vertices_indices), axis=1))[0]
275
+ crown_mesh = mesh.extract_faces(fs_faces_indices)
276
+ return connected_components(crown_mesh)
@@ -0,0 +1,80 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+
5
+ from capytaine.tools.optional_imports import silently_import_optional_dependency
6
+
7
+ LOG = logging.getLogger(__name__)
8
+
9
+ # The builtin methods are stored as a list of 2D-points in [-1, 1]² and a list
10
+ # of corresponding weights. The 2D points will be remapped to the actual shape
11
+ # of the faces. They are only defined for quadrilaterals. They also work for
12
+ # triangles (although they might be subobtimal).
13
+
14
+ builtin_methods = {
15
+ "First order": (np.array([(0.0, 0.0)]), np.array([1.0])),
16
+ "Gauss-Legendre 2": (
17
+ np.array([(+1/np.sqrt(3), +1/np.sqrt(3)),
18
+ (+1/np.sqrt(3), -1/np.sqrt(3)),
19
+ (-1/np.sqrt(3), +1/np.sqrt(3)),
20
+ (-1/np.sqrt(3), -1/np.sqrt(3))]),
21
+ np.array([1/4, 1/4, 1/4, 1/4])
22
+ )
23
+ }
24
+
25
+
26
+ def compute_quadrature_on_faces(faces, method):
27
+ """
28
+ Compute the quadrature points and weight for numerical integration over the faces.
29
+
30
+ Parameters
31
+ ----------
32
+ faces: array of shape (nb_faces, 4, 3)
33
+ The 3D-coordinates of each of the 4 corners of each face.
34
+ method: string or quadpy object
35
+ The method used to compute the quadrature scheme
36
+
37
+ Returns
38
+ -------
39
+ points: array of shape (nb_faces, nb_quad_points, 3)
40
+ The 3D-coordinates of each of the quadrature points (their number depends on the method) of each face.
41
+ weights: array of shape (nb_faces, nb_quad_points)
42
+ Weights associated to each of the quadrature points.
43
+ """
44
+
45
+ if method in builtin_methods:
46
+ LOG.debug("Quadrature method found in builtin methods.")
47
+ local_points, local_weights = builtin_methods[method]
48
+
49
+ elif ((quadpy := silently_import_optional_dependency("quadpy")) is not None
50
+ and isinstance(method, quadpy.c2._helpers.C2Scheme)):
51
+ LOG.debug("Quadrature method is a Quadpy scheme: %s", method.name)
52
+ local_points = method.points.T
53
+ local_weights = method.weights
54
+
55
+ else:
56
+ raise ValueError(f"Unrecognized quadrature scheme: {method}.\n"
57
+ f"Consider using one of the following: {set(builtin_methods.keys())}")
58
+
59
+ nb_faces = faces.shape[0]
60
+ nb_quad_points = len(local_weights)
61
+ points = np.empty((nb_faces, nb_quad_points, 3))
62
+ weights = np.empty((nb_faces, nb_quad_points))
63
+
64
+ for i_face in range(nb_faces):
65
+ for k_quad in range(nb_quad_points):
66
+ xk, yk = local_points[k_quad, :]
67
+ points[i_face, k_quad, :] = (
68
+ (1+xk)*(1+yk) * faces[i_face, 0, :]
69
+ + (1+xk)*(1-yk) * faces[i_face, 1, :]
70
+ + (1-xk)*(1-yk) * faces[i_face, 2, :]
71
+ + (1-xk)*(1+yk) * faces[i_face, 3, :]
72
+ )/4
73
+ dxidx = ((1+yk)*faces[i_face, 0, :] + (1-yk)*faces[i_face, 1, :]
74
+ - (1-yk)*faces[i_face, 2, :] - (1+yk)*faces[i_face, 3, :])/4
75
+ dxidy = ((1+xk)*faces[i_face, 0, :] - (1+xk)*faces[i_face, 1, :]
76
+ - (1-xk)*faces[i_face, 2, :] + (1-xk)*faces[i_face, 3, :])/4
77
+ detJ = np.linalg.norm(np.cross(dxidx, dxidy))
78
+ weights[i_face, k_quad] = local_weights[k_quad] * 4 * detJ
79
+
80
+ return points, weights