capytaine 2.3.1__cp314-cp314-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.
- capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
- capytaine/.dylibs/libgfortran.5.dylib +0 -0
- capytaine/.dylibs/libquadmath.0.dylib +0 -0
- capytaine/__about__.py +16 -0
- capytaine/__init__.py +36 -0
- capytaine/bem/__init__.py +0 -0
- capytaine/bem/airy_waves.py +111 -0
- capytaine/bem/engines.py +441 -0
- capytaine/bem/problems_and_results.py +600 -0
- capytaine/bem/solver.py +594 -0
- capytaine/bodies/__init__.py +4 -0
- capytaine/bodies/bodies.py +1221 -0
- capytaine/bodies/dofs.py +19 -0
- capytaine/bodies/predefined/__init__.py +6 -0
- capytaine/bodies/predefined/cylinders.py +151 -0
- capytaine/bodies/predefined/rectangles.py +111 -0
- capytaine/bodies/predefined/spheres.py +70 -0
- capytaine/green_functions/FinGreen3D/.gitignore +1 -0
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
- capytaine/green_functions/FinGreen3D/LICENSE +165 -0
- capytaine/green_functions/FinGreen3D/Makefile +16 -0
- capytaine/green_functions/FinGreen3D/README.md +24 -0
- capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
- capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
- capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
- capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
- capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
- capytaine/green_functions/__init__.py +2 -0
- capytaine/green_functions/abstract_green_function.py +64 -0
- capytaine/green_functions/delhommeau.py +507 -0
- capytaine/green_functions/hams.py +204 -0
- capytaine/green_functions/libs/Delhommeau_float32.cpython-314-darwin.so +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cpython-314-darwin.so +0 -0
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/__init__.py +0 -0
- capytaine/io/bemio.py +153 -0
- capytaine/io/legacy.py +328 -0
- capytaine/io/mesh_loaders.py +1086 -0
- capytaine/io/mesh_writers.py +692 -0
- capytaine/io/meshio.py +38 -0
- capytaine/io/wamit.py +479 -0
- capytaine/io/xarray.py +668 -0
- capytaine/matrices/__init__.py +16 -0
- capytaine/matrices/block.py +592 -0
- capytaine/matrices/block_toeplitz.py +325 -0
- capytaine/matrices/builders.py +89 -0
- capytaine/matrices/linear_solvers.py +232 -0
- capytaine/matrices/low_rank.py +395 -0
- capytaine/meshes/__init__.py +6 -0
- capytaine/meshes/clipper.py +465 -0
- capytaine/meshes/collections.py +342 -0
- capytaine/meshes/geometry.py +409 -0
- capytaine/meshes/mesh_like_protocol.py +37 -0
- capytaine/meshes/meshes.py +890 -0
- capytaine/meshes/predefined/__init__.py +6 -0
- capytaine/meshes/predefined/cylinders.py +314 -0
- capytaine/meshes/predefined/rectangles.py +261 -0
- capytaine/meshes/predefined/spheres.py +62 -0
- capytaine/meshes/properties.py +276 -0
- capytaine/meshes/quadratures.py +80 -0
- capytaine/meshes/quality.py +448 -0
- capytaine/meshes/surface_integrals.py +63 -0
- capytaine/meshes/symmetric.py +462 -0
- capytaine/post_pro/__init__.py +6 -0
- capytaine/post_pro/free_surfaces.py +88 -0
- capytaine/post_pro/impedance.py +92 -0
- capytaine/post_pro/kochin.py +54 -0
- capytaine/post_pro/rao.py +60 -0
- capytaine/tools/__init__.py +0 -0
- capytaine/tools/cache_on_disk.py +26 -0
- capytaine/tools/deprecation_handling.py +18 -0
- capytaine/tools/lists_of_points.py +52 -0
- capytaine/tools/lru_cache.py +49 -0
- capytaine/tools/optional_imports.py +27 -0
- capytaine/tools/prony_decomposition.py +150 -0
- capytaine/tools/symbolic_multiplication.py +149 -0
- capytaine/tools/timer.py +66 -0
- capytaine/ui/__init__.py +0 -0
- capytaine/ui/cli.py +28 -0
- capytaine/ui/rich.py +5 -0
- capytaine/ui/vtk/__init__.py +3 -0
- capytaine/ui/vtk/animation.py +329 -0
- capytaine/ui/vtk/body_viewer.py +28 -0
- capytaine/ui/vtk/helpers.py +82 -0
- capytaine/ui/vtk/mesh_viewer.py +461 -0
- capytaine-2.3.1.dist-info/LICENSE +674 -0
- capytaine-2.3.1.dist-info/METADATA +750 -0
- capytaine-2.3.1.dist-info/RECORD +92 -0
- capytaine-2.3.1.dist-info/WHEEL +6 -0
- capytaine-2.3.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Helper functions to compute some properties of the mesh.
|
|
2
|
+
Based on meshmagick <https://github.com/LHEEA/meshmagick> by François Rongère.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright (C) 2017-2019 Matthieu Ancellin, based on the work of François Rongère
|
|
5
|
+
# See LICENSE file at <https://github.com/mancellin/capytaine>
|
|
6
|
+
|
|
7
|
+
from functools import reduce
|
|
8
|
+
from itertools import chain
|
|
9
|
+
import numpy as np
|
|
10
|
+
from typing import List
|
|
11
|
+
from numpy.typing import NDArray
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def compute_faces_properties(mesh):
|
|
15
|
+
"""Compute the faces properties of the mesh"""
|
|
16
|
+
|
|
17
|
+
# faces_areas, faces_normals, faces_centers = mm.get_all_faces_properties(mesh._vertices, mesh._faces)
|
|
18
|
+
nf = mesh.nb_faces
|
|
19
|
+
|
|
20
|
+
# triangle_mask = _faces[:, 0] == _faces[:, -1]
|
|
21
|
+
# nb_triangles = np.sum(triangle_mask)
|
|
22
|
+
# quads_mask = np.invert(triangle_mask)
|
|
23
|
+
# nb_quads = nf - nb_triangles
|
|
24
|
+
|
|
25
|
+
faces_areas = np.zeros(nf, dtype=float)
|
|
26
|
+
faces_normals = np.zeros((nf, 3), dtype=float)
|
|
27
|
+
faces_centers = np.zeros((nf, 3), dtype=float)
|
|
28
|
+
|
|
29
|
+
# Collectively dealing with triangles
|
|
30
|
+
# triangles = _faces[triangle_mask]
|
|
31
|
+
triangles_id = mesh.triangles_ids
|
|
32
|
+
triangles = mesh._faces[triangles_id]
|
|
33
|
+
|
|
34
|
+
triangles_normals = np.cross(mesh._vertices[triangles[:, 1]] - mesh._vertices[triangles[:, 0]],
|
|
35
|
+
mesh._vertices[triangles[:, 2]] - mesh._vertices[triangles[:, 0]])
|
|
36
|
+
triangles_normals_norm = np.linalg.norm(triangles_normals, axis=1)
|
|
37
|
+
|
|
38
|
+
degenerate_triangle = np.abs(triangles_normals_norm) < 1e-12
|
|
39
|
+
triangles_id = triangles_id[~degenerate_triangle]
|
|
40
|
+
triangles_normals = triangles_normals[~degenerate_triangle, :]
|
|
41
|
+
triangles_normals_norm = triangles_normals_norm[~degenerate_triangle]
|
|
42
|
+
triangles = triangles[~degenerate_triangle, :]
|
|
43
|
+
# Now, continue the computations without the degenerate triangles
|
|
44
|
+
|
|
45
|
+
faces_normals[triangles_id] = triangles_normals / triangles_normals_norm[:, np.newaxis]
|
|
46
|
+
faces_areas[triangles_id] = triangles_normals_norm / 2.
|
|
47
|
+
faces_centers[triangles_id] = np.sum(mesh._vertices[triangles[:, :3]], axis=1) / 3.
|
|
48
|
+
|
|
49
|
+
# Collectively dealing with quads
|
|
50
|
+
quads_id = mesh.quadrangles_ids
|
|
51
|
+
quads = mesh._faces[quads_id]
|
|
52
|
+
# quads = _faces[quads_mask]
|
|
53
|
+
|
|
54
|
+
quads_normals = np.cross(mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]],
|
|
55
|
+
mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 1]])
|
|
56
|
+
|
|
57
|
+
quads_normals_norm = np.linalg.norm(quads_normals, axis=1)
|
|
58
|
+
|
|
59
|
+
degenerate_quad = np.abs(quads_normals_norm) < 1e-12
|
|
60
|
+
quads_id = quads_id[~degenerate_quad]
|
|
61
|
+
quads_normals = quads_normals[~degenerate_quad]
|
|
62
|
+
quads_normals_norm = quads_normals_norm[~degenerate_quad]
|
|
63
|
+
quads = quads[~degenerate_quad, :]
|
|
64
|
+
# Now, continue the computations without the degenerate quads
|
|
65
|
+
|
|
66
|
+
faces_normals[quads_id] = quads_normals / quads_normals_norm[:, np.newaxis]
|
|
67
|
+
|
|
68
|
+
a1 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 1]] - mesh._vertices[quads[:, 0]],
|
|
69
|
+
mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
|
|
70
|
+
a2 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 0]],
|
|
71
|
+
mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
|
|
72
|
+
faces_areas[quads_id] = a1 + a2
|
|
73
|
+
|
|
74
|
+
c1 = np.sum(mesh._vertices[quads[:, :3]], axis=1) / 3.
|
|
75
|
+
c2 = (np.sum(mesh._vertices[quads[:, 2:4]], axis=1) + mesh._vertices[quads[:, 0]]) / 3.
|
|
76
|
+
|
|
77
|
+
faces_centers[quads_id] = (np.array(([a1, ] * 3)).T * c1 + np.array(([a2, ] * 3)).T * c2)
|
|
78
|
+
faces_centers[quads_id] /= np.array(([faces_areas[quads_id], ] * 3)).T
|
|
79
|
+
|
|
80
|
+
faces_radiuses = compute_radiuses(mesh, faces_centers)
|
|
81
|
+
|
|
82
|
+
return {'faces_areas': faces_areas,
|
|
83
|
+
'faces_normals': faces_normals,
|
|
84
|
+
'faces_centers': faces_centers,
|
|
85
|
+
'faces_radiuses': faces_radiuses,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def compute_radiuses(mesh, faces_centers):
|
|
90
|
+
"""Compute the radiuses of the faces of the mesh.
|
|
91
|
+
|
|
92
|
+
The radius is defined here as the maximal distance between the center
|
|
93
|
+
of mass of a cell and one of its points."""
|
|
94
|
+
|
|
95
|
+
# Coordinates of all the vertices grouped by face
|
|
96
|
+
faces_vertices = mesh.vertices[mesh.faces, :]
|
|
97
|
+
# faces_vertices.shape == (nb_faces, 4, 3)
|
|
98
|
+
|
|
99
|
+
# Reorder the axes for array broadcasting below
|
|
100
|
+
faces_vertices = np.moveaxis(faces_vertices, 0, 1)
|
|
101
|
+
# faces_vertices.shape == (4, nb_faces, 3)
|
|
102
|
+
|
|
103
|
+
# Get all the vectors between the center of faces and their vertices.
|
|
104
|
+
radial_vector = faces_centers - faces_vertices
|
|
105
|
+
# radial_vector.shape == (4, nb_faces, 3)
|
|
106
|
+
|
|
107
|
+
# Keep the maximum length
|
|
108
|
+
faces_radiuses = np.max(np.linalg.norm(radial_vector, axis=2), axis=0)
|
|
109
|
+
# faces_radiuses.shape = (nb_faces)
|
|
110
|
+
|
|
111
|
+
return faces_radiuses
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def compute_connectivity(mesh):
|
|
115
|
+
"""Compute the connectivities of the mesh.
|
|
116
|
+
|
|
117
|
+
It concerns further connectivity than simple faces/vertices connectivities. It computes the vertices / vertices, vertices / faces and faces / faces connectivities.
|
|
118
|
+
|
|
119
|
+
Note
|
|
120
|
+
----
|
|
121
|
+
* Note that if the mesh is not conformal, the algorithm may not perform correctly
|
|
122
|
+
|
|
123
|
+
TODO: The computation of boundaries should be in the future computed with help of VTK
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
nv = mesh.nb_vertices
|
|
127
|
+
nf = mesh.nb_faces
|
|
128
|
+
|
|
129
|
+
mesh_closed = True
|
|
130
|
+
|
|
131
|
+
# Building connectivities
|
|
132
|
+
|
|
133
|
+
# Establishing v_v and v_f connectivities
|
|
134
|
+
v_v = dict([(i, set()) for i in range(nv)])
|
|
135
|
+
v_f = dict([(i, set()) for i in range(nv)])
|
|
136
|
+
for (iface, face) in enumerate(mesh._faces):
|
|
137
|
+
if face[0] == face[-1]:
|
|
138
|
+
face_w = face[:3]
|
|
139
|
+
else:
|
|
140
|
+
face_w = face
|
|
141
|
+
for (index, iV) in enumerate(face_w):
|
|
142
|
+
v_f[iV].add(iface)
|
|
143
|
+
v_v[face_w[index - 1]].add(iV)
|
|
144
|
+
v_v[iV].add(face_w[index - 1])
|
|
145
|
+
|
|
146
|
+
# Connectivity f_f
|
|
147
|
+
boundary_edges = dict()
|
|
148
|
+
|
|
149
|
+
f_f = dict([(i, set()) for i in range(nf)])
|
|
150
|
+
for ivertex in range(nv):
|
|
151
|
+
set1 = v_f[ivertex]
|
|
152
|
+
for iadj_v in v_v[ivertex]:
|
|
153
|
+
set2 = v_f[iadj_v]
|
|
154
|
+
intersection = list(set1 & set2)
|
|
155
|
+
if len(intersection) == 2:
|
|
156
|
+
f_f[intersection[0]].add(intersection[1])
|
|
157
|
+
f_f[intersection[1]].add(intersection[0])
|
|
158
|
+
|
|
159
|
+
elif len(intersection) == 1:
|
|
160
|
+
boundary_face = mesh._faces[intersection[0]]
|
|
161
|
+
|
|
162
|
+
if boundary_face[0] == boundary_face[-1]:
|
|
163
|
+
boundary_face = boundary_face[:3]
|
|
164
|
+
ids = np.where((boundary_face == ivertex) + (boundary_face == iadj_v))[0]
|
|
165
|
+
|
|
166
|
+
if ids[1] != ids[0]+1:
|
|
167
|
+
i_v_orig, i_v_target = boundary_face[ids]
|
|
168
|
+
else:
|
|
169
|
+
i_v_target, i_v_orig = boundary_face[ids]
|
|
170
|
+
|
|
171
|
+
boundary_edges[i_v_orig] = i_v_target
|
|
172
|
+
else:
|
|
173
|
+
raise RuntimeError('Unexpected error while computing mesh connectivities')
|
|
174
|
+
|
|
175
|
+
# Computing boundaries
|
|
176
|
+
boundaries = list()
|
|
177
|
+
# TODO: calculer des boundaries fermees et ouvertes (closed_boundaries et open_boundaries) et mettre dans dict
|
|
178
|
+
while True:
|
|
179
|
+
try:
|
|
180
|
+
boundary = list()
|
|
181
|
+
i_v0_init, i_v1 = boundary_edges.popitem()
|
|
182
|
+
boundary.append(i_v0_init)
|
|
183
|
+
boundary.append(i_v1)
|
|
184
|
+
i_v0 = i_v1
|
|
185
|
+
|
|
186
|
+
while True:
|
|
187
|
+
try:
|
|
188
|
+
i_v1 = boundary_edges.pop(i_v0)
|
|
189
|
+
boundary.append(i_v1)
|
|
190
|
+
i_v0 = i_v1
|
|
191
|
+
except KeyError:
|
|
192
|
+
if boundary[0] != boundary[-1]:
|
|
193
|
+
print('Boundary is not closed !!!')
|
|
194
|
+
else:
|
|
195
|
+
boundaries.append(boundary)
|
|
196
|
+
break
|
|
197
|
+
except KeyError:
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
return {'v_v': v_v,
|
|
201
|
+
'v_f': v_f,
|
|
202
|
+
'f_f': f_f,
|
|
203
|
+
'boundaries': boundaries}
|
|
204
|
+
|
|
205
|
+
def faces_in_group(faces: NDArray[np.integer], group: NDArray[np.integer]) -> NDArray[np.bool_]:
|
|
206
|
+
"""Identification of faces with vertices within group.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
faces : NDArray[np.integer]
|
|
211
|
+
Mesh faces. Expecting a numpy array of shape N_faces x N_vertices_per_face.
|
|
212
|
+
group : NDArray[np.integer]
|
|
213
|
+
Group of connected vertices
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
NDArray[np.bool]
|
|
218
|
+
Mask of faces containing vertices from the group
|
|
219
|
+
"""
|
|
220
|
+
return np.any(np.isin(faces, group), axis=1)
|
|
221
|
+
|
|
222
|
+
def clustering(faces: NDArray[np.integer]) -> List[NDArray[np.integer]]:
|
|
223
|
+
"""Clustering of vertices per connected faces.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
faces : NDArray[np.integer]
|
|
228
|
+
Mesh faces. Expecting a numpy array of shape N_faces x N_vertices_per_face.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
list[NDArray[np.integer]]
|
|
233
|
+
Groups of connected vertices.
|
|
234
|
+
"""
|
|
235
|
+
vert_groups: list[NDArray[np.integer]] = []
|
|
236
|
+
mask = np.ones(faces.shape[0], dtype=bool)
|
|
237
|
+
while np.any(mask):
|
|
238
|
+
# Consider faces whose vertices are not already identified in a group.
|
|
239
|
+
# Start new group by considering first face
|
|
240
|
+
remaining_faces = faces[mask]
|
|
241
|
+
group = remaining_faces[0]
|
|
242
|
+
rem_mask = np.ones(remaining_faces.shape[0], dtype=bool)
|
|
243
|
+
# Iterative update of vertices group. Output final result to frozenset
|
|
244
|
+
while not np.allclose(new:=faces_in_group(remaining_faces, group), rem_mask):
|
|
245
|
+
group = np.unique(remaining_faces[new])
|
|
246
|
+
rem_mask = new
|
|
247
|
+
else:
|
|
248
|
+
group = np.unique(remaining_faces[new])
|
|
249
|
+
vert_groups.append(group)
|
|
250
|
+
# Identify faces that have no vertices in current groups
|
|
251
|
+
mask = ~reduce(np.logical_or, [faces_in_group(faces, group) for group in vert_groups])
|
|
252
|
+
return vert_groups
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def connected_components(mesh):
|
|
256
|
+
"""Returns a list of meshes that each corresponds to the a connected component in the original mesh.
|
|
257
|
+
Assumes the mesh is mostly conformal without duplicate vertices.
|
|
258
|
+
"""
|
|
259
|
+
# Get connected vertices
|
|
260
|
+
vertices_components = clustering(mesh.faces)
|
|
261
|
+
# Verification
|
|
262
|
+
if sum(len(group) for group in vertices_components) != len(set(chain.from_iterable(vertices_components))):
|
|
263
|
+
raise ValueError("Error in connected components clustering. Some elements are duplicated")
|
|
264
|
+
# The components are found. The rest is just about retrieving the faces in each components.
|
|
265
|
+
faces_components = [np.argwhere(faces_in_group(mesh.faces, group)) for group in vertices_components]
|
|
266
|
+
components = [mesh.extract_faces(f) for f in faces_components]
|
|
267
|
+
return components
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def connected_components_of_waterline(mesh, z=0.0):
|
|
271
|
+
if np.any(mesh.vertices[:, 2] > z + 1e-8):
|
|
272
|
+
mesh = mesh.immersed_part(free_surface=z)
|
|
273
|
+
fs_vertices_indices = np.where(np.isclose(mesh.vertices[:, 2], z))[0]
|
|
274
|
+
fs_faces_indices = np.where(np.any(np.isin(mesh.faces, fs_vertices_indices), axis=1))[0]
|
|
275
|
+
crown_mesh = mesh.extract_faces(fs_faces_indices)
|
|
276
|
+
return connected_components(crown_mesh)
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|