capytaine 3.0.0a1__cp38-cp38-macosx_15_0_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. capytaine/.dylibs/libgfortran.5.dylib +0 -0
  3. capytaine/.dylibs/libquadmath.0.dylib +0 -0
  4. capytaine/__about__.py +21 -0
  5. capytaine/__init__.py +32 -0
  6. capytaine/bem/__init__.py +0 -0
  7. capytaine/bem/airy_waves.py +111 -0
  8. capytaine/bem/engines.py +321 -0
  9. capytaine/bem/problems_and_results.py +601 -0
  10. capytaine/bem/solver.py +718 -0
  11. capytaine/bodies/__init__.py +4 -0
  12. capytaine/bodies/bodies.py +630 -0
  13. capytaine/bodies/dofs.py +146 -0
  14. capytaine/bodies/hydrostatics.py +540 -0
  15. capytaine/bodies/multibodies.py +216 -0
  16. capytaine/green_functions/Delhommeau_float32.cpython-38-darwin.so +0 -0
  17. capytaine/green_functions/Delhommeau_float64.cpython-38-darwin.so +0 -0
  18. capytaine/green_functions/__init__.py +2 -0
  19. capytaine/green_functions/abstract_green_function.py +64 -0
  20. capytaine/green_functions/delhommeau.py +522 -0
  21. capytaine/green_functions/hams.py +210 -0
  22. capytaine/io/__init__.py +0 -0
  23. capytaine/io/bemio.py +153 -0
  24. capytaine/io/legacy.py +228 -0
  25. capytaine/io/wamit.py +479 -0
  26. capytaine/io/xarray.py +673 -0
  27. capytaine/meshes/__init__.py +2 -0
  28. capytaine/meshes/abstract_meshes.py +375 -0
  29. capytaine/meshes/clean.py +302 -0
  30. capytaine/meshes/clip.py +347 -0
  31. capytaine/meshes/export.py +89 -0
  32. capytaine/meshes/geometry.py +259 -0
  33. capytaine/meshes/io.py +433 -0
  34. capytaine/meshes/meshes.py +826 -0
  35. capytaine/meshes/predefined/__init__.py +6 -0
  36. capytaine/meshes/predefined/cylinders.py +280 -0
  37. capytaine/meshes/predefined/rectangles.py +202 -0
  38. capytaine/meshes/predefined/spheres.py +55 -0
  39. capytaine/meshes/quality.py +159 -0
  40. capytaine/meshes/surface_integrals.py +82 -0
  41. capytaine/meshes/symmetric_meshes.py +641 -0
  42. capytaine/meshes/visualization.py +353 -0
  43. capytaine/post_pro/__init__.py +6 -0
  44. capytaine/post_pro/free_surfaces.py +85 -0
  45. capytaine/post_pro/impedance.py +92 -0
  46. capytaine/post_pro/kochin.py +54 -0
  47. capytaine/post_pro/rao.py +60 -0
  48. capytaine/tools/__init__.py +0 -0
  49. capytaine/tools/block_circulant_matrices.py +275 -0
  50. capytaine/tools/cache_on_disk.py +26 -0
  51. capytaine/tools/deprecation_handling.py +18 -0
  52. capytaine/tools/lists_of_points.py +52 -0
  53. capytaine/tools/memory_monitor.py +45 -0
  54. capytaine/tools/optional_imports.py +27 -0
  55. capytaine/tools/prony_decomposition.py +150 -0
  56. capytaine/tools/symbolic_multiplication.py +161 -0
  57. capytaine/tools/timer.py +90 -0
  58. capytaine/ui/__init__.py +0 -0
  59. capytaine/ui/cli.py +28 -0
  60. capytaine/ui/rich.py +5 -0
  61. capytaine-3.0.0a1.dist-info/LICENSE +674 -0
  62. capytaine-3.0.0a1.dist-info/METADATA +755 -0
  63. capytaine-3.0.0a1.dist-info/RECORD +65 -0
  64. capytaine-3.0.0a1.dist-info/WHEEL +4 -0
  65. capytaine-3.0.0a1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,6 @@
1
+ # Copyright (C) 2017-2022 Matthieu Ancellin
2
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
3
+
4
+ from capytaine.meshes.predefined.cylinders import mesh_disk, mesh_horizontal_cylinder, mesh_vertical_cylinder
5
+ from capytaine.meshes.predefined.spheres import mesh_sphere
6
+ from capytaine.meshes.predefined.rectangles import mesh_rectangle, mesh_parallelepiped
@@ -0,0 +1,280 @@
1
+ """Generate meshes of cylinders and disks"""
2
+ # Copyright (C) 2017-2025 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
4
+
5
+ import logging
6
+ from itertools import product
7
+
8
+ import numpy as np
9
+ from numpy import pi, cos, sin
10
+
11
+ from capytaine.meshes.meshes import Mesh
12
+ from capytaine.meshes.symmetric_meshes import RotationSymmetricMesh, ReflectionSymmetricMesh
13
+
14
+ LOG = logging.getLogger(__name__)
15
+
16
+
17
+ def mesh_disk(*, radius=1.0, center=(0, 0, 0), normal=(0, 0, 1),
18
+ resolution=(3, 6), faces_max_radius=None,
19
+ reflection_symmetry=False, axial_symmetry=False, name=None, _theta_max=2*pi):
20
+ """(One-sided) disk.
21
+
22
+ Parameters
23
+ ----------
24
+ radius : float, optional
25
+ radius of the disk
26
+ center : 3-ple or array of shape (3,), optional
27
+ position of the geometric center of the disk
28
+ normal: 3-ple of floats, optional
29
+ normal vector, default: along x axis
30
+ resolution : 2-ple of int, optional
31
+ number of panels along a radius and around the disk
32
+ faces_max_radius : float, optional
33
+ maximal radius of a panel. (Default: no maximal radius.)
34
+ If the provided resolution is too coarse, the number of panels is
35
+ changed to fit the constraint on the maximal radius.
36
+ axial_symmetry : bool, optional
37
+ if True, returns an AxialSymmetricMesh
38
+ reflection_symmetry : bool, optional
39
+ if True, returns a ReflectionSymmetricMesh
40
+ name : str, optional
41
+ a string naming the mesh
42
+ _theta_max: float, optional
43
+ internal parameter, to return an arc circle instead of a full circle
44
+ """
45
+ assert radius > 0, "Radius of the disk mesh should be given as a positive value."
46
+
47
+ assert len(resolution) == 2, "Resolution of a disk should be given as a couple of values."
48
+ assert all([h > 0 for h in resolution]), "Resolution of the disk mesh should be given as positive values."
49
+ assert all([i == int(i) for i in resolution]), "Resolution of a disk should be given as integer values."
50
+
51
+ assert len(center) == 3, "Position of the center of a disk should be given a 3-ple of values."
52
+
53
+ nr, ntheta = resolution
54
+ if faces_max_radius is not None:
55
+ # The biggest cell is on the side of the disk.
56
+ # Assuming it is a rectangle of sides radius/nr and perimeter/ntheta
57
+ estimated_max_radius = np.hypot(radius/nr, 2*pi*radius/ntheta)/2
58
+ if estimated_max_radius > faces_max_radius:
59
+ nr = int(np.ceil(radius / (np.sqrt(2) * faces_max_radius)))
60
+ ntheta = int(np.ceil(2*pi*radius / (np.sqrt(2) * faces_max_radius)))
61
+
62
+ if reflection_symmetry and axial_symmetry:
63
+ raise NotImplementedError("Disks with both symmetries have not been implemented.")
64
+
65
+ LOG.debug(f"New disk of radius {radius} and resolution {resolution}, named {name}.")
66
+
67
+ if reflection_symmetry:
68
+ if ntheta % 2 == 1:
69
+ raise ValueError("To use the reflection symmetry of the mesh, "
70
+ "it should have an even number of panels in this direction.")
71
+
72
+ half_mesh = mesh_disk(radius=radius, _theta_max=_theta_max/2, resolution=(nr, ntheta//2),
73
+ center=(0, 0, 0), normal=(0, 0, 1),
74
+ reflection_symmetry=False, axial_symmetry=False, name=f"half_of_{name}")
75
+ mesh = ReflectionSymmetricMesh(half_mesh, plane='xOz', name=name)
76
+
77
+ elif axial_symmetry:
78
+ mesh_slice = mesh_disk(radius=radius, _theta_max=_theta_max/ntheta, resolution=(nr, 1),
79
+ center=(0, 0, 0), normal=(0, 0, 1),
80
+ reflection_symmetry=False, axial_symmetry=False, name=f"slice_of_{name}")
81
+ mesh = RotationSymmetricMesh(mesh_slice, axis='z+', n=ntheta, name=name)
82
+
83
+ else:
84
+ theta_range = np.linspace(0, _theta_max, ntheta+1)
85
+ r_range = np.linspace(0.0, radius, nr+1)
86
+ nodes = np.array([(0, r*sin(t), -r*cos(t)) for (r, t) in product(r_range, theta_range)])
87
+ panels = np.array([(j+i*(ntheta+1), j+1+i*(ntheta+1), j+1+(i+1)*(ntheta+1), j+(i+1)*(ntheta+1))
88
+ for (i, j) in product(range(0, nr), range(0, ntheta))])
89
+
90
+ mesh = Mesh(nodes, panels, name=name)
91
+
92
+ mesh = mesh.rotated_such_that_vectors_are_aligned(mesh.faces_normals[0], normal)
93
+ mesh = mesh.translated(center, name=name)
94
+ return mesh
95
+
96
+
97
+ def mesh_vertical_cylinder(*, length=10.0, radius=1.0, center=(0, 0, 0),
98
+ resolution=(2, 8, 10), faces_max_radius=None,
99
+ axial_symmetry=False, reflection_symmetry=False, name=None, _theta_max=2*pi):
100
+ """Vertical cylinder.
101
+
102
+ Total number of panels = (2*resolution[0] + resolution[2])*resolution[1]
103
+
104
+ Parameters
105
+ ----------
106
+ length : float, optional
107
+ length of the cylinder
108
+ radius : float, optional
109
+ radius of the cylinder
110
+ center : 3-ple or array of shape (3,), optional
111
+ position of the geometric center of the cylinder
112
+ resolution : 3-ple of int, optional
113
+ (number of panel along a radius at the end, number of panels around a slice, number of slices)
114
+ Mnemonic: same ordering as the cylindrical coordinates (nr, ntheta, nz)
115
+ faces_max_radius : float, optional
116
+ maximal radius of a panel. (Default: no maximal radius.)
117
+ If the provided resolution is too coarse, the number of panels is
118
+ changed to fit the constraint on the maximal radius.
119
+ axial_symmetry : bool, optional
120
+ if True, returns an AxialSymmetricMesh
121
+ reflection_symmetry : bool, optional
122
+ if True, returns a ReflectionSymmetricMesh
123
+ name : str, optional
124
+ a string naming the mesh
125
+ _theta_max: float, optional
126
+ internal parameter, to return an arc circle instead of a full circle
127
+ """
128
+ assert length > 0, "Length of a cylinder should be given as a positive value."
129
+ assert radius > 0, "Radius of a cylinder should be given as a positive value."
130
+
131
+ assert len(resolution) == 3, "Resolution of a cylinder should be given as a 3-ple of values."
132
+ assert all([h >= 0 for h in resolution]), "Resolution of a cylinder should be given as positive values."
133
+ assert all([i == int(i) for i in resolution]), "Resolution of a cylinder should be given as integer values."
134
+
135
+ assert len(center) == 3, "Position of the center of a cylinder should be given a 3-ple of values."
136
+
137
+ nr, ntheta, nz = resolution
138
+ if faces_max_radius is not None:
139
+ dr, dtheta, dz = radius/nr, 2*pi*radius/ntheta, length/nz
140
+ estimated_max_radius = max(
141
+ np.hypot(dr, dtheta)/2, # Panel on disk side
142
+ np.hypot(dtheta, dz)/2, # Panel on cylinder itself
143
+ )
144
+ if estimated_max_radius > faces_max_radius:
145
+ nr = int(np.ceil(radius / (np.sqrt(2) * faces_max_radius)))
146
+ ntheta = 2*int(np.ceil(pi*radius / (np.sqrt(2) * faces_max_radius)))
147
+ nz = int(np.ceil(length / (np.sqrt(2) * faces_max_radius)))
148
+
149
+ LOG.debug(f"New vertical cylinder of length {length}, radius {radius} and resolution {resolution}, named {name}.")
150
+
151
+ if reflection_symmetry and axial_symmetry:
152
+ raise NotImplementedError("Vertical cylinders with both symmetries have not been implemented.")
153
+
154
+ if reflection_symmetry:
155
+ if ntheta % 2 == 1:
156
+ raise ValueError("To use the reflection symmetry of the mesh, "
157
+ "it should have an even number of panels in this direction.")
158
+
159
+ half_cylinder = mesh_vertical_cylinder(length=length, radius=radius, center=(0, 0, 0),
160
+ resolution=(nr, ntheta//2, nz), reflection_symmetry=False, axial_symmetry=False,
161
+ name=f"half_{name}", _theta_max=_theta_max/2)
162
+
163
+ mesh = ReflectionSymmetricMesh(half_cylinder, plane='yOz', name=name)
164
+
165
+ elif axial_symmetry:
166
+
167
+ mesh_slice = mesh_vertical_cylinder(length=length, radius=radius, resolution=(nr, 1, nz), center=(0, 0, 0),
168
+ reflection_symmetry=False, axial_symmetry=False, name=f"slice_of_{name}", _theta_max=_theta_max/ntheta)
169
+ mesh = RotationSymmetricMesh(mesh_slice, axis='z+', n=ntheta, name=name)
170
+
171
+ else:
172
+ theta_range = np.linspace(0, _theta_max, ntheta+1)
173
+ z_range = np.linspace(-length/2, length/2, nz+1)
174
+ if nr > 0:
175
+ r_range = np.linspace(0.0, radius, nr+1)
176
+ nodes = np.concatenate([
177
+ np.array([(r*sin(t), r*cos(t), -length/2) for (r, t) in product(r_range, theta_range)]),
178
+ np.array([(radius*sin(t), radius*cos(t), z) for (z, t) in product(z_range, theta_range)]),
179
+ np.array([(r*sin(t), r*cos(t), length/2) for (r, t) in product(r_range[::-1], theta_range)]),
180
+ ])
181
+ else:
182
+ r_range = np.array([])
183
+ nodes = np.array([(radius*sin(t), radius*cos(t), z) for (z, t) in product(z_range, theta_range)])
184
+ panels = np.array([(j+i*(ntheta+1), j+(i+1)*(ntheta+1), j+1+(i+1)*(ntheta+1), j+1+i*(ntheta+1), )
185
+ for (i, j) in product(range(nz+2*(len(r_range))), range(ntheta))])
186
+
187
+ mesh = Mesh(nodes, panels, name=name)
188
+
189
+ mesh = mesh.translated(center, name=name)
190
+ return mesh
191
+
192
+
193
+ def mesh_horizontal_cylinder(*, length=10.0, radius=1.0, center=(0, 0, 0),
194
+ resolution=(2, 8, 10), faces_max_radius=None,
195
+ reflection_symmetry=False, name=None, _theta_max=2*pi):
196
+ """Cylinder aligned along Ox axis.
197
+
198
+ Total number of panels = (2*resolution[0] + resolution[2])*resolution[1]
199
+
200
+ Parameters
201
+ ----------
202
+ length : float, optional
203
+ length of the cylinder
204
+ radius : float, optional
205
+ radius of the cylinder
206
+ center : 3-ple or array of shape (3,), optional
207
+ position of the geometric center of the cylinder
208
+ resolution : 3-ple of int, optional
209
+ (number of panel along a radius at the end, number of panels around a slice, number of slices)
210
+ Mnemonic: same ordering as the cylindrical coordinates (nr, ntheta, nz)
211
+ faces_max_radius : float, optional
212
+ maximal radius of a panel. (Default: no maximal radius.)
213
+ If the provided resolution is too coarse, the number of panels is
214
+ changed to fit the constraint on the maximal radius.
215
+ reflection_symmetry : bool, optional
216
+ if True, returns a ReflectionSymmetricMesh
217
+ translation_symmetry : bool, optional
218
+ if True, uses a TranslationalSymmetricMesh internally for the main part of the cylinder
219
+ name : str, optional
220
+ a string naming the mesh
221
+ _theta_max: float, optional
222
+ internal parameter, to return an arc circle instead of a full circle
223
+ """
224
+
225
+ assert length > 0, "Length of a cylinder should be given as a positive value."
226
+ assert radius > 0, "Radius of a cylinder should be given as a positive value."
227
+
228
+ assert len(resolution) == 3, "Resolution of a cylinder should be given as a 3-ple of values."
229
+ assert all([h >= 0 for h in resolution]), "Resolution of a cylinder should be given as positive values."
230
+ assert all([i == int(i) for i in resolution]), "Resolution of a cylinder should be given as integer values."
231
+
232
+ assert len(center) == 3, "Position of the center of a cylinder should be given a 3-ple of values."
233
+
234
+ LOG.debug(f"New horizontal cylinder of length {length}, radius {radius} and resolution {resolution}, named {name}.")
235
+
236
+ nr, ntheta, nx = resolution
237
+ if faces_max_radius is not None:
238
+ dr, dtheta, dx = radius/nr, 2*pi*radius/ntheta, length/nx
239
+ estimated_max_radius = max(
240
+ np.hypot(dr, dtheta)/2, # Panel on disk side
241
+ np.hypot(dtheta, dx)/2, # Panel on cylinder itself
242
+ )
243
+ if estimated_max_radius > faces_max_radius:
244
+ nr = int(np.ceil(radius / (np.sqrt(2) * faces_max_radius)))
245
+ ntheta = 2*int(np.ceil(pi*radius / (np.sqrt(2) * faces_max_radius)))
246
+ nx = int(np.ceil(length / (np.sqrt(2) * faces_max_radius)))
247
+
248
+ if reflection_symmetry:
249
+ if ntheta % 2 == 1:
250
+ raise ValueError("To use the reflection symmetry of the mesh, "
251
+ "it should have an even number of panels in this direction.")
252
+
253
+ half_cylinder = mesh_horizontal_cylinder(
254
+ length=length, radius=radius, center=(0, 0, 0),
255
+ resolution=(nr, ntheta//2, nx),
256
+ reflection_symmetry=False,
257
+ name=f"half_{name}", _theta_max=_theta_max/2,
258
+ )
259
+
260
+ mesh = ReflectionSymmetricMesh(half_cylinder, plane='xOz', name=name)
261
+
262
+ else: # General case
263
+ theta_range = np.linspace(0, _theta_max, ntheta+1)
264
+ x_range = np.linspace(-length/2, length/2, nx+1)
265
+ nodes = np.array([(x, radius*sin(t), -radius*cos(t)) for (x, t) in product(x_range, theta_range)])
266
+
267
+ panels = np.array([(i+j*(ntheta+1), i+1+j*(ntheta+1), i+1+(j+1)*(ntheta+1), i+(j+1)*(ntheta+1))
268
+ for (i, j) in product(range(ntheta), range(nx))])
269
+
270
+ mesh = Mesh(nodes, panels, name=f"open_{name}")
271
+
272
+ if nr > 0:
273
+ side = mesh_disk(radius=radius, center=(-length/2, 0, 0), normal=(-1, 0, 0),
274
+ reflection_symmetry=False, resolution=(nr, ntheta), name=f"side_of_{name}",
275
+ _theta_max=_theta_max)
276
+ other_side = side.mirrored('yOz', name=f"other_side_of_{name}")
277
+ mesh = Mesh.join_meshes(mesh, side, other_side, name=name)
278
+
279
+ mesh = mesh.translated(center, name=name)
280
+ return mesh
@@ -0,0 +1,202 @@
1
+ """Generate rectangular bodies."""
2
+ # Copyright (C) 2017-2024 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
4
+
5
+ import logging
6
+ from itertools import product
7
+
8
+ import numpy as np
9
+
10
+ from capytaine.meshes.meshes import Mesh
11
+ from capytaine.meshes.symmetric_meshes import ReflectionSymmetricMesh
12
+
13
+ LOG = logging.getLogger(__name__)
14
+
15
+ def mesh_rectangle(*, size=(5.0, 5.0), center=(0.0, 0.0, 0.0),
16
+ resolution=(5, 5), faces_max_radius=None,
17
+ normal=(0.0, 0.0, 1.0),
18
+ reflection_symmetry=False,
19
+ name=None):
20
+ """One-sided rectangle.
21
+
22
+ By default, the rectangle is horizontal, the normals are oriented upwards.
23
+
24
+ Parameters
25
+ ----------
26
+ size : couple of floats, optional
27
+ dimensions of the rectangle (width and height)
28
+ center : 3-ple of floats, optional
29
+ position of the geometric center of the rectangle, default: (0, 0, 0)
30
+ resolution : couple of ints, optional
31
+ number of faces along each of the two directions
32
+ faces_max_radius : float, optional
33
+ maximal radius of a panel. (Default: no maximal radius.)
34
+ If the provided resolution is too coarse, the number of panels is
35
+ changed to fit the constraint on the maximal radius.
36
+ normal: 3-ple of floats, optional
37
+ normal vector, default: (0, 0, 1)
38
+ reflection_symmetry : bool, optional
39
+ if True, use the reflection symmetry to speed up the computations
40
+ name : string, optional
41
+ a name for the body
42
+ """
43
+
44
+ assert len(size) == 2, "Size of a rectangle should be given as a couple of values."
45
+ assert all([h > 0 for h in size]), "Size of the rectangle mesh should be given as positive values."
46
+
47
+ assert len(resolution) == 2, "Resolution of a rectangle should be given as a couple a values."
48
+ assert all([h > 0 for h in resolution]), "Resolution of the rectangle mesh should be given as positive values."
49
+ assert all([i == int(i) for i in resolution]), "Resolution of a rectangle should be given as integer values."
50
+
51
+ assert len(center) == 3, "Position of the center of a rectangle should be given a 3-ple of values."
52
+
53
+ width, height = size
54
+ nw, nh = resolution
55
+
56
+ if faces_max_radius is not None:
57
+ estimated_max_radius = np.hypot(width/nw, height/nh)/2
58
+ if estimated_max_radius > faces_max_radius:
59
+ nw = int(np.ceil(width / (np.sqrt(2) * faces_max_radius)))
60
+ nh = int(np.ceil(height / (np.sqrt(2) * faces_max_radius)))
61
+
62
+ if reflection_symmetry:
63
+ if nw % 2 == 1:
64
+ raise ValueError("To define a rectangle mesh with reflection symmetry, "
65
+ "it should have an even number of panels in this direction.")
66
+
67
+ if not np.isclose(center[1], 0.0) or not np.isclose(normal[1], 0.0):
68
+ raise ValueError("To define a rectangle mesh with reflection symmetry, "
69
+ "its center should be in the xOz plane (that is y=0) "
70
+ "and so should also be its normal vector.")
71
+
72
+ half_mesh = mesh_rectangle(size=(width/2, height), resolution=(nw//2, nh),
73
+ center=(0, -width/4, 0), normal=normal,
74
+ reflection_symmetry=False,
75
+ name=f"half_of_{name}")
76
+ mesh = ReflectionSymmetricMesh(half_mesh, plane='xOz', name=name)
77
+
78
+ else:
79
+ y_range = np.linspace(-width/2, width/2, nw+1)
80
+ z_range = np.linspace(-height/2, height/2, nh+1)
81
+ nodes = np.array(list(product([0.0], y_range, z_range)), dtype=float)
82
+ panels = np.array([(j+i*(nh+1), j+1+i*(nh+1), j+1+(i+1)*(nh+1), j+(i+1)*(nh+1))
83
+ for (i, j) in product(range(nw), range(nh))])
84
+
85
+ mesh = Mesh(nodes, panels, name=name)
86
+ mesh = mesh.rotated_such_that_vectors_are_aligned(mesh.faces_normals[0], normal)
87
+
88
+ mesh = mesh.translated(center, name=name)
89
+ return mesh
90
+
91
+
92
+ def mesh_parallelepiped(size=(1.0, 1.0, 1.0), center=(0, 0, 0),
93
+ resolution=(4, 4, 4), faces_max_radius=None,
94
+ missing_sides=set(), reflection_symmetry=False,
95
+ name=None):
96
+ """Six rectangles forming a parallelepiped.
97
+
98
+ Parameters
99
+ ----------
100
+ size : 3-ple of floats, optional
101
+ dimensions of the parallelepiped (width, thickness, height) for coordinates (x, y, z).
102
+ center : 3-ple of floats, optional
103
+ coordinates of the geometric center of the parallelepiped
104
+ resolution : 3-ple of ints, optional
105
+ number of faces along the three directions
106
+ faces_max_radius : float, optional
107
+ maximal radius of a panel. (Default: no maximal radius.)
108
+ If the provided resolution is too coarse, the number of panels is
109
+ changed to fit the constraint on the maximal radius.
110
+ missing_sides : set of string, optional
111
+ if one of the keyword "top", "bottom", "front", "back", "left", "right" is in the set,
112
+ then the corresponding side is not included in the parallelepiped.
113
+ May be ignored when building a mesh with a symmetry.
114
+ reflection_symmetry : bool, optional
115
+ use xOz and yOz symmetry plane to generate the mesh
116
+ name : string, optional
117
+ a name for the body
118
+ """
119
+
120
+ assert len(size) == 3, "Size of a rectangular parallelepiped should be given as a 3-ple of values."
121
+ assert all([h > 0 for h in size]), "Size of the rectangular mesh should be given as positive values."
122
+
123
+ assert len(resolution) == 3, "Resolution of a rectangular parallelepiped should be given as a 3-ple a values."
124
+ assert all([h > 0 for h in resolution]), "Resolution of the rectangular parallelepiped mesh " \
125
+ "should be given as positive values."
126
+ assert all([i == int(i) for i in resolution]), "Resolution of a rectangular parallelepiped " \
127
+ "should be given as integer values."
128
+
129
+ assert len(center) == 3, "Position of the center of a parallelepiped should be given a 3-ple of values."
130
+
131
+ width, thickness, height = size
132
+ nw, nth, nh = resolution
133
+
134
+ if faces_max_radius is not None:
135
+ dw, dh, dth = width/nw, height/nh, thickness/nth
136
+ estimated_max_radius = max(
137
+ np.hypot(dw, dh)/2,
138
+ np.hypot(dw, dth)/2,
139
+ np.hypot(dth, dh)/2,
140
+ )
141
+ if estimated_max_radius > faces_max_radius:
142
+ nw = int(np.ceil(width / (np.sqrt(2) * faces_max_radius)))
143
+ nth = int(np.ceil(thickness / (np.sqrt(2) * faces_max_radius)))
144
+ nh = int(np.ceil(height / (np.sqrt(2) * faces_max_radius)))
145
+
146
+ if reflection_symmetry:
147
+ if (nw % 2 == 1 or nth % 2 == 1):
148
+ raise ValueError("To define a parallelepiped mesh with reflection symmetry, "
149
+ "it should have an even number of panels in both direction.")
150
+
151
+ if not np.isclose(center[0], 0.0) or not np.isclose(center[1], 0.0):
152
+ raise ValueError("To define a rectangle mesh with reflection symmetry, "
153
+ "its center should be on the Oz axis (that is x=0 and y=0).")
154
+
155
+ missing_sides_in_quarter = missing_sides | {"right", "back"}
156
+ quarter_mesh = mesh_parallelepiped(
157
+ size=(width/2, thickness/2, height), resolution=(nw//2, nth//2, nh),
158
+ center=(-width/4, -thickness/4, 0), missing_sides=missing_sides_in_quarter,
159
+ reflection_symmetry=False,
160
+ name=f"quarter_of_{name}"
161
+ )
162
+
163
+ half_mesh = ReflectionSymmetricMesh(quarter_mesh, plane='yOz', name=f"half_of_{name}")
164
+ mesh = ReflectionSymmetricMesh(half_mesh, plane='xOz', name=f"{name}")
165
+
166
+ else:
167
+
168
+ sides = []
169
+ if "left" not in missing_sides:
170
+ sides.append(
171
+ mesh_rectangle(size=(thickness, height), resolution=(nth, nh), center=(-width/2, 0, 0),
172
+ normal=(-1, 0, 0), name=f"left_of_{name}")
173
+ )
174
+ if "right" not in missing_sides:
175
+ sides.append(
176
+ mesh_rectangle(size=(thickness, height), resolution=(nth, nh), center=(width/2, 0, 0),
177
+ normal=(1, 0, 0), name=f"right_of_{name}")
178
+ )
179
+ if "front" not in missing_sides:
180
+ sides.append(
181
+ mesh_rectangle(size=(width, height), resolution=(nw, nh), center=(0, -thickness/2, 0),
182
+ normal=(0, -1, 0), name=f"front_of_{name}")
183
+ )
184
+ if "back" not in missing_sides:
185
+ sides.append(
186
+ mesh_rectangle(size=(width, height), resolution=(nw, nh), center=(0, thickness/2, 0),
187
+ normal=(0, 1, 0), name=f"back_of_{name}")
188
+ )
189
+ if "top" not in missing_sides:
190
+ sides.append(
191
+ mesh_rectangle(size=(thickness, width), resolution=(nth, nw), center=(0, 0, height/2),
192
+ normal=(0, 0, 1), name=f"top_of_{name}")
193
+ )
194
+ if "bottom" not in missing_sides:
195
+ sides.append(
196
+ mesh_rectangle(size=(thickness, width), resolution=(nth, nw), center=(0, 0, -height/2),
197
+ normal=(0, 0, -1), name=f"bottom_of_{name}")
198
+ )
199
+ mesh = Mesh.join_meshes(*sides, name=name)
200
+
201
+ mesh = mesh.translated(center, name=name)
202
+ return mesh
@@ -0,0 +1,55 @@
1
+ """Generate spherical bodies."""
2
+ # Copyright (C) 2017-2024 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
4
+
5
+ import logging
6
+
7
+ import numpy as np
8
+ from numpy import pi
9
+
10
+ from capytaine.meshes.symmetric_meshes import RotationSymmetricMesh
11
+
12
+ LOG = logging.getLogger(__name__)
13
+
14
+
15
+ def mesh_sphere(*, radius=1.0, center=(0.0, 0.0, 0.0),
16
+ resolution=(10, 10), faces_max_radius=None,
17
+ axial_symmetry=False, name=None):
18
+ """Sphere
19
+
20
+ Parameters
21
+ ----------
22
+ radius : float
23
+ radius of the sphere
24
+ center : 3-ple or array of shape (3,)
25
+ position of the geometric center of the sphere
26
+ resolution : couple of ints
27
+ number of panels along a meridian (or number of parallels-1) and
28
+ along a parallel (or number of meridians-1)
29
+ faces_max_radius : float, optional
30
+ maximal radius of a panel. (Default: no maximal radius.)
31
+ If the provided resolution is too coarse, the number of panels is
32
+ changed to fit the constraint on the maximal radius.
33
+ axial_symmetry : bool
34
+ if True, use the axial symmetry to build the mesh (default: False)
35
+ name : string
36
+ a name identifying the sphere (default: "sphere_id" where id is an unique integer).
37
+ """
38
+
39
+ ntheta, nphi = resolution
40
+ if faces_max_radius is not None:
41
+ perimeter = 2*np.pi*radius
42
+ estimated_max_radius = np.hypot(perimeter/ntheta, perimeter/nphi)/2
43
+ if estimated_max_radius > faces_max_radius:
44
+ ntheta = nphi = int(np.ceil(perimeter / (np.sqrt(2)*faces_max_radius)))
45
+
46
+ theta = np.linspace(0.0, pi, ntheta+1)
47
+ points_on_a_meridian = radius * np.stack([np.sin(theta), np.zeros_like(theta), -np.cos(theta)], axis=1)
48
+
49
+ mesh = RotationSymmetricMesh.from_profile_points(points_on_a_meridian, n=nphi, name=name)
50
+
51
+ if not axial_symmetry:
52
+ mesh = mesh.merged()
53
+
54
+ mesh = mesh.translated(center, name=name)
55
+ return mesh