capytaine 2.3__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.
- capytaine/__about__.py +7 -2
- capytaine/__init__.py +11 -15
- capytaine/bem/engines.py +234 -354
- capytaine/bem/problems_and_results.py +30 -21
- capytaine/bem/solver.py +205 -81
- capytaine/bodies/bodies.py +279 -862
- capytaine/bodies/dofs.py +136 -9
- capytaine/bodies/hydrostatics.py +540 -0
- capytaine/bodies/multibodies.py +216 -0
- capytaine/green_functions/{libs/Delhommeau_float32.cp310-win_amd64.dll.a → Delhommeau_float32.cp310-win_amd64.dll.a} +0 -0
- capytaine/green_functions/Delhommeau_float32.cp310-win_amd64.pyd +0 -0
- capytaine/green_functions/{libs/Delhommeau_float64.cp310-win_amd64.dll.a → Delhommeau_float64.cp310-win_amd64.dll.a} +0 -0
- capytaine/green_functions/Delhommeau_float64.cp310-win_amd64.pyd +0 -0
- capytaine/green_functions/abstract_green_function.py +2 -2
- capytaine/green_functions/delhommeau.py +50 -31
- capytaine/green_functions/hams.py +19 -13
- capytaine/io/legacy.py +3 -103
- capytaine/io/xarray.py +15 -10
- capytaine/meshes/__init__.py +2 -6
- capytaine/meshes/abstract_meshes.py +375 -0
- capytaine/meshes/clean.py +302 -0
- capytaine/meshes/clip.py +347 -0
- capytaine/meshes/export.py +89 -0
- capytaine/meshes/geometry.py +244 -394
- capytaine/meshes/io.py +433 -0
- capytaine/meshes/meshes.py +621 -676
- capytaine/meshes/predefined/cylinders.py +22 -56
- capytaine/meshes/predefined/rectangles.py +26 -85
- capytaine/meshes/predefined/spheres.py +4 -11
- capytaine/meshes/quality.py +118 -407
- capytaine/meshes/surface_integrals.py +48 -29
- capytaine/meshes/symmetric_meshes.py +641 -0
- capytaine/meshes/visualization.py +353 -0
- capytaine/post_pro/free_surfaces.py +1 -4
- capytaine/post_pro/kochin.py +10 -10
- capytaine/tools/block_circulant_matrices.py +275 -0
- capytaine/tools/lists_of_points.py +2 -2
- capytaine/tools/memory_monitor.py +45 -0
- capytaine/tools/symbolic_multiplication.py +31 -5
- capytaine/tools/timer.py +68 -42
- capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
- {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +8 -14
- capytaine-3.0.0a1.dist-info/RECORD +70 -0
- capytaine/bodies/predefined/__init__.py +0 -6
- capytaine/bodies/predefined/cylinders.py +0 -151
- capytaine/bodies/predefined/rectangles.py +0 -111
- capytaine/bodies/predefined/spheres.py +0 -70
- capytaine/green_functions/FinGreen3D/.gitignore +0 -1
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
- capytaine/green_functions/FinGreen3D/LICENSE +0 -165
- capytaine/green_functions/FinGreen3D/Makefile +0 -16
- capytaine/green_functions/FinGreen3D/README.md +0 -24
- capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
- capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
- capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
- capytaine/green_functions/LiangWuNoblesse/Makefile +0 -18
- capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
- capytaine/green_functions/libs/Delhommeau_float32.cp310-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp310-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/mesh_loaders.py +0 -1086
- capytaine/io/mesh_writers.py +0 -692
- capytaine/io/meshio.py +0 -38
- capytaine/matrices/__init__.py +0 -16
- capytaine/matrices/block.py +0 -592
- capytaine/matrices/block_toeplitz.py +0 -325
- capytaine/matrices/builders.py +0 -89
- capytaine/matrices/linear_solvers.py +0 -232
- capytaine/matrices/low_rank.py +0 -395
- capytaine/meshes/clipper.py +0 -465
- capytaine/meshes/collections.py +0 -334
- capytaine/meshes/mesh_like_protocol.py +0 -37
- capytaine/meshes/properties.py +0 -276
- capytaine/meshes/quadratures.py +0 -80
- capytaine/meshes/symmetric.py +0 -392
- capytaine/tools/lru_cache.py +0 -49
- capytaine/ui/vtk/__init__.py +0 -3
- capytaine/ui/vtk/animation.py +0 -329
- capytaine/ui/vtk/body_viewer.py +0 -28
- capytaine/ui/vtk/helpers.py +0 -82
- capytaine/ui/vtk/mesh_viewer.py +0 -461
- capytaine-2.3.dist-info/DELVEWHEEL +0 -2
- capytaine-2.3.dist-info/RECORD +0 -97
- {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
- {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
- {capytaine-2.3.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
capytaine/meshes/quadratures.py
DELETED
|
@@ -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
|
capytaine/meshes/symmetric.py
DELETED
|
@@ -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
|
capytaine/tools/lru_cache.py
DELETED
|
@@ -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...
|
capytaine/ui/vtk/__init__.py
DELETED