capytaine 2.3__cp312-cp312-win_amd64.whl → 3.0.0a1__cp312-cp312-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 +30 -21
  5. capytaine/bem/solver.py +205 -81
  6. capytaine/bodies/bodies.py +279 -862
  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.cp312-win_amd64.dll.a → Delhommeau_float32.cp312-win_amd64.dll.a} +0 -0
  11. capytaine/green_functions/Delhommeau_float32.cp312-win_amd64.pyd +0 -0
  12. capytaine/green_functions/{libs/Delhommeau_float64.cp312-win_amd64.dll.a → Delhommeau_float64.cp312-win_amd64.dll.a} +0 -0
  13. capytaine/green_functions/Delhommeau_float64.cp312-win_amd64.pyd +0 -0
  14. capytaine/green_functions/abstract_green_function.py +2 -2
  15. capytaine/green_functions/delhommeau.py +50 -31
  16. capytaine/green_functions/hams.py +19 -13
  17. capytaine/io/legacy.py +3 -103
  18. capytaine/io/xarray.py +15 -10
  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 +621 -676
  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 +31 -5
  40. capytaine/tools/timer.py +68 -42
  41. capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
  42. {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +8 -14
  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 -18
  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.cp312-win_amd64.pyd +0 -0
  61. capytaine/green_functions/libs/Delhommeau_float64.cp312-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 -334
  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 -392
  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.dist-info/DELVEWHEEL +0 -2
  85. capytaine-2.3.dist-info/RECORD +0 -97
  86. {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
  87. {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
  88. {capytaine-2.3.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,392 +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):
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
- half_mesh = meshes[0].half.join_meshes(*(mesh.half for mesh in meshes[1:]), name=f"half_of_{name}" if name is not None else None)
93
- return ReflectionSymmetricMesh(half_mesh, plane=meshes[0].plane, name=name)
94
-
95
- @inplace_transformation
96
- def translate(self, vector):
97
- self.plane.translate(vector)
98
- CollectionOfMeshes.translate(self, vector)
99
- return self
100
-
101
- @inplace_transformation
102
- def rotate(self, axis: Axis, angle: float):
103
- self.plane.rotate(axis, angle)
104
- CollectionOfMeshes.rotate(self, axis, angle)
105
- return self
106
-
107
- @inplace_transformation
108
- def mirror(self, plane: Plane):
109
- self.plane.mirror(plane)
110
- CollectionOfMeshes.mirror(self, plane)
111
- return self
112
-
113
- def generate_lid(self, z=0.0, faces_max_radius=None, name=None):
114
- if name is None:
115
- name = "lid for {}".format(self.name)
116
- return ReflectionSymmetricMesh(self.half.generate_lid(z, faces_max_radius), self.plane, name=name)
117
-
118
- def extract_lid(self, plane=xOy_Plane):
119
- hull, lid = self.half.extract_lid(plane)
120
- return ReflectionSymmetricMesh(hull, self.plane), ReflectionSymmetricMesh(lid, self.plane)
121
-
122
-
123
- class TranslationalSymmetricMesh(SymmetricMesh):
124
- """A mesh with a repeating pattern by translation.
125
-
126
- Parameters
127
- ----------
128
- mesh_slice : Mesh or CollectionOfMeshes
129
- the pattern that will be repeated to form the whole body
130
- translation : array(3)
131
- the vector of the translation
132
- nb_repetitions : int, optional
133
- the number of repetitions of the pattern (excluding the original one, default: 1)
134
- name : str, optional
135
- a name for the mesh
136
- """
137
-
138
- def __init__(self, mesh_slice: Union[Mesh, CollectionOfMeshes], translation, nb_repetitions=1, name=None):
139
- assert isinstance(mesh_slice, Mesh) or isinstance(mesh_slice, CollectionOfMeshes)
140
- assert isinstance(nb_repetitions, int)
141
- assert nb_repetitions >= 1
142
-
143
- translation = np.asarray(translation).copy()
144
- assert translation.shape == (3,)
145
- assert translation[2] == 0 # Only horizontal translation are supported.
146
-
147
- slices = [mesh_slice]
148
- for i in range(1, nb_repetitions+1):
149
- slices.append(mesh_slice.translated(vector=i*translation, name=f"repetition_{i}_of_{mesh_slice.name}"))
150
-
151
- if name is None:
152
- name = f"translation_of_{mesh_slice.name}"
153
-
154
- self.translation = translation
155
-
156
- super().__init__(slices, name=name)
157
-
158
- if self.name is not None:
159
- LOG.debug(f"New translation symmetric mesh: {self.name}.")
160
- else:
161
- LOG.debug(f"New translation symmetric mesh.")
162
-
163
- @property
164
- def first_slice(self):
165
- return self[0]
166
-
167
- def __str__(self):
168
- return f"{self.__class__.__name__}({self.first_slice.__short_str__()}, translation={self.translation}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
169
-
170
- def __repr__(self):
171
- return f"{self.__class__.__name__}({self.first_slice}, translation={self.translation}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
172
-
173
- def __rich_repr__(self):
174
- yield self.first_slice
175
- yield "translation", self.translation
176
- yield "nb_repetitions", len(self)-1
177
- yield "name", self.name
178
-
179
- def tree_view(self, fold_symmetry=True, **kwargs):
180
- if fold_symmetry:
181
- return (self.__short_str__() + '\n' + ' ├─' + self.first_slice.tree_view().replace('\n', '\n │ ') + '\n'
182
- + f" └─{len(self)-1} translated copies of the above {self.first_slice.__short_str__()}")
183
- else:
184
- return CollectionOfMeshes.tree_view(self, **kwargs)
185
-
186
- def __deepcopy__(self, *args):
187
- return TranslationalSymmetricMesh(self.first_slice.copy(), self.translation, nb_repetitions=len(self) - 1, name=self.name)
188
-
189
- @inplace_transformation
190
- def translate(self, vector):
191
- CollectionOfMeshes.translate(self, vector)
192
- return self
193
-
194
- @inplace_transformation
195
- def rotate(self, axis: Axis, angle: float):
196
- self.translation = axis.rotate_vectors([self.translation], angle)[0, :]
197
- CollectionOfMeshes.rotate(self, axis, angle)
198
- return self
199
-
200
- @inplace_transformation
201
- def mirror(self, plane: Plane):
202
- self.translation -= 2 * (self.translation @ plane.normal) * plane.normal
203
- CollectionOfMeshes.mirror(self, plane)
204
- return self
205
-
206
- def join_meshes(*meshes, name=None):
207
- assert all(isinstance(mesh, TranslationalSymmetricMesh) for mesh in meshes), \
208
- "Only meshes with the same symmetry can be joined together."
209
- assert all(np.allclose(meshes[0].translation, mesh.translation) for mesh in meshes), \
210
- "Only translation symmetric meshes with the same translation vector can be joined together."
211
- assert all(len(meshes[0]) == len(mesh) for mesh in meshes), \
212
- "Only symmetric meshes with the same number of elements can be joined together."
213
- mesh_strip = CollectionOfMeshes([mesh.first_slice for mesh in meshes], name=f"strip_of_{name}" if name is not None else None)
214
- return TranslationalSymmetricMesh(mesh_strip, translation=meshes[0].translation, nb_repetitions=len(meshes[0]) - 1, name=name)
215
-
216
-
217
- def build_regular_array_of_meshes(base_mesh, distance, nb_bodies):
218
- """Create an array of objects using TranslationalSymmetries.
219
-
220
- Parameters
221
- ----------
222
- base_mesh : Mesh or CollectionOfMeshes or SymmetricMesh
223
- The mesh to duplicate to create the array
224
- distance : float
225
- Center-to-center distance between objects in the array
226
- nb_bodies : couple of ints
227
- Number of objects in the x and y directions.
228
-
229
- Returns
230
- -------
231
- TranslationalSymmetricMesh
232
- """
233
- if nb_bodies[0] == 1:
234
- line = base_mesh
235
- else:
236
- line = TranslationalSymmetricMesh(base_mesh, translation=(distance, 0.0, 0.0), nb_repetitions=nb_bodies[0] - 1,
237
- name=f'line_of_{base_mesh.name}')
238
- if nb_bodies[1] == 1:
239
- array = line
240
- else:
241
- array = TranslationalSymmetricMesh(line, translation=(0.0, distance, 0.0), nb_repetitions=nb_bodies[1] - 1,
242
- name=f'array_of_{base_mesh.name}')
243
- return array
244
-
245
-
246
- class AxialSymmetricMesh(SymmetricMesh):
247
- """A mesh with a repeating pattern by rotation.
248
-
249
- Parameters
250
- ----------
251
- mesh_slice : Mesh or CollectionOfMeshes
252
- the pattern that will be repeated to form the whole body
253
- axis : Axis, optional
254
- symmetry axis
255
- nb_repetitions : int, optional
256
- the number of repetitions of the pattern (excluding the original one, default: 1)
257
- name : str, optional
258
- a name for the mesh
259
- """
260
- def __init__(self, mesh_slice: Union[Mesh, CollectionOfMeshes], axis: Axis=Oz_axis, nb_repetitions: int=1, name=None):
261
- assert isinstance(mesh_slice, Mesh) or isinstance(mesh_slice, CollectionOfMeshes)
262
- assert isinstance(nb_repetitions, int)
263
- assert nb_repetitions >= 1
264
- assert isinstance(axis, Axis)
265
-
266
- slices = [mesh_slice]
267
- for i in range(1, nb_repetitions+1):
268
- slices.append(mesh_slice.rotated(axis, angle=2*i*np.pi/(nb_repetitions+1),
269
- name=f"rotation_{i}_of_{mesh_slice.name}"))
270
-
271
- if name is None:
272
- name = f"rotation_of_{mesh_slice.name}"
273
-
274
- self.axis = axis.copy()
275
-
276
- super().__init__(slices, name=name)
277
-
278
- if not axis.is_parallel_to(Oz_axis):
279
- LOG.warning(f"{self.name} is an axi-symmetric mesh along a non vertical axis.")
280
-
281
- if self.name is not None:
282
- LOG.debug(f"New rotation symmetric mesh: {self.name}.")
283
- else:
284
- LOG.debug(f"New rotation symmetric mesh.")
285
-
286
- @staticmethod
287
- def from_profile(profile: Union[Callable, Iterable[float]],
288
- z_range: Iterable[float]=np.linspace(-5, 0, 20),
289
- axis: Axis=Oz_axis,
290
- nphi: int=20,
291
- name=None):
292
- """Return a floating body using the axial symmetry.
293
- 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.
294
- Alternatively, the profile can be defined as a list of points.
295
- The number of vertices along the vertical direction is len(z_range) in the first case and profile.shape[0] in the second case.
296
-
297
- Parameters
298
- ----------
299
- profile : function(float → float) or array(N, 3)
300
- define the shape of the body either as a function or a list of points.
301
- z_range: array(N), optional
302
- used only if the profile is defined as a function.
303
- axis : Axis
304
- symmetry axis
305
- nphi : int, optional
306
- number of vertical slices forming the body
307
- name : str, optional
308
- name of the generated body (optional)
309
-
310
- Returns
311
- -------
312
- AxialSymmetricMesh
313
- the generated mesh
314
- """
315
-
316
- if name is None:
317
- name = "axisymmetric_mesh"
318
-
319
- if callable(profile):
320
- z_range = np.asarray(z_range)
321
- x_values = [profile(z) for z in z_range]
322
- profile_array = np.stack([x_values, np.zeros(len(z_range)), z_range]).T
323
- else:
324
- profile_array = np.asarray(profile)
325
-
326
- assert len(profile_array.shape) == 2
327
- assert profile_array.shape[1] == 3
328
-
329
- n = profile_array.shape[0]
330
- angle = 2 * np.pi / nphi
331
-
332
- nodes_slice = np.concatenate([profile_array, axis.rotate_points(profile_array, angle)])
333
- faces_slice = np.array([[i, i+n, i+n+1, i+1] for i in range(n-1)])
334
- body_slice = Mesh(nodes_slice, faces_slice, name=f"slice_of_{name}")
335
- body_slice.merge_duplicates()
336
- body_slice.heal_triangles()
337
-
338
- return AxialSymmetricMesh(body_slice, axis=axis, nb_repetitions=nphi - 1, name=name)
339
-
340
- @property
341
- def first_slice(self):
342
- return self[0]
343
-
344
- def __str__(self):
345
- return f"{self.__class__.__name__}({self.first_slice.__short_str__()}, axis={self.axis}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
346
-
347
- def __repr__(self):
348
- return f"{self.__class__.__name__}({self.first_slice}, axis={self.axis}, nb_repetitions={len(self)-1}, name=\"{self.name}\")"
349
-
350
- def __rich_repr__(self):
351
- yield self.first_slice
352
- yield "axis", self.axis
353
- yield "nb_repetitions", len(self)-1
354
- yield "name", self.name
355
-
356
- def tree_view(self, fold_symmetry=True, **kwargs):
357
- if fold_symmetry:
358
- return (self.__short_str__() + '\n' + ' ├─' + self.first_slice.tree_view().replace('\n', '\n │ ') + '\n'
359
- + f" └─{len(self)-1} rotated copies of the above {self.first_slice.__short_str__()}")
360
- else:
361
- return CollectionOfMeshes.tree_view(self, **kwargs)
362
-
363
- def __deepcopy__(self, *args):
364
- return AxialSymmetricMesh(self.first_slice.copy(), axis=self.axis.copy(), nb_repetitions=len(self) - 1, name=self.name)
365
-
366
- def join_meshes(*meshes, name=None):
367
- assert all(isinstance(mesh, AxialSymmetricMesh) for mesh in meshes), \
368
- "Only meshes with the same symmetry can be joined together."
369
- assert all(meshes[0].axis == mesh.axis for mesh in meshes), \
370
- "Only axisymmetric meshes with the same symmetry axis can be joined together."
371
- assert all(len(meshes[0]) == len(mesh) for mesh in meshes), \
372
- "Only axisymmetric meshes with the same number of elements can be joined together."
373
- mesh_slice = CollectionOfMeshes([mesh.first_slice for mesh in meshes], name=f"slice_of_{name}" if name is not None else None)
374
- return AxialSymmetricMesh(mesh_slice, axis=meshes[0].axis, nb_repetitions=len(meshes[0]) - 1, name=name)
375
-
376
- @inplace_transformation
377
- def translate(self, vector):
378
- self.axis.translate(vector)
379
- CollectionOfMeshes.translate(self, vector)
380
- return self
381
-
382
- @inplace_transformation
383
- def rotate(self, other_axis: Axis, angle: float):
384
- self.axis.rotate(other_axis, angle)
385
- CollectionOfMeshes.rotate(self, other_axis, angle)
386
- return self
387
-
388
- @inplace_transformation
389
- def mirror(self, plane: Plane):
390
- self.axis.mirror(plane)
391
- CollectionOfMeshes.mirror(self, plane)
392
- 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