def facet_reference_edge_vectors(L, tablename, cellname): celltype = getattr(basix.CellType, cellname) topology = basix.topology(celltype) geometry = basix.geometry(celltype) triangle_edges = basix.topology(basix.CellType.triangle)[1] quadrilateral_edges = basix.topology(basix.CellType.quadrilateral)[1] if len(topology) != 4: raise ValueError("Can only get facet edges for 3D cells.") edge_vectors = [] for facet in topology[-2]: if len(facet) == 3: edge_vectors += [ geometry[facet[j]] - geometry[facet[i]] for i, j in triangle_edges ] elif len(facet) == 4: edge_vectors += [ geometry[facet[j]] - geometry[facet[i]] for i, j in quadrilateral_edges ] else: raise ValueError( "Only triangular and quadrilateral faces supported.") out = numpy.array(edge_vectors) return L.ArrayDecl("static const double", f"{cellname}_{tablename}", out.shape, out)
def test_outward_normals(cell): cell_type = getattr(basix.CellType, cell) normals = basix.cell.facet_outward_normals(cell_type) facets = basix.topology(cell_type)[-2] geometry = basix.geometry(cell_type) midpoint = sum(geometry) / len(geometry) for normal, facet in zip(normals, facets): assert np.dot(geometry[facet[0]] - midpoint, normal) > 0
def reference_edge_vectors(L, tablename, cellname): celltype = getattr(basix.CellType, cellname) topology = basix.topology(celltype) geometry = basix.geometry(celltype) edge_vectors = [geometry[j] - geometry[i] for i, j in topology[1]] out = numpy.array(edge_vectors[cellname]) return L.ArrayDecl("static const double", f"{cellname}_{tablename}", out.shape, out)
def test_normals(cell): cell_type = getattr(basix.CellType, cell) normals = basix.cell.facet_normals(cell_type) facets = basix.topology(cell_type)[-2] geometry = basix.geometry(cell_type) for normal, facet in zip(normals, facets): assert np.isclose(np.linalg.norm(normal), 1) for v in facet[1:]: tangent = geometry[v] - geometry[facet[0]] assert np.isclose(np.dot(tangent, normal), 0)
def map_facet_points(points, facet, cellname): geom = basix.geometry(basix_cells[cellname]) facet_vertices = [ geom[i] for i in basix.topology(basix_cells[cellname])[-2][facet] ] return [ facet_vertices[0] + sum((i - facet_vertices[0]) * j for i, j in zip(facet_vertices[1:], p)) for p in points ]
def map_facet_points(points, facet, cellname): """Map points from a reference facet to a physical facet.""" geom = basix.geometry(basix.cell.string_to_type(cellname)) facet_vertices = [ geom[i] for i in basix.topology(basix.cell.string_to_type(cellname))[-2][facet] ] return [ facet_vertices[0] + sum((i - facet_vertices[0]) * j for i, j in zip(facet_vertices[1:], p)) for p in points ]
def sympy_lagrange(celltype, n): x = sympy.Symbol("x") y = sympy.Symbol("y") z = sympy.Symbol("z") from sympy import S topology = basix.topology(celltype) geometry = S(basix.geometry(celltype).astype(int)) pt = [] for dim, entities in enumerate(topology): for ent in entities: entity_geom = [geometry[t, :] for t in ent] if (dim == 0): pt += [entity_geom[0]] elif (dim == 1): for i in range(n - 1): pt += [ entity_geom[0] + sympy.Rational(i + 1, n) * (entity_geom[1] - entity_geom[0]) ] elif (dim == 2): for i in range(n - 2): for j in range(n - 2 - i): pt += [ entity_geom[0] + sympy.Rational(i + 1, n) * (entity_geom[2] - entity_geom[0]) + sympy.Rational(j + 1, n) * (entity_geom[1] - entity_geom[0]) ] elif (dim == 3): for i in range(n - 3): for j in range(n - 3 - i): for k in range(n - 3 - i - j): pt += [ entity_geom[0] + sympy.Rational(i + 1, n) * (entity_geom[3] - entity_geom[0]) + sympy.Rational(j + 1, n) * (entity_geom[2] - entity_geom[0]) + sympy.Rational(k + 1, n) * (entity_geom[1] - entity_geom[0]) ] funcs = [] if celltype == basix.CellType.interval: for i in range(n + 1): funcs += [x**i] mat = numpy.empty((len(pt), len(funcs)), dtype=object) for i, f in enumerate(funcs): for j, p in enumerate(pt): mat[i, j] = f.subs([(x, p[0])]) elif celltype == basix.CellType.triangle: for i in range(n + 1): for j in range(n + 1 - i): funcs += [x**j * y**i] mat = numpy.empty((len(pt), len(funcs)), dtype=object) for i, f in enumerate(funcs): for j, p in enumerate(pt): mat[i, j] = f.subs([(x, p[0]), (y, p[1])]) elif celltype == basix.CellType.tetrahedron: for i in range(n + 1): for j in range(n + 1 - i): for k in range(n + 1 - i - j): funcs += [x**j * y**i * z**k] mat = numpy.empty((len(pt), len(funcs)), dtype=object) for i, f in enumerate(funcs): for j, p in enumerate(pt): mat[i, j] = f.subs([(x, p[0]), (y, p[1]), (z, p[2])]) mat = sympy.Matrix(mat) mat = mat.inv() g = [] for r in range(mat.shape[0]): g += [sum([v * funcs[i] for i, v in enumerate(mat.row(r))])] return g
def sympy_rt(celltype, n): x = sympy.Symbol("x") y = sympy.Symbol("y") z = sympy.Symbol("z") from sympy import S topology = basix.topology(celltype) geometry = S(basix.geometry(celltype).astype(int)) dummy = [ sympy.Symbol("DUMMY1"), sympy.Symbol("DUMMY2"), sympy.Symbol("DUMMY3") ] funcs = [] if celltype == basix.CellType.triangle: tdim = 2 for i in range(n): for j in range(n - i): for d in range(2): funcs += [[x**j * y**i if k == d else 0 for k in range(2)]] for i in range(n): funcs.append([x**(n - i) * y**i, x**(n - 1 - i) * y**(i + 1)]) mat = numpy.empty((len(funcs), len(funcs)), dtype=object) # edge normals for i, f in enumerate(funcs): if n == 1: edge_basis = [sympy.Integer(1)] else: edge_basis = sympy_lagrange(basix.CellType.interval, n - 1) edge_basis = [a.subs(x, dummy[0]) for a in edge_basis] j = 0 for edge in topology[1]: edge_geom = [geometry[t, :] for t in edge] tangent = edge_geom[1] - edge_geom[0] norm = sympy.sqrt(sum(i**2 for i in tangent)) tangent = [i / norm for i in tangent] normal = [-tangent[1], tangent[0]] param = [(1 - dummy[0]) * a + dummy[0] * b for a, b in zip(edge_geom[0], edge_geom[1])] for g in edge_basis: integrand = sum((f_i * v_i) for f_i, v_i in zip(f, normal)) integrand = integrand.subs(x, param[0]).subs(y, param[1]) integrand *= g * norm mat[i, j] = integrand.integrate((dummy[0], 0, 1)) j += 1 # interior dofs if n > 1: for i, f in enumerate(funcs): if n == 2: face_basis = [sympy.Integer(1)] else: face_basis = sympy_lagrange(basix.CellType.triangle, n - 2) j = n * 3 for g in face_basis: for vec in [(1, 0), (0, 1)]: integrand = sum( (f_i * v_i) for f_i, v_i in zip(f, vec)) * g mat[i, j] = integrand.integrate( (x, 0, 1 - y)).integrate((y, 0, 1)) j += 1 elif celltype == basix.CellType.tetrahedron: tdim = 3 for i in range(n): for j in range(n - i): for k in range(n - i - j): for d in range(3): funcs += [[ x**k * y**j * z**i if m == d else 0 for m in range(3) ]] for j in range(n): for k in range(n - j): p = x**(n - 1 - j - k) * y**j * z**k funcs.append((x * p, y * p, z * p)) mat = numpy.empty((len(funcs), len(funcs)), dtype=object) # face normals for i, f in enumerate(funcs): if n == 1: face_basis = [sympy.Integer(1)] else: face_basis = sympy_lagrange(basix.CellType.triangle, n - 1) face_basis = [ a.subs(x, dummy[0]).subs(y, dummy[1]) for a in face_basis ] j = 0 for face in topology[2]: face_geom = [geometry[t, :] for t in face] axes = [ face_geom[1] - face_geom[0], face_geom[2] - face_geom[0] ] normal = [ axes[0][1] * axes[1][2] - axes[0][2] * axes[1][1], axes[0][2] * axes[1][0] - axes[0][0] * axes[1][2], axes[0][0] * axes[1][1] - axes[0][1] * axes[1][0] ] norm = sympy.sqrt(sum(i**2 for i in normal)) normal = [k / norm for k in normal] param = [ a + dummy[0] * b + dummy[1] * c for a, b, c in zip(face_geom[0], *axes) ] for g in face_basis: integrand = sum(f_i * v_i for f_i, v_i in zip(f, normal)) integrand = integrand.subs(x, param[0]).subs( y, param[1]).subs(z, param[2]) integrand *= g * norm mat[i, j] = integrand.integrate( (dummy[0], 0, 1 - dummy[1])).integrate( (dummy[1], 0, 1)) j += 1 assert j == 2 * n * (n + 1) if n > 1: for i, f in enumerate(funcs): if n == 2: interior_basis = [sympy.Integer(1)] else: interior_basis = sympy_lagrange(basix.CellType.tetrahedron, n - 2) j = 2 * n * (n + 1) for g in interior_basis: for vec in [(1, 0, 0), (0, 1, 0), (0, 0, 1)]: integrand = sum(f_i * v_i for f_i, v_i in zip(f, vec)) integrand *= g mat[i, j] = integrand.integrate( (x, 0, 1 - y - z)).integrate( (y, 0, 1 - z)).integrate((z, 0, 1)) j += 1 mat = sympy.Matrix(mat) mat = mat.inv() g = [] for r in range(mat.shape[0]): row = [] for dim in range(tdim): row.append( sum([v * funcs[i][dim] for i, v in enumerate(mat.row(r))])) g.append(row) return g
def sympy_nedelec(celltype, n): x = sympy.Symbol("x") y = sympy.Symbol("y") z = sympy.Symbol("z") from sympy import S topology = basix.topology(celltype) geometry = S(basix.geometry(celltype).astype(int)) dummy = [ sympy.Symbol("DUMMY1"), sympy.Symbol("DUMMY2"), sympy.Symbol("DUMMY3") ] funcs = [] if celltype == basix.CellType.triangle: tdim = 2 for i in range(n + 1): for j in range(n + 1 - i): for d in range(2): funcs += [[x**j * y**i if k == d else 0 for k in range(2)]] mat = numpy.empty((len(funcs), len(funcs)), dtype=object) # edge tangents edge_basis = sympy_lagrange(basix.CellType.interval, n) edge_basis = [a.subs(x, dummy[0]) for a in edge_basis] for i, f in enumerate(funcs): j = 0 for edge in topology[1]: edge_geom = [geometry[t, :] for t in edge] tangent = edge_geom[1] - edge_geom[0] norm = sympy.sqrt(sum(i**2 for i in tangent)) tangent = [i / norm for i in tangent] param = [(1 - dummy[0]) * a + dummy[0] * b for a, b in zip(edge_geom[0], edge_geom[1])] for g in edge_basis: integrand = sum( (f_i * v_i) for f_i, v_i in zip(f, tangent)) integrand = integrand.subs(x, param[0]).subs(y, param[1]) integrand *= g * norm mat[i, j] = integrand.integrate((dummy[0], 0, 1)) j += 1 # interior dofs if n > 1: face_basis = sympy_rt(basix.CellType.triangle, n - 1) for i, f in enumerate(funcs): j = (n + 1) * 3 for g in face_basis: integrand = sum((f_i * v_i) for f_i, v_i in zip(f, g)) mat[i, j] = integrand.integrate((x, 0, 1 - y)).integrate( (y, 0, 1)) j += 1 elif celltype == basix.CellType.tetrahedron: tdim = 3 for i in range(n + 1): for j in range(n + 1 - i): for k in range(n + 1 - i - j): for d in range(3): funcs += [[ x**k * y**j * z**i if m == d else 0 for m in range(3) ]] mat = numpy.empty((len(funcs), len(funcs)), dtype=object) # edge tangents edge_basis = sympy_lagrange(basix.CellType.interval, n) edge_basis = [a.subs(x, dummy[0]) for a in edge_basis] for i, f in enumerate(funcs): j = 0 for edge in topology[1]: edge_geom = [geometry[t, :] for t in edge] tangent = edge_geom[1] - edge_geom[0] norm = sympy.sqrt(sum(i**2 for i in tangent)) tangent = [i / norm for i in tangent] param = [(1 - dummy[0]) * a + dummy[0] * b for a, b in zip(edge_geom[0], edge_geom[1])] for g in edge_basis: integrand = sum( (f_i * v_i) for f_i, v_i in zip(f, tangent)) integrand = integrand.subs(x, param[0]).subs( y, param[1]).subs(z, param[2]) integrand *= g * norm mat[i, j] = integrand.integrate((dummy[0], 0, 1)) j += 1 # face dofs if n > 1: face_basis = sympy_rt(basix.CellType.triangle, n - 1) face_basis = [[b.subs(x, dummy[0]).subs(y, dummy[1]) for b in a] for a in face_basis] for i, f in enumerate(funcs): j = (n + 1) * 6 for face in topology[2]: face_geom = [geometry[t, :] for t in face] axes = [ face_geom[1] - face_geom[0], face_geom[2] - face_geom[0] ] norm = sympy.sqrt( sum(i**2 for i in [ axes[0][1] * axes[1][2] - axes[0][2] * axes[1][1], axes[0][2] * axes[1][0] - axes[0][0] * axes[1][2], axes[0][0] * axes[1][1] - axes[0][1] * axes[1][0] ])) scaled_axes = [] for a in axes: scaled_axes.append([k / norm for k in a]) param = [ a + dummy[0] * b + dummy[1] * c for a, b, c in zip(face_geom[0], *axes) ] this_face_basis = [[ a[0] * b + a[1] * c for b, c in zip(*scaled_axes) ] for a in face_basis] for g in this_face_basis: integrand = sum(f_i * v_i for f_i, v_i in zip(f, g)) integrand = integrand.subs(x, param[0]).subs( y, param[1]).subs(z, param[2]) integrand *= norm mat[i, j] = integrand.integrate( (dummy[0], 0, 1 - dummy[1])).integrate( (dummy[1], 0, 1)) j += 1 # interior dofs if n > 2: interior_basis = sympy_rt(basix.CellType.tetrahedron, n - 2) for i, f in enumerate(funcs): j = (n + 1) * 6 + 4 * (n - 1) * (n + 1) for g in interior_basis: integrand = sum(f_i * v_i for f_i, v_i in zip(f, g)) mat[i, j] = integrand.integrate( (x, 0, 1 - y - z)).integrate((y, 0, 1 - z)).integrate( (z, 0, 1)) j += 1 mat = sympy.Matrix(mat) mat = mat.inv() g = [] for r in range(mat.shape[0]): row = [] for dim in range(tdim): row.append( sum([v * funcs[i][dim] for i, v in enumerate(mat.row(r))])) g.append(row) return g
def sympy_nedelec(celltype, n): x = sympy.Symbol("x") y = sympy.Symbol("y") z = sympy.Symbol("z") from sympy import S topology = basix.topology(celltype) geometry = S(basix.geometry(celltype).astype(int)) dummy = [sympy.Symbol("DUMMY1"), sympy.Symbol("DUMMY2"), sympy.Symbol("DUMMY3")] funcs = [] if celltype == basix.CellType.triangle: tdim = 2 for i in range(n): for j in range(n - i): for d in range(2): funcs += [[x**j * y**i if k == d else 0 for k in range(2)]] for i in range(n): funcs += [[x ** (n - 1 - i) * y ** (i + 1), -x ** (n - i) * y ** i]] mat = numpy.empty((len(funcs), len(funcs)), dtype=object) # edge tangents if n == 1: edge_basis = [sympy.Integer(1)] else: edge_basis = sympy_lagrange(basix.CellType.interval, n - 1) edge_basis = [a.subs(x, dummy[0]) for a in edge_basis] for i, f in enumerate(funcs): j = 0 for edge in topology[1]: edge_geom = [geometry[t, :] for t in edge] tangent = edge_geom[1] - edge_geom[0] norm = sympy.sqrt(sum(i ** 2 for i in tangent)) tangent = [i / norm for i in tangent] param = [(1 - dummy[0]) * a + dummy[0] * b for a, b in zip(edge_geom[0], edge_geom[1])] for g in edge_basis: integrand = sum((f_i * v_i) for f_i, v_i in zip(f, tangent)) integrand = integrand.subs(x, param[0]).subs(y, param[1]) integrand *= g * norm mat[i, j] = integrand.integrate((dummy[0], 0, 1)) j += 1 # interior dofs if n > 1: if n == 2: face_basis = [sympy.Integer(1)] else: face_basis = sympy_lagrange(basix.CellType.triangle, n - 2) for i, f in enumerate(funcs): j = n * 3 for g in face_basis: for vec in [(1, 0), (0, 1)]: integrand = sum((f_i * v_i) for f_i, v_i in zip(f, vec)) * g mat[i, j] = integrand.integrate((x, 0, 1 - y)).integrate((y, 0, 1)) j += 1 elif celltype == basix.CellType.tetrahedron: tdim = 3 for i in range(n): for j in range(n - i): for k in range(n - i - j): for d in range(3): funcs += [[x**k * y**j * z**i if m == d else 0 for m in range(3)]] if n == 1: funcs += [[y, -x, sympy.Integer(0)], [z, sympy.Integer(0), -x], [sympy.Integer(0), z, -y]] elif n == 2: funcs += [ [y ** 2, -x * y, sympy.Integer(0)], [x * y, -x ** 2, sympy.Integer(0)], [z * y, -z * x, sympy.Integer(0)], [sympy.Integer(0), y * z, -y ** 2], [sympy.Integer(0), z ** 2, -z * y], [sympy.Integer(0), x * z, -x * y], [x * z, sympy.Integer(0), -x ** 2], [z ** 2, sympy.Integer(0), -z * x], ] elif n == 3: funcs += [ [x ** 2 * y, -x ** 3, sympy.Integer(0)], [x ** 2 * z, sympy.Integer(0), -x ** 3], [sympy.Integer(0), x ** 2 * z, -x ** 2 * y], [x * y ** 2, -x ** 2 * y, sympy.Integer(0)], [2 * x * y * z, -x ** 2 * z, -x ** 2 * y], [sympy.Integer(0), x * y * z, -x * y ** 2], [x * z ** 2, sympy.Integer(0), -x ** 2 * z], [sympy.Integer(0), x * z ** 2, -x * y * z], [y ** 3, -x * y ** 2, sympy.Integer(0)], [9 * y ** 2 * z, -4 * x * y * z, -5 * x * y ** 2], [sympy.Integer(0), y ** 2 * z, -y ** 3], [9 * y * z ** 2, -5 * x * z ** 2, -4 * x * y * z], [sympy.Integer(0), y * z ** 2, -y ** 2 * z], [z ** 3, sympy.Integer(0), -x * z ** 2], [sympy.Integer(0), z ** 3, -y * z ** 2], ] else: raise NotImplementedError mat = numpy.empty((len(funcs), len(funcs)), dtype=object) # edge tangents if n == 1: edge_basis = [sympy.Integer(1)] else: edge_basis = sympy_lagrange(basix.CellType.interval, n - 1) edge_basis = [a.subs(x, dummy[0]) for a in edge_basis] for i, f in enumerate(funcs): j = 0 for edge in topology[1]: edge_geom = [geometry[t, :] for t in edge] tangent = edge_geom[1] - edge_geom[0] norm = sympy.sqrt(sum(i ** 2 for i in tangent)) tangent = [i / norm for i in tangent] param = [(1 - dummy[0]) * a + dummy[0] * b for a, b in zip(edge_geom[0], edge_geom[1])] for g in edge_basis: integrand = sum((f_i * v_i) for f_i, v_i in zip(f, tangent)) integrand = integrand.subs(x, param[0]).subs(y, param[1]).subs(z, param[2]) integrand *= g * norm mat[i, j] = integrand.integrate((dummy[0], 0, 1)) j += 1 # face dofs if n > 1: def dot(a, b): return sum(i * j for i, j in zip(a, b)) def cross(a, b): assert len(a) == 3 and len(b) == 3 return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]] if n == 2: face_basis = [sympy.Integer(1)] else: face_basis = sympy_lagrange(basix.CellType.triangle, n - 2) face_basis = [a.subs(x, dummy[0]).subs(y, dummy[1]) for a in face_basis] for i, f in enumerate(funcs): j = n * 6 for face in topology[2]: face_geom = [geometry[t, :] for t in face] axes = [face_geom[1] - face_geom[0], face_geom[2] - face_geom[0]] norm = sympy.sqrt(sum(i**2 for i in cross(axes[0], axes[1]))) scaled_axes = [] for a in axes: scaled_axes.append([k / norm for k in a]) param = [a + dummy[0] * b + dummy[1] * c for a, b, c in zip(face_geom[0], *axes)] for g in face_basis: for vec in scaled_axes: integrand = dot(vec, f) integrand = integrand.subs(x, param[0]).subs(y, param[1]).subs(z, param[2]) integrand *= g * norm mat[i, j] = integrand.integrate((dummy[0], 0, 1 - dummy[1])).integrate((dummy[1], 0, 1)) j += 1 # interior dofs if n > 2: if n == 3: interior_basis = [sympy.Integer(1)] else: interior_basis = sympy_lagrange(basix.CellType.tetrahedron, n - 3) for i, f in enumerate(funcs): j = n * 6 + 4 * n * (n - 1) for g in interior_basis: for vec in [(1, 0, 0), (0, 1, 0), (0, 0, 1)]: integrand = sum(f_i * v_i for f_i, v_i in zip(f, vec)) integrand *= g mat[i, j] = integrand.integrate((x, 0, 1 - y - z)).integrate((y, 0, 1 - z)).integrate((z, 0, 1)) j += 1 mat = sympy.Matrix(mat) mat = mat.inv() g = [] for r in range(mat.shape[0]): row = [] for dim in range(tdim): row.append(sum([v * funcs[i][dim] for i, v in enumerate(mat.row(r))])) g.append(row) return g
def reference_cell_vertices(cellname): """Get the vertices of a reference cell.""" return basix.geometry(basix.cell.string_to_type(cellname))
def reference_geometry(self): """Get the geometry of the reference element.""" return basix.geometry(self.element.cell_type)
def reference_cell_vertices(cellname): return basix.geometry(basix_cells[cellname])
def reference_geometry(self): return basix.geometry(self.element.cell_type)
if len(p) == 3: return 100 * p[0] + 30 * p[1] def to_y(p): if len(p) == 1: return 120 if len(p) == 2: return 120 - 100 * p[1] if len(p) == 3: return 120 - 100 * p[2] - 40 * p[1] for shape in ["interval", "triangle", "tetrahedron", "quadrilateral", "hexahedron", "prism", "pyramid"]: cell_type = getattr(basix.CellType, shape) geometry = basix.geometry(cell_type) topology = basix.topology(cell_type) yadd = 0 width = 100 if shape == "interval": yadd = -100 if shape == "hexahedron": yadd = 40 width = 140 if shape == "pyramid": width = 140 if shape == "prism": yadd = 40 svg = ""
# The facets of a tetrahedron are triangular, so we create a quadrature # rule on a triangle. We use an order 3 rule so that we can integrate the # basis functions in our space exactly. points, weights = basix.make_quadrature(CellType.triangle, 3) # Next, we must map the quadrature points to our facet. We use the function # `geometry` to get the coordinates of the vertices of the tetrahedron, and # we use `sub_entity_connectivity` to see which vertices are adjacent to # our facet. We get the sub-entity connectivity using the indices 2 (facets # of 3D cells have dimension 2), 0 (vertices have dimension 0), and 0 (the # index of the facet we chose to use). # # Using this information, we can map the quadrature points to the facet. vertices = basix.geometry(CellType.tetrahedron) facet = basix.cell.sub_entity_connectivity(CellType.tetrahedron)[2][0][0] mapped_points = np.array([ vertices[facet[0]] * (1 - x - y) + vertices[facet[1]] * x + vertices[facet[2]] * y for x, y in points ]) # We now compute the normal derivative of the fifth basis function at # the quadrature points. First, we use `facet_outward_normals` to get # the normal vector to the facet. # # We then tabulate the basis functions of our space at the quadrature # points. We pass 1 in as the first argument, as we want the derivatives # of the basis functions. The result of tabulation will be an array of # size 4 by number of quadrature points by number of degrees of freedom. # To get the data that we want, we use the indices `1:` (to get the