capytaine 2.3.1__cp310-cp310-macosx_14_0_arm64.whl → 3.0.0a1__cp310-cp310-macosx_14_0_arm64.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 (82) hide show
  1. capytaine/__about__.py +7 -2
  2. capytaine/__init__.py +8 -12
  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.cpython-310-darwin.so → Delhommeau_float32.cpython-310-darwin.so} +0 -0
  11. capytaine/green_functions/{libs/Delhommeau_float64.cpython-310-darwin.so → Delhommeau_float64.cpython-310-darwin.so} +0 -0
  12. capytaine/green_functions/abstract_green_function.py +2 -2
  13. capytaine/green_functions/delhommeau.py +31 -16
  14. capytaine/green_functions/hams.py +19 -13
  15. capytaine/io/legacy.py +3 -103
  16. capytaine/io/xarray.py +11 -6
  17. capytaine/meshes/__init__.py +2 -6
  18. capytaine/meshes/abstract_meshes.py +375 -0
  19. capytaine/meshes/clean.py +302 -0
  20. capytaine/meshes/clip.py +347 -0
  21. capytaine/meshes/export.py +89 -0
  22. capytaine/meshes/geometry.py +244 -394
  23. capytaine/meshes/io.py +433 -0
  24. capytaine/meshes/meshes.py +617 -681
  25. capytaine/meshes/predefined/cylinders.py +22 -56
  26. capytaine/meshes/predefined/rectangles.py +26 -85
  27. capytaine/meshes/predefined/spheres.py +4 -11
  28. capytaine/meshes/quality.py +118 -407
  29. capytaine/meshes/surface_integrals.py +48 -29
  30. capytaine/meshes/symmetric_meshes.py +641 -0
  31. capytaine/meshes/visualization.py +353 -0
  32. capytaine/post_pro/free_surfaces.py +1 -4
  33. capytaine/post_pro/kochin.py +10 -10
  34. capytaine/tools/block_circulant_matrices.py +275 -0
  35. capytaine/tools/lists_of_points.py +2 -2
  36. capytaine/tools/memory_monitor.py +45 -0
  37. capytaine/tools/symbolic_multiplication.py +13 -1
  38. capytaine/tools/timer.py +58 -34
  39. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
  40. capytaine-3.0.0a1.dist-info/RECORD +65 -0
  41. capytaine/bodies/predefined/__init__.py +0 -6
  42. capytaine/bodies/predefined/cylinders.py +0 -151
  43. capytaine/bodies/predefined/rectangles.py +0 -111
  44. capytaine/bodies/predefined/spheres.py +0 -70
  45. capytaine/green_functions/FinGreen3D/.gitignore +0 -1
  46. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
  47. capytaine/green_functions/FinGreen3D/LICENSE +0 -165
  48. capytaine/green_functions/FinGreen3D/Makefile +0 -16
  49. capytaine/green_functions/FinGreen3D/README.md +0 -24
  50. capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
  51. capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
  52. capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
  53. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
  54. capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
  55. capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
  56. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
  57. capytaine/green_functions/libs/__init__.py +0 -0
  58. capytaine/io/mesh_loaders.py +0 -1086
  59. capytaine/io/mesh_writers.py +0 -692
  60. capytaine/io/meshio.py +0 -38
  61. capytaine/matrices/__init__.py +0 -16
  62. capytaine/matrices/block.py +0 -592
  63. capytaine/matrices/block_toeplitz.py +0 -325
  64. capytaine/matrices/builders.py +0 -89
  65. capytaine/matrices/linear_solvers.py +0 -232
  66. capytaine/matrices/low_rank.py +0 -395
  67. capytaine/meshes/clipper.py +0 -465
  68. capytaine/meshes/collections.py +0 -342
  69. capytaine/meshes/mesh_like_protocol.py +0 -37
  70. capytaine/meshes/properties.py +0 -276
  71. capytaine/meshes/quadratures.py +0 -80
  72. capytaine/meshes/symmetric.py +0 -462
  73. capytaine/tools/lru_cache.py +0 -49
  74. capytaine/ui/vtk/__init__.py +0 -3
  75. capytaine/ui/vtk/animation.py +0 -329
  76. capytaine/ui/vtk/body_viewer.py +0 -28
  77. capytaine/ui/vtk/helpers.py +0 -82
  78. capytaine/ui/vtk/mesh_viewer.py +0 -461
  79. capytaine-2.3.1.dist-info/RECORD +0 -92
  80. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
  81. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
  82. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
@@ -1,80 +0,0 @@
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
@@ -1,462 +0,0 @@
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
@@ -1,49 +0,0 @@
1
- # Copyright (C) 2017-2024 Matthieu Ancellin
2
- # See LICENSE file at <https://github.com/capytaine/capytaine>
3
- """Tools for memoization of functions."""
4
- from collections import OrderedDict
5
- from functools import wraps
6
-
7
- import logging
8
-
9
- LOG = logging.getLogger(__name__)
10
-
11
-
12
- def lru_cache_with_strict_maxsize(maxsize=1):
13
- """Behaves mostly like functools.lru_cache(), but the oldest data in the cache is
14
- deleted *before* computing a new one, in order to *never* have more that
15
- `maxsize` items in memory.
16
- This is useful to limit RAM usage when stored objects are big, like the interaction
17
- matrices of Capytaine."""
18
-
19
- def decorator(f):
20
- cache = OrderedDict()
21
-
22
- @wraps(f)
23
- def decorated_f(*args, **kwargs):
24
- hashable_kwargs = tuple((k, v) for (k, v) in kwargs.items())
25
- # Might miss a cache hit if the order of kwargs is changed.
26
- # But at least unlike a previous version, should not return a wrong value.
27
-
28
- if (args, hashable_kwargs) in cache:
29
- # Get item in cache
30
- LOG.debug("Get cached version of %s(%s, %s)", f.__name__, args, hashable_kwargs)
31
- return cache[(args, hashable_kwargs)]
32
-
33
- if len(cache) + 1 > maxsize:
34
- # Drop oldest item in cache.
35
- cache.popitem(last=False)
36
-
37
- # Compute and store
38
- LOG.debug("Computing %s(%s, %s)", f.__name__, args, hashable_kwargs)
39
- result = f(*args, **kwargs)
40
- cache[(args, hashable_kwargs)] = result
41
-
42
- return result
43
-
44
- return decorated_f
45
-
46
- return decorator
47
-
48
-
49
- delete_first_lru_cache = lru_cache_with_strict_maxsize # For backward compatibility...
@@ -1,3 +0,0 @@
1
- from capytaine.ui.vtk.mesh_viewer import MeshViewer
2
- from capytaine.ui.vtk.body_viewer import FloatingBodyViewer
3
- from capytaine.ui.vtk.animation import Animation