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
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
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])
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,))
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
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,))
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
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
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 == ()
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 == ()
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)
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)
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
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
def promote(table): v = gem.partial_indexed(table, beta) u = gem.ListTensor(self.transform(v)) return gem.ComponentTensor(gem.Indexed(u, zeta), beta + zeta)
def expression(self): return gem.partial_indexed(gem.Literal(self.points), self.indices)
def expression(self): return gem.partial_indexed(self._points_expr, self.indices)
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)
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
def promote(table): v = gem.partial_indexed(table, beta) u = gem.ListTensor(self._transform(v)) return gem.ComponentTensor(gem.Indexed(u, zeta), beta + zeta)
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)
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,) )
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,))
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)