Пример #1
0
    def basis_evaluation(self, order, ps, entity=None, coordinate_mapping=None):
        """Return code for evaluating the element at known points on the
        reference element.

        :param order: return derivatives up to this order.
        :param ps: the point set object.
        :param entity: the cell entity on which to tabulate.
        """
        # Spatial dimension
        dimension = self.cell.get_spatial_dimension()

        # Shape of the tabulation matrix
        shape = tuple(index.extent for index in ps.indices) + self.index_shape + self.value_shape

        result = {}
        for derivative in range(order + 1):
            for alpha in mis(dimension, derivative):
                name = str.format("rt_{}_{}_{}_{}_{}_{}",
                                  self.variant,
                                  self.degree,
                                  ''.join(map(str, alpha)),
                                  self.shift_axes,
                                  'c' if self.continuous else 'd',
                                  {None: "",
                                   '+': "p",
                                   '-': "m"}[self.restriction])
                result[alpha] = gem.partial_indexed(gem.Variable(name, shape), ps.indices)
        return result
Пример #2
0
    def basis_evaluation(self, order, ps, entity=None):
        """Return code for evaluating the element at known points on the
        reference element.

        :param order: return derivatives up to this order.
        :param ps: the point set object.
        :param entity: the cell entity on which to tabulate.
        """
        # Spatial dimension
        dimension = self.cell.get_spatial_dimension()

        # Shape of the tabulation matrix
        shape = tuple(
            index.extent
            for index in ps.indices) + self.index_shape + self.value_shape

        result = {}
        for derivative in range(order + 1):
            for alpha in mis(dimension, derivative):
                name = str.format("rt_{}_{}_{}_{}_{}_{}", self.variant,
                                  self.degree, ''.join(map(str, alpha)),
                                  self.shift_axes,
                                  'c' if self.continuous else 'd', {
                                      None: "",
                                      '+': "p",
                                      '-': "m"
                                  }[self.restriction])
                result[alpha] = gem.partial_indexed(gem.Variable(name, shape),
                                                    ps.indices)
        return result
Пример #3
0
 def coefficient(self, ufl_coefficient, restriction):
     """A function that maps :class:`ufl.Coefficient`s to GEM
     expressions."""
     kernel_arg = self.coefficient_map[ufl_coefficient]
     if ufl_coefficient.ufl_element().family() == 'Real':
         return kernel_arg
     else:
         return gem.partial_indexed(kernel_arg, {None: (), '+': (0,), '-': (1,)}[restriction])
Пример #4
0
 def _selector(self, callback, opts, restriction):
     """Helper function for selecting code for the correct entity
     at run-time."""
     if len(opts) == 1:
         return callback(opts[0])
     else:
         results = gem.ListTensor(list(map(callback, opts)))
         f = self.facet_number(restriction)
         return gem.partial_indexed(results, (f,))
Пример #5
0
 def _coefficient(self, coefficient, name):
     element = create_element(coefficient.ufl_element())
     shape = self.shape + element.index_shape
     size = numpy.prod(shape, dtype=int)
     funarg = ast.Decl(SCALAR_TYPE, ast.Symbol(name), pointers=[("restrict", )],
                       qualifiers=["const"])
     expression = gem.reshape(gem.Variable(name, (size, )), shape)
     expression = gem.partial_indexed(expression, self.indices)
     self.coefficient_map[coefficient] = expression
     return funarg
Пример #6
0
 def callback(key):
     table = ctx.tabulation_manager[key]
     if len(table.shape) == 1:
         # Cellwise constant
         row = table
     else:
         table = ctx.index_selector(lambda i: as_gem(table.array[i]),
                                    mt.restriction)
         row = gem.partial_indexed(table, (ctx.point_index,))
     return gem.Indexed(row, (argument_index,))
Пример #7
0
 def _coefficient(self, coefficient, name):
     element = create_element(coefficient.ufl_element())
     shape = self.shape + element.index_shape
     size = numpy.prod(shape, dtype=int)
     funarg = ast.Decl(SCALAR_TYPE, ast.Symbol(name), pointers=[("restrict", )],
                       qualifiers=["const"])
     expression = gem.reshape(gem.Variable(name, (size, )), shape)
     expression = gem.partial_indexed(expression, self.indices)
     self.coefficient_map[coefficient] = expression
     return funarg
Пример #8
0
    def basis_evaluation(self, order, ps, entity=None):
        '''Return code for evaluating the element at known points on the
        reference element.

        :param order: return derivatives up to this order.
        :param ps: the point set.
        :param entity: the cell entity on which to tabulate.
        '''
        space_dimension = self._element.space_dimension()
        value_size = np.prod(self._element.value_shape(), dtype=int)
        fiat_result = self._element.tabulate(order, ps.points, entity)
        result = {}
        for alpha, fiat_table in iteritems(fiat_result):
            if isinstance(fiat_table, Exception):
                result[alpha] = gem.Failure(
                    self.index_shape + self.value_shape, fiat_table)
                continue

            derivative = sum(alpha)
            table_roll = fiat_table.reshape(space_dimension, value_size,
                                            len(ps.points)).transpose(1, 2, 0)

            exprs = []
            for table in table_roll:
                if derivative < self.degree:
                    point_indices = ps.indices
                    point_shape = tuple(index.extent
                                        for index in point_indices)
                    exprs.append(
                        gem.partial_indexed(
                            gem.Literal(
                                table.reshape(point_shape + self.index_shape)),
                            point_indices))
                elif derivative == self.degree:
                    # Make sure numerics satisfies theory
                    assert np.allclose(table, table.mean(axis=0,
                                                         keepdims=True))
                    exprs.append(gem.Literal(table[0]))
                else:
                    # Make sure numerics satisfies theory
                    assert np.allclose(table, 0.0)
                    exprs.append(gem.Zero(self.index_shape))
            if self.value_shape:
                beta = self.get_indices()
                zeta = self.get_value_indices()
                result[alpha] = gem.ComponentTensor(
                    gem.Indexed(
                        gem.ListTensor(
                            np.array([
                                gem.Indexed(expr, beta) for expr in exprs
                            ]).reshape(self.value_shape)), zeta), beta + zeta)
            else:
                expr, = exprs
                result[alpha] = expr
        return result
Пример #9
0
 def dual_basis(self):
     Q, x = self.wrappee.dual_basis
     beta = self.get_indices()
     zeta = self.get_value_indices()
     # Index out the basis indices from wrapee's Q, to get
     # something of wrappee.value_shape, then promote to new shape
     # with the same transform as done for basis evaluation
     Q = gem.ListTensor(self.transform(gem.partial_indexed(Q, beta)))
     # Finally wrap up Q in shape again (now with some extra
     # value_shape indices)
     return gem.ComponentTensor(Q[zeta], beta + zeta), x
Пример #10
0
def test_cellwise_constant(cell, degree):
    dim = cell.get_spatial_dimension()
    element = finat.Lagrange(cell, degree)
    index = gem.Index()
    point = gem.partial_indexed(gem.Variable('X', (17, dim)), (index,))

    order = 2
    for alpha, table in element.point_evaluation(order, point).items():
        if sum(alpha) < degree:
            assert table.free_indices == (index,)
        else:
            assert table.free_indices == ()
Пример #11
0
def test_cellwise_constant(cell, degree):
    dim = cell.get_spatial_dimension()
    element = finat.Lagrange(cell, degree)
    index = gem.Index()
    point = gem.partial_indexed(gem.Variable('X', (17, dim)), (index, ))

    order = 2
    for alpha, table in element.point_evaluation(order, point).items():
        if sum(alpha) < degree:
            assert table.free_indices == (index, )
        else:
            assert table.free_indices == ()
Пример #12
0
def translate_cell_coordinate(terminal, mt, ctx):
    if ctx.integration_dim == ctx.fiat_cell.get_dimension():
        return ctx.point_expr

    # This destroys the structure of the quadrature points, but since
    # this code path is only used to implement CellCoordinate in facet
    # integrals, hopefully it does not matter much.
    ps = ctx.point_set
    point_shape = tuple(index.extent for index in ps.indices)

    def callback(entity_id):
        t = ctx.fiat_cell.get_entity_transform(ctx.integration_dim, entity_id)
        data = numpy.asarray(list(map(t, ps.points)))
        return gem.Literal(data.reshape(point_shape + data.shape[1:]))

    return gem.partial_indexed(ctx.entity_selector(callback, mt.restriction),
                               ps.indices)
Пример #13
0
def translate_cell_coordinate(terminal, mt, ctx):
    if ctx.integration_dim == ctx.fiat_cell.get_dimension():
        return ctx.point_expr

    # This destroys the structure of the quadrature points, but since
    # this code path is only used to implement CellCoordinate in facet
    # integrals, hopefully it does not matter much.
    ps = ctx.point_set
    point_shape = tuple(index.extent for index in ps.indices)

    def callback(entity_id):
        t = ctx.fiat_cell.get_entity_transform(ctx.integration_dim, entity_id)
        data = numpy.asarray(list(map(t, ps.points)))
        return gem.Literal(data.reshape(point_shape + data.shape[1:]))

    return gem.partial_indexed(ctx.entity_selector(callback, mt.restriction),
                               ps.indices)
Пример #14
0
    def callback(key):
        table = ctx.tabulation_manager[key]
        if len(table.shape) == 1:
            # Cellwise constant
            row = table
            if numpy.count_nonzero(table.array) <= 2:
                assert row.shape == vec.shape
                return reduce(gem.Sum,
                              [gem.Product(gem.Indexed(row, (i,)), gem.Indexed(vec, (i,)))
                               for i in range(row.shape[0])],
                              gem.Zero())
        else:
            table = ctx.index_selector(lambda i: as_gem(table.array[i]),
                                       mt.restriction)
            row = gem.partial_indexed(table, (ctx.point_index,))

        r = ctx.index_cache[terminal.ufl_element()]
        return gem.IndexSum(gem.Product(gem.Indexed(row, (r,)),
                                        gem.Indexed(vec, (r,))), r)
Пример #15
0
    def basis_evaluation(self,
                         order,
                         ps,
                         entity=None,
                         coordinate_mapping=None):
        '''Return code for evaluating the element at known points on the
        reference element.

        :param order: return derivatives up to this order.
        :param ps: the point set.
        :param entity: the cell entity on which to tabulate.
        '''
        # Build everything in sympy
        vs, xx, _ = self._basis

        # and convert -- all this can be used for each derivative!
        phys_verts = coordinate_mapping.physical_vertices()

        phys_points = gem.partial_indexed(
            coordinate_mapping.physical_points(ps, entity=entity), ps.indices)

        repl = dict(
            (vs[idx], phys_verts[idx]) for idx in numpy.ndindex(vs.shape))

        repl.update(zip(xx, phys_points))

        mapper = gem.node.Memoizer(sympy2gem)
        mapper.bindings = repl

        result = {}

        for i in range(order + 1):
            alphas = mis(2, i)
            for alpha in alphas:
                dphis = self._basis_deriv(xx, alpha)
                result[alpha] = gem.ListTensor(list(map(mapper, dphis)))

        return result
Пример #16
0
    def basis_evaluation(self,
                         order,
                         ps,
                         entity=None,
                         coordinate_mapping=None):
        '''Return code for evaluating the element at known points on the
        reference element.

        :param order: return derivatives up to this order.
        :param ps: the point set.
        :param entity: the cell entity on which to tabulate.
        '''
        space_dimension = self._element.space_dimension()
        value_size = np.prod(self._element.value_shape(), dtype=int)
        fiat_result = self._element.tabulate(order, ps.points, entity)
        result = {}
        # In almost all cases, we have
        # self.space_dimension() == self._element.space_dimension()
        # But for Bell, FIAT reports 21 basis functions,
        # but FInAT only 18 (because there are actually 18
        # basis functions, and the additional 3 are for
        # dealing with transformations between physical
        # and reference space).
        index_shape = (self._element.space_dimension(), )
        for alpha, fiat_table in fiat_result.items():
            if isinstance(fiat_table, Exception):
                result[alpha] = gem.Failure(
                    self.index_shape + self.value_shape, fiat_table)
                continue

            derivative = sum(alpha)
            table_roll = fiat_table.reshape(space_dimension, value_size,
                                            len(ps.points)).transpose(1, 2, 0)

            exprs = []
            for table in table_roll:
                if derivative < self.degree:
                    point_indices = ps.indices
                    point_shape = tuple(index.extent
                                        for index in point_indices)

                    exprs.append(
                        gem.partial_indexed(
                            gem.Literal(
                                table.reshape(point_shape + index_shape)),
                            point_indices))
                elif derivative == self.degree:
                    # Make sure numerics satisfies theory
                    exprs.append(gem.Literal(table[0]))
                else:
                    # Make sure numerics satisfies theory
                    assert np.allclose(table, 0.0)
                    exprs.append(gem.Zero(self.index_shape))
            if self.value_shape:
                # As above, this extent may be different from that
                # advertised by the finat element.
                beta = tuple(gem.Index(extent=i) for i in index_shape)
                assert len(beta) == len(self.get_indices())

                zeta = self.get_value_indices()
                result[alpha] = gem.ComponentTensor(
                    gem.Indexed(
                        gem.ListTensor(
                            np.array([
                                gem.Indexed(expr, beta) for expr in exprs
                            ]).reshape(self.value_shape)), zeta), beta + zeta)
            else:
                expr, = exprs
                result[alpha] = expr
        return result
Пример #17
0
 def promote(table):
     v = gem.partial_indexed(table, beta)
     u = gem.ListTensor(self.transform(v))
     return gem.ComponentTensor(gem.Indexed(u, zeta), beta + zeta)
Пример #18
0
 def expression(self):
     return gem.partial_indexed(gem.Literal(self.points), self.indices)
Пример #19
0
 def expression(self):
     return gem.partial_indexed(self._points_expr, self.indices)
Пример #20
0
def compile_expression_dual_evaluation(expression,
                                       to_element,
                                       *,
                                       domain=None,
                                       interface=None,
                                       parameters=None,
                                       coffee=False):
    """Compile a UFL expression to be evaluated against a compile-time known reference element's dual basis.

    Useful for interpolating UFL expressions into e.g. N1curl spaces.

    :arg expression: UFL expression
    :arg to_element: A FInAT element for the target space
    :arg domain: optional UFL domain the expression is defined on (required when expression contains no domain).
    :arg interface: backend module for the kernel interface
    :arg parameters: parameters object
    :arg coffee: compile coffee kernel instead of loopy kernel
    """
    import coffee.base as ast
    import loopy as lp

    # Just convert FInAT element to FIAT for now.
    # Dual evaluation in FInAT will bring a thorough revision.
    to_element = to_element.fiat_equivalent

    if any(len(dual.deriv_dict) != 0 for dual in to_element.dual_basis()):
        raise NotImplementedError(
            "Can only interpolate onto dual basis functionals without derivative evaluation, sorry!"
        )

    if parameters is None:
        parameters = default_parameters()
    else:
        _ = default_parameters()
        _.update(parameters)
        parameters = _

    # Determine whether in complex mode
    complex_mode = is_complex(parameters["scalar_type"])

    # Find out which mapping to apply
    try:
        mapping, = set(to_element.mapping())
    except ValueError:
        raise NotImplementedError(
            "Don't know how to interpolate onto zany spaces, sorry")
    expression = apply_mapping(expression, mapping, domain)

    # Apply UFL preprocessing
    expression = ufl_utils.preprocess_expression(expression,
                                                 complex_mode=complex_mode)

    # Initialise kernel builder
    if interface is None:
        if coffee:
            import tsfc.kernel_interface.firedrake as firedrake_interface_coffee
            interface = firedrake_interface_coffee.ExpressionKernelBuilder
        else:
            # Delayed import, loopy is a runtime dependency
            import tsfc.kernel_interface.firedrake_loopy as firedrake_interface_loopy
            interface = firedrake_interface_loopy.ExpressionKernelBuilder

    builder = interface(parameters["scalar_type"])
    arguments = extract_arguments(expression)
    argument_multiindices = tuple(
        builder.create_element(arg.ufl_element()).get_indices()
        for arg in arguments)

    # Replace coordinates (if any) unless otherwise specified by kwarg
    if domain is None:
        domain = expression.ufl_domain()
    assert domain is not None

    # Collect required coefficients
    first_coefficient_fake_coords = False
    coefficients = extract_coefficients(expression)
    if has_type(expression, GeometricQuantity) or any(
            fem.needs_coordinate_mapping(c.ufl_element())
            for c in coefficients):
        # Create a fake coordinate coefficient for a domain.
        coords_coefficient = ufl.Coefficient(
            ufl.FunctionSpace(domain, domain.ufl_coordinate_element()))
        builder.domain_coordinate[domain] = coords_coefficient
        builder.set_cell_sizes(domain)
        coefficients = [coords_coefficient] + coefficients
        first_coefficient_fake_coords = True
    builder.set_coefficients(coefficients)

    # Split mixed coefficients
    expression = ufl_utils.split_coefficients(expression,
                                              builder.coefficient_split)

    # Translate to GEM
    kernel_cfg = dict(
        interface=builder,
        ufl_cell=domain.ufl_cell(),
        # FIXME: change if we ever implement
        # interpolation on facets.
        integral_type="cell",
        argument_multiindices=argument_multiindices,
        index_cache={},
        scalar_type=parameters["scalar_type"])

    if all(
            isinstance(dual, PointEvaluation)
            for dual in to_element.dual_basis()):
        # This is an optimisation for point-evaluation nodes which
        # should go away once FInAT offers the interface properly
        qpoints = []
        # Everything is just a point evaluation.
        for dual in to_element.dual_basis():
            ptdict = dual.get_point_dict()
            qpoint, = ptdict.keys()
            (qweight, component), = ptdict[qpoint]
            assert allclose(qweight, 1.0)
            assert component == ()
            qpoints.append(qpoint)
        point_set = PointSet(qpoints)
        config = kernel_cfg.copy()
        config.update(point_set=point_set)

        # Allow interpolation onto QuadratureElements to refer to the quadrature
        # rule they represent
        if isinstance(to_element, FIAT.QuadratureElement):
            assert allclose(asarray(qpoints), asarray(to_element._points))
            quad_rule = QuadratureRule(point_set, to_element._weights)
            config["quadrature_rule"] = quad_rule

        expr, = fem.compile_ufl(expression, **config, point_sum=False)
        # In some cases point_set.indices may be dropped from expr, but nothing
        # new should now appear
        assert set(expr.free_indices) <= set(
            chain(point_set.indices, *argument_multiindices))
        shape_indices = tuple(gem.Index() for _ in expr.shape)
        basis_indices = point_set.indices
        ir = gem.Indexed(expr, shape_indices)
    else:
        # This is general code but is more unrolled than necssary.
        dual_expressions = []  # one for each functional
        broadcast_shape = len(expression.ufl_shape) - len(
            to_element.value_shape())
        shape_indices = tuple(gem.Index()
                              for _ in expression.ufl_shape[:broadcast_shape])
        expr_cache = {}  # Sharing of evaluation of the expression at points
        for dual in to_element.dual_basis():
            pts = tuple(sorted(dual.get_point_dict().keys()))
            try:
                expr, point_set = expr_cache[pts]
            except KeyError:
                point_set = PointSet(pts)
                config = kernel_cfg.copy()
                config.update(point_set=point_set)
                expr, = fem.compile_ufl(expression, **config, point_sum=False)
                # In some cases point_set.indices may be dropped from expr, but
                # nothing new should now appear
                assert set(expr.free_indices) <= set(
                    chain(point_set.indices, *argument_multiindices))
                expr = gem.partial_indexed(expr, shape_indices)
                expr_cache[pts] = expr, point_set
            weights = collections.defaultdict(list)
            for p in pts:
                for (w, cmp) in dual.get_point_dict()[p]:
                    weights[cmp].append(w)
            qexprs = gem.Zero()
            for cmp in sorted(weights):
                qweights = gem.Literal(weights[cmp])
                qexpr = gem.Indexed(expr, cmp)
                qexpr = gem.index_sum(
                    gem.Indexed(qweights, point_set.indices) * qexpr,
                    point_set.indices)
                qexprs = gem.Sum(qexprs, qexpr)
            assert qexprs.shape == ()
            assert set(qexprs.free_indices) == set(
                chain(shape_indices, *argument_multiindices))
            dual_expressions.append(qexprs)
        basis_indices = (gem.Index(), )
        ir = gem.Indexed(gem.ListTensor(dual_expressions), basis_indices)

    # Build kernel body
    return_indices = basis_indices + shape_indices + tuple(
        chain(*argument_multiindices))
    return_shape = tuple(i.extent for i in return_indices)
    return_var = gem.Variable('A', return_shape)
    if coffee:
        return_arg = ast.Decl(parameters["scalar_type"],
                              ast.Symbol('A', rank=return_shape))
    else:
        return_arg = lp.GlobalArg("A",
                                  dtype=parameters["scalar_type"],
                                  shape=return_shape)

    return_expr = gem.Indexed(return_var, return_indices)

    # TODO: one should apply some GEM optimisations as in assembly,
    # but we don't for now.
    ir, = impero_utils.preprocess_gem([ir])
    impero_c = impero_utils.compile_gem([(return_expr, ir)], return_indices)
    index_names = dict(
        (idx, "p%d" % i) for (i, idx) in enumerate(basis_indices))
    # Handle kernel interface requirements
    builder.register_requirements([ir])
    # Build kernel tuple
    return builder.construct_kernel(return_arg, impero_c, index_names,
                                    first_coefficient_fake_coords)
Пример #21
0
    def basis_evaluation(self, order, ps, entity=None, coordinate_mapping=None):
        '''Return code for evaluating the element at known points on the
        reference element.

        :param order: return derivatives up to this order.
        :param ps: the point set.
        :param entity: the cell entity on which to tabulate.
        '''
        space_dimension = self._element.space_dimension()
        value_size = np.prod(self._element.value_shape(), dtype=int)
        fiat_result = self._element.tabulate(order, ps.points, entity)
        result = {}
        # In almost all cases, we have
        # self.space_dimension() == self._element.space_dimension()
        # But for Bell, FIAT reports 21 basis functions,
        # but FInAT only 18 (because there are actually 18
        # basis functions, and the additional 3 are for
        # dealing with transformations between physical
        # and reference space).
        index_shape = (self._element.space_dimension(),)
        for alpha, fiat_table in fiat_result.items():
            if isinstance(fiat_table, Exception):
                result[alpha] = gem.Failure(self.index_shape + self.value_shape, fiat_table)
                continue

            derivative = sum(alpha)
            table_roll = fiat_table.reshape(
                space_dimension, value_size, len(ps.points)
            ).transpose(1, 2, 0)

            exprs = []
            for table in table_roll:
                if derivative < self.degree:
                    point_indices = ps.indices
                    point_shape = tuple(index.extent for index in point_indices)

                    exprs.append(gem.partial_indexed(
                        gem.Literal(table.reshape(point_shape + index_shape)),
                        point_indices
                    ))
                elif derivative == self.degree:
                    # Make sure numerics satisfies theory
                    exprs.append(gem.Literal(table[0]))
                else:
                    # Make sure numerics satisfies theory
                    assert np.allclose(table, 0.0)
                    exprs.append(gem.Zero(self.index_shape))
            if self.value_shape:
                beta = self.get_indices()
                zeta = self.get_value_indices()
                result[alpha] = gem.ComponentTensor(
                    gem.Indexed(
                        gem.ListTensor(np.array(
                            [gem.Indexed(expr, beta) for expr in exprs]
                        ).reshape(self.value_shape)),
                        zeta),
                    beta + zeta
                )
            else:
                expr, = exprs
                result[alpha] = expr
        return result
Пример #22
0
 def promote(table):
     v = gem.partial_indexed(table, beta)
     u = gem.ListTensor(self._transform(v))
     return gem.ComponentTensor(gem.Indexed(u, zeta), beta + zeta)
Пример #23
0
 def cell_size(self, restriction):
     f = {None: (), '+': (0, ), '-': (1, )}[restriction]
     # cell_sizes expression must have been set up by now.
     return gem.partial_indexed(self._cell_sizes, f)
Пример #24
0
def translate_cell_coordinate(terminal, mt, params):
    return gem.partial_indexed(
        params.index_selector(lambda i: gem.Literal(params.entity_points[i]), mt.restriction), (params.point_index,)
    )
Пример #25
0
def translate_facet_coordinate(terminal, mt, params):
    assert params.integration_dim != params.fiat_cell.get_dimension()
    points = params.points
    return gem.partial_indexed(gem.Literal(points), (params.point_index,))
Пример #26
0
 def cell_size(self, restriction):
     if not hasattr(self, "_cell_sizes"):
         raise RuntimeError("Haven't called set_cell_sizes")
     f = {None: (), '+': (0, ), '-': (1, )}[restriction]
     # cell_sizes expression must have been set up by now.
     return gem.partial_indexed(self._cell_sizes, f)
Пример #27
0
 def expression(self):
     return gem.partial_indexed(gem.Literal(self.points), self.indices)