capytaine 2.2__cp38-cp38-macosx_11_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 +35 -0
- capytaine/bem/__init__.py +0 -0
- capytaine/bem/airy_waves.py +106 -0
- capytaine/bem/engines.py +441 -0
- capytaine/bem/problems_and_results.py +545 -0
- capytaine/bem/solver.py +497 -0
- capytaine/bodies/__init__.py +4 -0
- capytaine/bodies/bodies.py +1185 -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 +109 -0
- capytaine/bodies/predefined/spheres.py +70 -0
- capytaine/green_functions/__init__.py +2 -0
- capytaine/green_functions/abstract_green_function.py +12 -0
- capytaine/green_functions/delhommeau.py +432 -0
- capytaine/green_functions/libs/Delhommeau_float32.cpython-38-darwin.so +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cpython-38-darwin.so +0 -0
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/__init__.py +0 -0
- capytaine/io/bemio.py +141 -0
- capytaine/io/legacy.py +328 -0
- capytaine/io/mesh_loaders.py +1085 -0
- capytaine/io/mesh_writers.py +692 -0
- capytaine/io/meshio.py +38 -0
- capytaine/io/xarray.py +516 -0
- capytaine/matrices/__init__.py +16 -0
- capytaine/matrices/block.py +590 -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 +393 -0
- capytaine/meshes/__init__.py +6 -0
- capytaine/meshes/clipper.py +464 -0
- capytaine/meshes/collections.py +324 -0
- capytaine/meshes/geometry.py +409 -0
- capytaine/meshes/meshes.py +868 -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 +242 -0
- capytaine/meshes/quadratures.py +80 -0
- capytaine/meshes/quality.py +448 -0
- capytaine/meshes/surface_integrals.py +63 -0
- capytaine/meshes/symmetric.py +383 -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 +94 -0
- capytaine/tools/symbolic_multiplication.py +107 -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.2.dist-info/LICENSE +674 -0
- capytaine-2.2.dist-info/METADATA +751 -0
- capytaine-2.2.dist-info/RECORD +76 -0
- capytaine-2.2.dist-info/WHEEL +4 -0
- capytaine-2.2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,242 @@
|
|
|
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
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def compute_faces_properties(mesh):
|
|
11
|
+
"""Compute the faces properties of the mesh"""
|
|
12
|
+
|
|
13
|
+
# faces_areas, faces_normals, faces_centers = mm.get_all_faces_properties(mesh._vertices, mesh._faces)
|
|
14
|
+
nf = mesh.nb_faces
|
|
15
|
+
|
|
16
|
+
# triangle_mask = _faces[:, 0] == _faces[:, -1]
|
|
17
|
+
# nb_triangles = np.sum(triangle_mask)
|
|
18
|
+
# quads_mask = np.invert(triangle_mask)
|
|
19
|
+
# nb_quads = nf - nb_triangles
|
|
20
|
+
|
|
21
|
+
faces_areas = np.zeros(nf, dtype=float)
|
|
22
|
+
faces_normals = np.zeros((nf, 3), dtype=float)
|
|
23
|
+
faces_centers = np.zeros((nf, 3), dtype=float)
|
|
24
|
+
|
|
25
|
+
# Collectively dealing with triangles
|
|
26
|
+
# triangles = _faces[triangle_mask]
|
|
27
|
+
triangles_id = mesh.triangles_ids
|
|
28
|
+
triangles = mesh._faces[triangles_id]
|
|
29
|
+
|
|
30
|
+
triangles_normals = np.cross(mesh._vertices[triangles[:, 1]] - mesh._vertices[triangles[:, 0]],
|
|
31
|
+
mesh._vertices[triangles[:, 2]] - mesh._vertices[triangles[:, 0]])
|
|
32
|
+
triangles_normals_norm = np.linalg.norm(triangles_normals, axis=1)
|
|
33
|
+
|
|
34
|
+
degenerate_triangle = np.abs(triangles_normals_norm) < 1e-12
|
|
35
|
+
triangles_id = triangles_id[~degenerate_triangle]
|
|
36
|
+
triangles_normals = triangles_normals[~degenerate_triangle, :]
|
|
37
|
+
triangles_normals_norm = triangles_normals_norm[~degenerate_triangle]
|
|
38
|
+
triangles = triangles[~degenerate_triangle, :]
|
|
39
|
+
# Now, continue the computations without the degenerate triangles
|
|
40
|
+
|
|
41
|
+
faces_normals[triangles_id] = triangles_normals / triangles_normals_norm[:, np.newaxis]
|
|
42
|
+
faces_areas[triangles_id] = triangles_normals_norm / 2.
|
|
43
|
+
faces_centers[triangles_id] = np.sum(mesh._vertices[triangles[:, :3]], axis=1) / 3.
|
|
44
|
+
|
|
45
|
+
# Collectively dealing with quads
|
|
46
|
+
quads_id = mesh.quadrangles_ids
|
|
47
|
+
quads = mesh._faces[quads_id]
|
|
48
|
+
# quads = _faces[quads_mask]
|
|
49
|
+
|
|
50
|
+
quads_normals = np.cross(mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]],
|
|
51
|
+
mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 1]])
|
|
52
|
+
|
|
53
|
+
quads_normals_norm = np.linalg.norm(quads_normals, axis=1)
|
|
54
|
+
|
|
55
|
+
degenerate_quad = np.abs(quads_normals_norm) < 1e-12
|
|
56
|
+
quads_id = quads_id[~degenerate_quad]
|
|
57
|
+
quads_normals = quads_normals[~degenerate_quad]
|
|
58
|
+
quads_normals_norm = quads_normals_norm[~degenerate_quad]
|
|
59
|
+
quads = quads[~degenerate_quad, :]
|
|
60
|
+
# Now, continue the computations without the degenerate quads
|
|
61
|
+
|
|
62
|
+
faces_normals[quads_id] = quads_normals / quads_normals_norm[:, np.newaxis]
|
|
63
|
+
|
|
64
|
+
a1 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 1]] - mesh._vertices[quads[:, 0]],
|
|
65
|
+
mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
|
|
66
|
+
a2 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 0]],
|
|
67
|
+
mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
|
|
68
|
+
faces_areas[quads_id] = a1 + a2
|
|
69
|
+
|
|
70
|
+
c1 = np.sum(mesh._vertices[quads[:, :3]], axis=1) / 3.
|
|
71
|
+
c2 = (np.sum(mesh._vertices[quads[:, 2:4]], axis=1) + mesh._vertices[quads[:, 0]]) / 3.
|
|
72
|
+
|
|
73
|
+
faces_centers[quads_id] = (np.array(([a1, ] * 3)).T * c1 + np.array(([a2, ] * 3)).T * c2)
|
|
74
|
+
faces_centers[quads_id] /= np.array(([faces_areas[quads_id], ] * 3)).T
|
|
75
|
+
|
|
76
|
+
faces_radiuses = compute_radiuses(mesh, faces_centers)
|
|
77
|
+
|
|
78
|
+
return {'faces_areas': faces_areas,
|
|
79
|
+
'faces_normals': faces_normals,
|
|
80
|
+
'faces_centers': faces_centers,
|
|
81
|
+
'faces_radiuses': faces_radiuses,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def compute_radiuses(mesh, faces_centers):
|
|
86
|
+
"""Compute the radiuses of the faces of the mesh.
|
|
87
|
+
|
|
88
|
+
The radius is defined here as the maximal distance between the center
|
|
89
|
+
of mass of a cell and one of its points."""
|
|
90
|
+
|
|
91
|
+
# Coordinates of all the vertices grouped by face
|
|
92
|
+
faces_vertices = mesh.vertices[mesh.faces, :]
|
|
93
|
+
# faces_vertices.shape == (nb_faces, 4, 3)
|
|
94
|
+
|
|
95
|
+
# Reorder the axes for array broadcasting below
|
|
96
|
+
faces_vertices = np.moveaxis(faces_vertices, 0, 1)
|
|
97
|
+
# faces_vertices.shape == (4, nb_faces, 3)
|
|
98
|
+
|
|
99
|
+
# Get all the vectors between the center of faces and their vertices.
|
|
100
|
+
radial_vector = faces_centers - faces_vertices
|
|
101
|
+
# radial_vector.shape == (4, nb_faces, 3)
|
|
102
|
+
|
|
103
|
+
# Keep the maximum length
|
|
104
|
+
faces_radiuses = np.max(np.linalg.norm(radial_vector, axis=2), axis=0)
|
|
105
|
+
# faces_radiuses.shape = (nb_faces)
|
|
106
|
+
|
|
107
|
+
return faces_radiuses
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def compute_connectivity(mesh):
|
|
111
|
+
"""Compute the connectivities of the mesh.
|
|
112
|
+
|
|
113
|
+
It concerns further connectivity than simple faces/vertices connectivities. It computes the vertices / vertices, vertices / faces and faces / faces connectivities.
|
|
114
|
+
|
|
115
|
+
Note
|
|
116
|
+
----
|
|
117
|
+
* Note that if the mesh is not conformal, the algorithm may not perform correctly
|
|
118
|
+
|
|
119
|
+
TODO: The computation of boundaries should be in the future computed with help of VTK
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
nv = mesh.nb_vertices
|
|
123
|
+
nf = mesh.nb_faces
|
|
124
|
+
|
|
125
|
+
mesh_closed = True
|
|
126
|
+
|
|
127
|
+
# Building connectivities
|
|
128
|
+
|
|
129
|
+
# Establishing v_v and v_f connectivities
|
|
130
|
+
v_v = dict([(i, set()) for i in range(nv)])
|
|
131
|
+
v_f = dict([(i, set()) for i in range(nv)])
|
|
132
|
+
for (iface, face) in enumerate(mesh._faces):
|
|
133
|
+
if face[0] == face[-1]:
|
|
134
|
+
face_w = face[:3]
|
|
135
|
+
else:
|
|
136
|
+
face_w = face
|
|
137
|
+
for (index, iV) in enumerate(face_w):
|
|
138
|
+
v_f[iV].add(iface)
|
|
139
|
+
v_v[face_w[index - 1]].add(iV)
|
|
140
|
+
v_v[iV].add(face_w[index - 1])
|
|
141
|
+
|
|
142
|
+
# Connectivity f_f
|
|
143
|
+
boundary_edges = dict()
|
|
144
|
+
|
|
145
|
+
f_f = dict([(i, set()) for i in range(nf)])
|
|
146
|
+
for ivertex in range(nv):
|
|
147
|
+
set1 = v_f[ivertex]
|
|
148
|
+
for iadj_v in v_v[ivertex]:
|
|
149
|
+
set2 = v_f[iadj_v]
|
|
150
|
+
intersection = list(set1 & set2)
|
|
151
|
+
if len(intersection) == 2:
|
|
152
|
+
f_f[intersection[0]].add(intersection[1])
|
|
153
|
+
f_f[intersection[1]].add(intersection[0])
|
|
154
|
+
|
|
155
|
+
elif len(intersection) == 1:
|
|
156
|
+
boundary_face = mesh._faces[intersection[0]]
|
|
157
|
+
|
|
158
|
+
if boundary_face[0] == boundary_face[-1]:
|
|
159
|
+
boundary_face = boundary_face[:3]
|
|
160
|
+
ids = np.where((boundary_face == ivertex) + (boundary_face == iadj_v))[0]
|
|
161
|
+
|
|
162
|
+
if ids[1] != ids[0]+1:
|
|
163
|
+
i_v_orig, i_v_target = boundary_face[ids]
|
|
164
|
+
else:
|
|
165
|
+
i_v_target, i_v_orig = boundary_face[ids]
|
|
166
|
+
|
|
167
|
+
boundary_edges[i_v_orig] = i_v_target
|
|
168
|
+
else:
|
|
169
|
+
raise RuntimeError('Unexpected error while computing mesh connectivities')
|
|
170
|
+
|
|
171
|
+
# Computing boundaries
|
|
172
|
+
boundaries = list()
|
|
173
|
+
# TODO: calculer des boundaries fermees et ouvertes (closed_boundaries et open_boundaries) et mettre dans dict
|
|
174
|
+
while True:
|
|
175
|
+
try:
|
|
176
|
+
boundary = list()
|
|
177
|
+
i_v0_init, i_v1 = boundary_edges.popitem()
|
|
178
|
+
boundary.append(i_v0_init)
|
|
179
|
+
boundary.append(i_v1)
|
|
180
|
+
i_v0 = i_v1
|
|
181
|
+
|
|
182
|
+
while True:
|
|
183
|
+
try:
|
|
184
|
+
i_v1 = boundary_edges.pop(i_v0)
|
|
185
|
+
boundary.append(i_v1)
|
|
186
|
+
i_v0 = i_v1
|
|
187
|
+
except KeyError:
|
|
188
|
+
if boundary[0] != boundary[-1]:
|
|
189
|
+
print('Boundary is not closed !!!')
|
|
190
|
+
else:
|
|
191
|
+
boundaries.append(boundary)
|
|
192
|
+
break
|
|
193
|
+
except KeyError:
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
return {'v_v': v_v,
|
|
197
|
+
'v_f': v_f,
|
|
198
|
+
'f_f': f_f,
|
|
199
|
+
'boundaries': boundaries}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def connected_components(mesh):
|
|
203
|
+
"""Returns a list of meshes that each corresponds to the a connected component in the original mesh.
|
|
204
|
+
Assumes the mesh is mostly conformal without duplicate vertices.
|
|
205
|
+
"""
|
|
206
|
+
from typing import Set, FrozenSet, List
|
|
207
|
+
|
|
208
|
+
vertices_components: Set[FrozenSet[int]] = set()
|
|
209
|
+
for set_of_v_in_face in map(frozenset, mesh.faces):
|
|
210
|
+
intersecting_components = [c for c in vertices_components if len(c.intersection(set_of_v_in_face)) > 0]
|
|
211
|
+
if len(intersecting_components) == 0:
|
|
212
|
+
vertices_components.add(set_of_v_in_face)
|
|
213
|
+
else:
|
|
214
|
+
for c in intersecting_components:
|
|
215
|
+
vertices_components.remove(c)
|
|
216
|
+
vertices_components.add(frozenset.union(set_of_v_in_face, *intersecting_components))
|
|
217
|
+
|
|
218
|
+
# Verification
|
|
219
|
+
for component in vertices_components:
|
|
220
|
+
assert all(len(component.intersection(c)) == 0 for c in vertices_components if c != component)
|
|
221
|
+
|
|
222
|
+
# The components are found. The rest is just about retrieving the faces in each components.
|
|
223
|
+
vertices_components: List[FrozenSet[int]] = list(vertices_components)
|
|
224
|
+
faces_components: List[List[int]] = [[] for _ in vertices_components]
|
|
225
|
+
for i_face, v_in_face in enumerate(mesh.faces):
|
|
226
|
+
for i_component, v_c in enumerate(vertices_components):
|
|
227
|
+
if any(v in v_c for v in v_in_face):
|
|
228
|
+
assert all(v in v_c for v in v_in_face)
|
|
229
|
+
faces_components[i_component].append(i_face)
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
components = [mesh.extract_faces(f) for f in faces_components]
|
|
233
|
+
return components
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def connected_components_of_waterline(mesh, z=0.0):
|
|
237
|
+
if np.any(mesh.vertices[:, 2] > z + 1e-8):
|
|
238
|
+
mesh = mesh.immersed_part(free_surface=z)
|
|
239
|
+
fs_vertices_indices = np.where(np.isclose(mesh.vertices[:, 2], z))[0]
|
|
240
|
+
fs_faces_indices = np.where(np.any(np.isin(mesh.faces, fs_vertices_indices), axis=1))[0]
|
|
241
|
+
crown_mesh = mesh.extract_faces(fs_faces_indices)
|
|
242
|
+
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
|