capytaine 2.3.1__cp310-cp310-win_amd64.whl → 3.0.0a1__cp310-cp310-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 (88) hide show
  1. capytaine/__about__.py +7 -2
  2. capytaine/__init__.py +11 -15
  3. capytaine/bem/engines.py +234 -354
  4. capytaine/bem/problems_and_results.py +14 -13
  5. capytaine/bem/solver.py +204 -80
  6. capytaine/bodies/bodies.py +278 -869
  7. capytaine/bodies/dofs.py +136 -9
  8. capytaine/bodies/hydrostatics.py +540 -0
  9. capytaine/bodies/multibodies.py +216 -0
  10. capytaine/green_functions/{libs/Delhommeau_float32.cp310-win_amd64.dll.a → Delhommeau_float32.cp310-win_amd64.dll.a} +0 -0
  11. capytaine/green_functions/Delhommeau_float32.cp310-win_amd64.pyd +0 -0
  12. capytaine/green_functions/{libs/Delhommeau_float64.cp310-win_amd64.dll.a → Delhommeau_float64.cp310-win_amd64.dll.a} +0 -0
  13. capytaine/green_functions/Delhommeau_float64.cp310-win_amd64.pyd +0 -0
  14. capytaine/green_functions/abstract_green_function.py +2 -2
  15. capytaine/green_functions/delhommeau.py +31 -16
  16. capytaine/green_functions/hams.py +19 -13
  17. capytaine/io/legacy.py +3 -103
  18. capytaine/io/xarray.py +11 -6
  19. capytaine/meshes/__init__.py +2 -6
  20. capytaine/meshes/abstract_meshes.py +375 -0
  21. capytaine/meshes/clean.py +302 -0
  22. capytaine/meshes/clip.py +347 -0
  23. capytaine/meshes/export.py +89 -0
  24. capytaine/meshes/geometry.py +244 -394
  25. capytaine/meshes/io.py +433 -0
  26. capytaine/meshes/meshes.py +617 -681
  27. capytaine/meshes/predefined/cylinders.py +22 -56
  28. capytaine/meshes/predefined/rectangles.py +26 -85
  29. capytaine/meshes/predefined/spheres.py +4 -11
  30. capytaine/meshes/quality.py +118 -407
  31. capytaine/meshes/surface_integrals.py +48 -29
  32. capytaine/meshes/symmetric_meshes.py +641 -0
  33. capytaine/meshes/visualization.py +353 -0
  34. capytaine/post_pro/free_surfaces.py +1 -4
  35. capytaine/post_pro/kochin.py +10 -10
  36. capytaine/tools/block_circulant_matrices.py +275 -0
  37. capytaine/tools/lists_of_points.py +2 -2
  38. capytaine/tools/memory_monitor.py +45 -0
  39. capytaine/tools/symbolic_multiplication.py +13 -1
  40. capytaine/tools/timer.py +58 -34
  41. capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
  42. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
  43. capytaine-3.0.0a1.dist-info/RECORD +70 -0
  44. capytaine/bodies/predefined/__init__.py +0 -6
  45. capytaine/bodies/predefined/cylinders.py +0 -151
  46. capytaine/bodies/predefined/rectangles.py +0 -111
  47. capytaine/bodies/predefined/spheres.py +0 -70
  48. capytaine/green_functions/FinGreen3D/.gitignore +0 -1
  49. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
  50. capytaine/green_functions/FinGreen3D/LICENSE +0 -165
  51. capytaine/green_functions/FinGreen3D/Makefile +0 -16
  52. capytaine/green_functions/FinGreen3D/README.md +0 -24
  53. capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
  54. capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
  55. capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
  56. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
  57. capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
  58. capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
  59. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
  60. capytaine/green_functions/libs/Delhommeau_float32.cp310-win_amd64.pyd +0 -0
  61. capytaine/green_functions/libs/Delhommeau_float64.cp310-win_amd64.pyd +0 -0
  62. capytaine/green_functions/libs/__init__.py +0 -0
  63. capytaine/io/mesh_loaders.py +0 -1086
  64. capytaine/io/mesh_writers.py +0 -692
  65. capytaine/io/meshio.py +0 -38
  66. capytaine/matrices/__init__.py +0 -16
  67. capytaine/matrices/block.py +0 -592
  68. capytaine/matrices/block_toeplitz.py +0 -325
  69. capytaine/matrices/builders.py +0 -89
  70. capytaine/matrices/linear_solvers.py +0 -232
  71. capytaine/matrices/low_rank.py +0 -395
  72. capytaine/meshes/clipper.py +0 -465
  73. capytaine/meshes/collections.py +0 -342
  74. capytaine/meshes/mesh_like_protocol.py +0 -37
  75. capytaine/meshes/properties.py +0 -276
  76. capytaine/meshes/quadratures.py +0 -80
  77. capytaine/meshes/symmetric.py +0 -462
  78. capytaine/tools/lru_cache.py +0 -49
  79. capytaine/ui/vtk/__init__.py +0 -3
  80. capytaine/ui/vtk/animation.py +0 -329
  81. capytaine/ui/vtk/body_viewer.py +0 -28
  82. capytaine/ui/vtk/helpers.py +0 -82
  83. capytaine/ui/vtk/mesh_viewer.py +0 -461
  84. capytaine-2.3.1.dist-info/DELVEWHEEL +0 -2
  85. capytaine-2.3.1.dist-info/RECORD +0 -97
  86. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
  87. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
  88. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,347 @@
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
+ from __future__ import annotations
16
+ from typing import List, Set, Tuple
17
+
18
+ import numpy as np
19
+
20
+
21
+ def _get_intersection(
22
+ v1: np.ndarray,
23
+ v2: np.ndarray,
24
+ normal: np.ndarray,
25
+ origin: np.ndarray,
26
+ tol: float = 1e-8,
27
+ ) -> np.ndarray:
28
+ """Intersect a line segment with a plane.
29
+
30
+ Parameters
31
+ ----------
32
+ v1, v2 : numpy.ndarray
33
+ Endpoints of the segment expressed as 3D vectors.
34
+ normal : numpy.ndarray
35
+ Plane normal; does not need to be unit length.
36
+ origin : numpy.ndarray
37
+ Point lying on the plane.
38
+ tol : float, default=1e-8
39
+ Tolerance used to detect parallel segments and clamp the intersection
40
+ parameter to the segment bounds.
41
+
42
+ Returns
43
+ -------
44
+ numpy.ndarray
45
+ Intersection point located on the (possibly clamped) segment.
46
+
47
+ Raises
48
+ ------
49
+ ValueError
50
+ If the segment is parallel to the plane or the intersection lies
51
+ outside the tolerated segment range.
52
+ """
53
+ v1 = np.asarray(v1, dtype=float)
54
+ v2 = np.asarray(v2, dtype=float)
55
+ n = np.asarray(normal, dtype=float)
56
+ o = np.asarray(origin, dtype=float)
57
+
58
+ u = v2 - v1
59
+ denom = float(np.dot(n, u))
60
+ if abs(denom) < tol:
61
+ raise ValueError("Segment is parallel to the plane (no unique intersection).")
62
+
63
+ t = float(np.dot(n, (o - v1)) / denom)
64
+
65
+ if t < -tol or t > 1.0 + tol:
66
+ raise ValueError(f"Intersection t={t:.6g} lies outside the segment [0,1].")
67
+
68
+ t = max(0.0, min(1.0, t))
69
+ return v1 + t * u
70
+
71
+
72
+ def compute_aspect_ratio(tri_pts: np.ndarray) -> float:
73
+ """Compute the aspect ratio of a triangle.
74
+
75
+ The aspect ratio is defined as the longest edge length divided by the
76
+ altitude relative to that edge.
77
+
78
+ Parameters
79
+ ----------
80
+ tri_pts : numpy.ndarray
81
+ Triangle vertices arranged in a ``(3, 3)`` array.
82
+
83
+ Returns
84
+ -------
85
+ float
86
+ Aspect ratio ``L / h`` (values greater than or equal to 1).
87
+
88
+ Raises
89
+ ------
90
+ ValueError
91
+ If the triangle is degenerate and its area approaches zero.
92
+ """
93
+ tri_pts = np.asarray(tri_pts, dtype=float)
94
+ if tri_pts.shape != (3, 3):
95
+ raise ValueError("tri_pts must have shape (3,3).")
96
+
97
+ edges = np.array(
98
+ [
99
+ np.linalg.norm(tri_pts[1] - tri_pts[0]),
100
+ np.linalg.norm(tri_pts[2] - tri_pts[1]),
101
+ np.linalg.norm(tri_pts[0] - tri_pts[2]),
102
+ ],
103
+ dtype=float,
104
+ )
105
+ L = float(np.max(edges))
106
+ i = int(np.argmax(edges))
107
+
108
+ A, B = tri_pts[i], tri_pts[(i + 1) % 3]
109
+ C = tri_pts[(i + 2) % 3]
110
+ area = 0.5 * np.linalg.norm(np.cross(B - A, C - A))
111
+ if area <= 0.0:
112
+ raise ValueError("Degenerate triangle: zero area.")
113
+ h = 2.0 * area / L
114
+ return L / h
115
+
116
+
117
+ def _signed_distances(
118
+ verts: list[list[float]],
119
+ face: list[int],
120
+ normal: np.ndarray,
121
+ origin: np.ndarray,
122
+ ) -> np.ndarray:
123
+ """Evaluate signed distances of face vertices to a clipping plane.
124
+
125
+ Parameters
126
+ ----------
127
+ verts : list of list of float
128
+ All mesh vertices.
129
+ face : list of int
130
+ Indices of the vertices forming the face.
131
+ normal : numpy.ndarray
132
+ Plane normal vector.
133
+ origin : numpy.ndarray
134
+ Point belonging to the plane.
135
+
136
+ Returns
137
+ -------
138
+ numpy.ndarray
139
+ Signed distances for the vertices belonging to ``face``.
140
+ """
141
+ pts = np.asarray([verts[i] for i in face], dtype=float)
142
+ return (origin - pts) @ normal
143
+
144
+
145
+ def _compute_keep_sets(
146
+ verts: list[list[float]],
147
+ face: list[int],
148
+ normal: np.ndarray,
149
+ origin: np.ndarray,
150
+ tol: float,
151
+ ) -> tuple[list[int], set[int], np.ndarray]:
152
+ """Split vertices of a face between kept and discarded sets.
153
+
154
+ Parameters
155
+ ----------
156
+ verts : list of list of float
157
+ All mesh vertices.
158
+ face : list of int
159
+ Indices forming the face currently being clipped.
160
+ normal : numpy.ndarray
161
+ Plane normal vector defining the clipping plane.
162
+ origin : numpy.ndarray
163
+ Point on the clipping plane.
164
+ tol : float
165
+ Tolerance used to consider vertices inside the kept half-space.
166
+
167
+ Returns
168
+ -------
169
+ list of int
170
+ Indices of vertices that remain after clipping.
171
+ set of int
172
+ Indices of vertices removed by the clipping plane.
173
+ numpy.ndarray
174
+ Signed distance of each face vertex to the plane.
175
+ """
176
+ s = _signed_distances(verts, face, normal, origin)
177
+ mask = s >= -tol
178
+ keep = [face[i] for i, m in enumerate(mask) if m]
179
+ unkeep = set(face) - set(keep)
180
+ return keep, unkeep, s
181
+
182
+
183
+ def _compute_edge_intersections(
184
+ verts: list[list[float]],
185
+ face: list[int],
186
+ keep: list[int],
187
+ normal: np.ndarray,
188
+ origin: np.ndarray,
189
+ tol: float,
190
+ ) -> dict[tuple[int, int], int]:
191
+ """Intersect face edges with the clipping plane.
192
+
193
+ Parameters
194
+ ----------
195
+ verts : list of list of float
196
+ Mutable list of mesh vertices; intersection points are appended here.
197
+ face : list of int
198
+ Indices of the vertices forming the face being processed.
199
+ keep : list of int
200
+ Vertices that remain inside the kept half-space.
201
+ normal : numpy.ndarray
202
+ Plane normal vector.
203
+ origin : numpy.ndarray
204
+ Point belonging to the plane.
205
+ tol : float
206
+ Tolerance for plane intersection checks.
207
+
208
+ Returns
209
+ -------
210
+ dict[tuple[int, int], int]
211
+ Mapping from directed edges to newly created vertex indices.
212
+ """
213
+ keep_set = set(keep)
214
+ edges = list(zip(face, face[1:] + face[:1]))
215
+ cut_edges = [(i, j) for (i, j) in edges if (i in keep_set) ^ (j in keep_set)]
216
+
217
+ edge_inters: dict[tuple[int, int], int] = {}
218
+ for i, j in cut_edges:
219
+ ip = _get_intersection(
220
+ np.array(verts[i]), np.array(verts[j]), normal, origin, tol
221
+ )
222
+ idx = len(verts)
223
+ verts.append(ip.tolist())
224
+ edge_inters[(i, j)] = idx
225
+ return edge_inters
226
+
227
+
228
+ def _build_clipped_boundary(
229
+ face: list[int],
230
+ keep: list[int],
231
+ edge_inters: dict[tuple[int, int], int],
232
+ ) -> list[int]:
233
+ """Assemble the boundary indices for a clipped polygon.
234
+
235
+ Parameters
236
+ ----------
237
+ face : list of int
238
+ Original face indices.
239
+ keep : list of int
240
+ Vertices that remain after clipping.
241
+ edge_inters : dict[tuple[int, int], int]
242
+ Mapping from edges to newly created intersection vertices.
243
+
244
+ Returns
245
+ -------
246
+ list of int
247
+ Ordered vertex indices describing the clipped boundary.
248
+ """
249
+ keep_set = set(keep)
250
+ boundary: list[int] = []
251
+ for i, j in zip(face, face[1:] + face[:1]):
252
+ if i in keep_set:
253
+ boundary.append(i)
254
+ if (i, j) in edge_inters:
255
+ boundary.append(edge_inters[(i, j)])
256
+ return boundary
257
+
258
+
259
+ def clip_faces(
260
+ vertices: np.ndarray,
261
+ faces: list[list[int]],
262
+ normal: np.ndarray,
263
+ origin: np.ndarray,
264
+ tol: float = 1e-8,
265
+ ) -> Tuple[np.ndarray, List[List[int]], np.ndarray]:
266
+ """Clip faces of a mesh against a plane.
267
+
268
+ The kept half-space is defined by ``(v - origin) · normal >= -tol``.
269
+
270
+ Parameters
271
+ ----------
272
+ vertices : numpy.ndarray
273
+ Input vertex positions of shape ``(n, 3)``.
274
+ faces : list of list of int
275
+ Face connectivity; triangles and quads are supported.
276
+ normal : numpy.ndarray
277
+ Normal vector of the clipping plane.
278
+ origin : numpy.ndarray
279
+ Point located on the plane.
280
+ tol : float, default=1e-8
281
+ Tolerance for classifying vertices relative to the plane.
282
+
283
+ Returns
284
+ -------
285
+ np.ndarray of floats of shape (new_nb_vertices, 3)
286
+ The pruned vertex array
287
+ list of list of int
288
+ The list of of length new_nb_faces with clipped faces
289
+ np.ndarray of ints of shape (new_nb_faces,)
290
+ For each new face, the index of the face it comes from in the input.
291
+ """
292
+ normal = np.asarray(normal, dtype=float)
293
+ origin = np.asarray(origin, dtype=float)
294
+
295
+ verts: List[List[float]] = vertices.astype(float).tolist()
296
+ new_faces: List[Tuple[int, List[int]]] = []
297
+ # A new face is a tuple storing the index of the parent face in the
298
+ # original mesh and a list of vertices
299
+ dropped_vs: Set[int] = set()
300
+
301
+ for i_face, face in enumerate(faces):
302
+ keep, unkeep, _ = _compute_keep_sets(verts, face, normal, origin, tol)
303
+ dropped_vs.update(unkeep)
304
+
305
+ if len(keep) == 0:
306
+ continue # fully outside
307
+ if len(keep) == len(face):
308
+ # Face fully inside → keep original (quad or triangle unchanged)
309
+ new_faces.append((i_face, list(face)))
310
+ continue
311
+
312
+ edge_inters = _compute_edge_intersections(
313
+ verts, face, keep, normal, origin, tol
314
+ )
315
+ boundary = _build_clipped_boundary(face, keep, edge_inters)
316
+
317
+ if len(boundary) == 3:
318
+ new_faces.append((i_face, boundary))
319
+ elif len(boundary) == 4:
320
+ # clipped quad → 2 triangles
321
+ new_faces.append((i_face, [boundary[0], boundary[1], boundary[2]]))
322
+ new_faces.append((i_face, [boundary[0], boundary[2], boundary[3]]))
323
+ elif len(boundary) == 5:
324
+ # pentagon → 1 triangle + 1 quad
325
+ tri = [boundary[0], boundary[1], boundary[2]]
326
+ quad = [boundary[0], boundary[2], boundary[3], boundary[4]]
327
+ new_faces.append((i_face, tri))
328
+ new_faces.append((i_face, quad))
329
+ else:
330
+ # fallback: fan triangulation
331
+ for k in range(1, len(boundary) - 1):
332
+ new_faces.append((i_face, [boundary[0], boundary[k], boundary[k + 1]]))
333
+
334
+ if not new_faces:
335
+ return np.empty((0, 3), dtype=float), [], np.empty((0,), dtype=int)
336
+
337
+ used = {idx for (_, f) in new_faces for idx in f}
338
+ dropped_vs -= used
339
+ keep_vs = [i for i in range(len(verts)) if i not in dropped_vs]
340
+ remap = {old: new for new, old in enumerate(keep_vs)}
341
+
342
+ pruned_verts = np.asarray([verts[i] for i in keep_vs], dtype=float)
343
+ pruned_faces = [[remap[i] for i in face] for (_, face) in new_faces]
344
+
345
+ parent_of_face = np.array([i_parent_face for (i_parent_face, _) in new_faces])
346
+
347
+ return pruned_verts, pruned_faces, parent_of_face
@@ -0,0 +1,89 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+
4
+ from capytaine.tools.optional_imports import import_optional_dependency
5
+
6
+
7
+ def export_mesh(mesh, format: str):
8
+ format = format.lower()
9
+ if format == "pyvista":
10
+ return export_to_pyvista(mesh)
11
+ elif format == "xarray":
12
+ return export_to_xarray(mesh)
13
+ elif format == "meshio":
14
+ return export_to_meshio(mesh)
15
+ elif format == "trimesh":
16
+ return export_to_trimesh(mesh)
17
+ else:
18
+ raise ValueError(f"Unrecognized output format: {format}")
19
+
20
+
21
+ def export_to_pyvista(mesh):
22
+ """
23
+ Build a PyVista UnstructuredGrid from a list of faces (triangles or quads).
24
+ """
25
+ pv = import_optional_dependency("pyvista")
26
+
27
+ # flatten into the VTK cell‐array format: [n0, i0, i1, ..., in-1, n1, j0, j1, ...]
28
+ flat_cells = []
29
+ cell_types = []
30
+ for face in mesh._faces:
31
+ n = len(face)
32
+ flat_cells.append(n)
33
+ flat_cells.extend(face)
34
+ if n == 3:
35
+ cell_types.append(pv.CellType.TRIANGLE)
36
+ elif n == 4:
37
+ cell_types.append(pv.CellType.QUAD)
38
+ else:
39
+ # if you ever have ngons, you can map them as POLYGON:
40
+ cell_types.append(pv.CellType.POLYGON)
41
+
42
+ cells_array = np.array(flat_cells, dtype=np.int64)
43
+ cell_types = np.array(cell_types, dtype=np.uint8)
44
+
45
+ return pv.UnstructuredGrid(cells_array, cell_types, mesh.vertices.astype(np.float32))
46
+
47
+
48
+ def export_to_xarray(mesh):
49
+ return xr.Dataset(
50
+ {
51
+ "mesh_vertices": (
52
+ ["face", "vertices_of_face", "space_coordinate"],
53
+ mesh.as_array_of_faces()
54
+ )
55
+ },
56
+ coords={
57
+ "space_coordinate": ["x", "y", "z"],
58
+ })
59
+
60
+
61
+ def export_to_meshio(mesh):
62
+ meshio = import_optional_dependency("meshio")
63
+
64
+ quads = [f for f in mesh._faces if len(f) == 4]
65
+ tris = [f for f in mesh._faces if len(f) == 3]
66
+
67
+ cells = []
68
+ if quads:
69
+ cells.append(meshio.CellBlock("quad", np.array(quads, dtype=np.int32)))
70
+ if tris:
71
+ cells.append(meshio.CellBlock("triangle", np.array(tris, dtype=np.int32)))
72
+
73
+ return meshio.Mesh(points=mesh.vertices, cells=cells)
74
+
75
+
76
+ def export_to_trimesh(mesh):
77
+ trimesh = import_optional_dependency("trimesh")
78
+ triangle_faces = []
79
+ for face in mesh._faces:
80
+ if len(face) == 4 and face[3] != face[2]:
81
+ triangle_faces.append([face[0], face[1], face[2]])
82
+ triangle_faces.append([face[0], face[2], face[3]])
83
+ else:
84
+ triangle_faces.append(face[:3])
85
+ return trimesh.Trimesh(
86
+ vertices=mesh.vertices,
87
+ faces=np.array(triangle_faces),
88
+ process=False
89
+ )