def form_info(form): ufl_assert(isinstance(form, Form), "Expecting a Form.") bf = extract_arguments(form) cf = extract_coefficients(form) ci = form.integrals(Measure.CELL) ei = form.integrals(Measure.EXTERIOR_FACET) ii = form.integrals(Measure.INTERIOR_FACET) pi = form.integrals(Measure.POINT) mi = form.integrals(Measure.MACRO_CELL) s = "Form info:\n" s += " rank: %d\n" % len(bf) s += " num_coefficients: %d\n" % len(cf) s += " num_cell_integrals: %d\n" % len(ci) s += " num_exterior_facet_integrals: %d\n" % len(ei) s += " num_interior_facet_integrals: %d\n" % len(ii) s += " num_point_integrals: %d\n" % len(pi) s += " num_macro_cell_integrals: %d\n" % len(mi) for f in cf: if f._name: s += "\n" s += " Coefficient %d is named '%s'" % (f._count, f._name) s += "\n" for itg in ci: s += "\n" s += integral_info(itg) for itg in ei: s += "\n" s += integral_info(itg) for itg in ii: s += "\n" s += integral_info(itg) for itg in mi: s += "\n" s += integral_info(itg) return s
def compile_element(expression, dual_space=None, parameters=None, name="evaluate"): """Generate code for point evaluations. :arg expression: A UFL expression (may contain up to one coefficient, or one argument) :arg dual_space: if the expression has an argument, should we also distribute residual data? :returns: Some coffee AST """ if parameters is None: parameters = default_parameters() else: _ = default_parameters() _.update(parameters) parameters = _ expression = tsfc.ufl_utils.preprocess_expression(expression) # # Collect required coefficients try: arg, = extract_coefficients(expression) argument_multiindices = () coefficient = True if expression.ufl_shape: tensor_indices = tuple(gem.Index() for s in expression.ufl_shape) else: tensor_indices = () except ValueError: arg, = extract_arguments(expression) finat_elem = create_element(arg.ufl_element()) argument_multiindices = (finat_elem.get_indices(), ) argument_multiindex, = argument_multiindices value_shape = finat_elem.value_shape if value_shape: tensor_indices = argument_multiindex[-len(value_shape):] else: tensor_indices = () coefficient = False # Replace coordinates (if any) builder = firedrake_interface.KernelBuilderBase(scalar_type=ScalarType_c) domain = expression.ufl_domain() # Translate to GEM cell = domain.ufl_cell() dim = cell.topological_dimension() point = gem.Variable('X', (dim, )) point_arg = ast.Decl(ScalarType_c, ast.Symbol('X', rank=(dim, ))) config = dict(interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, argument_multiindices=argument_multiindices) context = tsfc.fem.GemPointContext(**config) # Abs-simplification expression = tsfc.ufl_utils.simplify_abs(expression) # Translate UFL -> GEM if coefficient: assert dual_space is None f_arg = [builder._coefficient(arg, "f")] else: f_arg = [] translator = tsfc.fem.Translator(context) result, = map_expr_dags(translator, [expression]) b_arg = [] if coefficient: if expression.ufl_shape: return_variable = gem.Indexed( gem.Variable('R', expression.ufl_shape), tensor_indices) result_arg = ast.Decl(ScalarType_c, ast.Symbol('R', rank=expression.ufl_shape)) result = gem.Indexed(result, tensor_indices) else: return_variable = gem.Indexed(gem.Variable('R', (1, )), (0, )) result_arg = ast.Decl(ScalarType_c, ast.Symbol('R', rank=(1, ))) else: return_variable = gem.Indexed( gem.Variable('R', finat_elem.index_shape), argument_multiindex) result = gem.Indexed(result, tensor_indices) if dual_space: elem = create_element(dual_space.ufl_element()) if elem.value_shape: var = gem.Indexed(gem.Variable("b", elem.value_shape), tensor_indices) b_arg = [ ast.Decl(ScalarType_c, ast.Symbol("b", rank=elem.value_shape)) ] else: var = gem.Indexed(gem.Variable("b", (1, )), (0, )) b_arg = [ast.Decl(ScalarType_c, ast.Symbol("b", rank=(1, )))] result = gem.Product(result, var) result_arg = ast.Decl(ScalarType_c, ast.Symbol('R', rank=finat_elem.index_shape)) # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent result, = gem.optimise.unroll_indexsum([result], predicate=predicate) # Translate GEM -> COFFEE result, = gem.impero_utils.preprocess_gem([result]) impero_c = gem.impero_utils.compile_gem([(return_variable, result)], tensor_indices) body = generate_coffee(impero_c, {}, parameters["precision"], ScalarType_c) # Build kernel tuple kernel_code = builder.construct_kernel( "pyop2_kernel_" + name, [result_arg] + b_arg + f_arg + [point_arg], body) return kernel_code
def compute_form_data( form, # Default arguments configured to behave the way old FFC expects it: do_apply_function_pullbacks=False, do_apply_integral_scaling=False, do_apply_geometry_lowering=False, preserve_geometry_types=(), do_apply_default_restrictions=True, do_apply_restrictions=True, do_estimate_degrees=True, ): # TODO: Move this to the constructor instead self = FormData() # --- Store untouched form for reference. # The user of FormData may get original arguments, # original coefficients, and form signature from this object. # But be aware that the set of original coefficients are not # the same as the ones used in the final UFC form. # See 'reduced_coefficients' below. self.original_form = form # --- Pass form integrands through some symbolic manipulation # Note: Default behaviour here will process form the way that is # currently expected by vanilla FFC # Lower abstractions for tensor-algebra types into index notation, # reducing the number of operators later algorithms and form # compilers need to handle form = apply_algebra_lowering(form) # Apply differentiation before function pullbacks, because for # example coefficient derivatives are more complicated to derive # after coefficients are rewritten, and in particular for # user-defined coefficient relations it just gets too messy form = apply_derivatives(form) # --- Group form integrals # TODO: Refactor this, it's rather opaque what this does # TODO: Is self.original_form.ufl_domains() right here? # It will matter when we start including 'num_domains' in ufc form. form = group_form_integrals(form, self.original_form.ufl_domains()) # Estimate polynomial degree of integrands now, before applying # any pullbacks and geometric lowering. Otherwise quad degrees # blow up horrifically. if do_estimate_degrees: form = attach_estimated_degrees(form) if do_apply_function_pullbacks: # Rewrite coefficients and arguments in terms of their # reference cell values with Piola transforms and symmetry # transforms injected where needed. # Decision: Not supporting grad(dolfin.Expression) without a # Domain. Current dolfin works if Expression has a # cell but this should be changed to a mesh. form = apply_function_pullbacks(form) # Scale integrals to reference cell frames if do_apply_integral_scaling: form = apply_integral_scaling(form) # Apply default restriction to fully continuous terminals if do_apply_default_restrictions: form = apply_default_restrictions(form) # Lower abstractions for geometric quantities into a smaller set # of quantities, allowing the form compiler to deal with a smaller # set of types and treating geometric quantities like any other # expressions w.r.t. loop-invariant code motion etc. if do_apply_geometry_lowering: form = apply_geometry_lowering(form, preserve_geometry_types) # Apply differentiation again, because the algorithms above can # generate new derivatives or rewrite expressions inside # derivatives if do_apply_function_pullbacks or do_apply_geometry_lowering: form = apply_derivatives(form) # Neverending story: apply_derivatives introduces new Jinvs, # which needs more geometry lowering if do_apply_geometry_lowering: form = apply_geometry_lowering(form, preserve_geometry_types) # Lower derivatives that may have appeared form = apply_derivatives(form) # Propagate restrictions to terminals if do_apply_restrictions: form = apply_restrictions(form) # --- Group integrals into IntegralData objects # Most of the heavy lifting is done above in group_form_integrals. self.integral_data = build_integral_data(form.integrals()) # --- Create replacements for arguments and coefficients # Figure out which form coefficients each integral should enable for itg_data in self.integral_data: itg_coeffs = set() # Get all coefficients in integrand for itg in itg_data.integrals: itg_coeffs.update(extract_coefficients(itg.integrand())) # Store with IntegralData object itg_data.integral_coefficients = itg_coeffs # Figure out which coefficients from the original form are # actually used in any integral (Differentiation may reduce the # set of coefficients w.r.t. the original form) reduced_coefficients_set = set() for itg_data in self.integral_data: reduced_coefficients_set.update(itg_data.integral_coefficients) self.reduced_coefficients = sorted(reduced_coefficients_set, key=lambda c: c.count()) self.num_coefficients = len(self.reduced_coefficients) self.original_coefficient_positions = [ i for i, c in enumerate(self.original_form.coefficients()) if c in self.reduced_coefficients ] # Store back into integral data which form coefficients are used # by each integral for itg_data in self.integral_data: itg_data.enabled_coefficients = [ bool(coeff in itg_data.integral_coefficients) for coeff in self.reduced_coefficients ] # --- Collect some trivial data # Get rank of form from argument list (assuming not a mixed arity form) self.rank = len(self.original_form.arguments()) # Extract common geometric dimension (topological is not common!) self.geometric_dimension = self.original_form.integrals()[0].ufl_domain( ).geometric_dimension() # --- Build mapping from old incomplete element objects to new # well defined elements. This is to support the Expression # construct in dolfin which subclasses Coefficient but doesn't # provide an element, and the Constant construct that doesn't # provide the domain that a Coefficient is supposed to have. A # future design iteration in UFL/UFC/FFC/DOLFIN may allow removal # of this mapping with the introduction of UFL types for # Expression-like functions that can be evaluated in quadrature # points. self.element_replace_map = _compute_element_mapping(self.original_form) # Mappings from elements and coefficients that reside in form to # objects with canonical numbering as well as completed cells and # elements renumbered_coefficients, function_replace_map = \ _build_coefficient_replace_map(self.reduced_coefficients, self.element_replace_map) self.function_replace_map = function_replace_map # --- Store various lists of elements and sub elements (adds # members to self) _compute_form_data_elements(self, self.original_form.arguments(), renumbered_coefficients, self.original_form.ufl_domains()) # --- Store number of domains for integral types # TODO: Group this by domain first. For now keep a backwards # compatible data structure. self.max_subdomain_ids = _compute_max_subdomain_ids(self.integral_data) # --- Checks _check_elements(self) _check_facet_geometry(self.integral_data) # TODO: This is a very expensive check... Replace with something # faster! preprocessed_form = reconstruct_form_from_integral_data(self.integral_data) check_form_arity( preprocessed_form, self.original_form.arguments()) # Currently testing how fast this is # TODO: This member is used by unit tests, change the tests to # remove this! self.preprocessed_form = preprocessed_form return self
def compute_form_data(form, # Default arguments configured to behave the way old FFC expects it: do_apply_function_pullbacks=False, do_apply_integral_scaling=False, do_apply_geometry_lowering=False, preserve_geometry_types=(), do_apply_default_restrictions=True, do_apply_restrictions=True, do_estimate_degrees=True, complex_mode=False, ): # TODO: Move this to the constructor instead self = FormData() # --- Store untouched form for reference. # The user of FormData may get original arguments, # original coefficients, and form signature from this object. # But be aware that the set of original coefficients are not # the same as the ones used in the final UFC form. # See 'reduced_coefficients' below. self.original_form = form # --- Pass form integrands through some symbolic manipulation # Note: Default behaviour here will process form the way that is # currently expected by vanilla FFC # Check that the form does not try to compare complex quantities: # if the quantites being compared are 'provably' real, wrap them # with Real, otherwise throw an error. if complex_mode: form = do_comparison_check(form) # Lower abstractions for tensor-algebra types into index notation, # reducing the number of operators later algorithms and form # compilers need to handle form = apply_algebra_lowering(form) # After lowering to index notation, remove any complex nodes that # have been introduced but are not wanted when working in real mode, # allowing for purely real forms to be written if not complex_mode: form = remove_complex_nodes(form) # Apply differentiation before function pullbacks, because for # example coefficient derivatives are more complicated to derive # after coefficients are rewritten, and in particular for # user-defined coefficient relations it just gets too messy form = apply_derivatives(form) # strip the coordinate derivatives away from the integral as they # interfere with the integral grouping (form, coordinate_derivatives) = strip_coordinate_derivatives(form) # --- Group form integrals # TODO: Refactor this, it's rather opaque what this does # TODO: Is self.original_form.ufl_domains() right here? # It will matter when we start including 'num_domains' in ufc form. form = group_form_integrals(form, self.original_form.ufl_domains()) # attach coordinate derivatives again form = attach_coordinate_derivatives(form, coordinate_derivatives) # Estimate polynomial degree of integrands now, before applying # any pullbacks and geometric lowering. Otherwise quad degrees # blow up horrifically. if do_estimate_degrees: form = attach_estimated_degrees(form) if do_apply_function_pullbacks: # Rewrite coefficients and arguments in terms of their # reference cell values with Piola transforms and symmetry # transforms injected where needed. # Decision: Not supporting grad(dolfin.Expression) without a # Domain. Current dolfin works if Expression has a # cell but this should be changed to a mesh. form = apply_function_pullbacks(form) # Scale integrals to reference cell frames if do_apply_integral_scaling: form = apply_integral_scaling(form) # Apply default restriction to fully continuous terminals if do_apply_default_restrictions: form = apply_default_restrictions(form) # Lower abstractions for geometric quantities into a smaller set # of quantities, allowing the form compiler to deal with a smaller # set of types and treating geometric quantities like any other # expressions w.r.t. loop-invariant code motion etc. if do_apply_geometry_lowering: form = apply_geometry_lowering(form, preserve_geometry_types) # Apply differentiation again, because the algorithms above can # generate new derivatives or rewrite expressions inside # derivatives if do_apply_function_pullbacks or do_apply_geometry_lowering: form = apply_derivatives(form) # Neverending story: apply_derivatives introduces new Jinvs, # which needs more geometry lowering if do_apply_geometry_lowering: form = apply_geometry_lowering(form, preserve_geometry_types) # Lower derivatives that may have appeared form = apply_derivatives(form) form = apply_coordinate_derivatives(form) # Propagate restrictions to terminals if do_apply_restrictions: form = apply_restrictions(form) # --- Group integrals into IntegralData objects # Most of the heavy lifting is done above in group_form_integrals. self.integral_data = build_integral_data(form.integrals()) # --- Create replacements for arguments and coefficients # Figure out which form coefficients each integral should enable for itg_data in self.integral_data: itg_coeffs = set() # Get all coefficients in integrand for itg in itg_data.integrals: itg_coeffs.update(extract_coefficients(itg.integrand())) # Store with IntegralData object itg_data.integral_coefficients = itg_coeffs # Figure out which coefficients from the original form are # actually used in any integral (Differentiation may reduce the # set of coefficients w.r.t. the original form) reduced_coefficients_set = set() for itg_data in self.integral_data: reduced_coefficients_set.update(itg_data.integral_coefficients) self.reduced_coefficients = sorted(reduced_coefficients_set, key=lambda c: c.count()) self.num_coefficients = len(self.reduced_coefficients) self.original_coefficient_positions = [i for i, c in enumerate(self.original_form.coefficients()) if c in self.reduced_coefficients] # Store back into integral data which form coefficients are used # by each integral for itg_data in self.integral_data: itg_data.enabled_coefficients = [bool(coeff in itg_data.integral_coefficients) for coeff in self.reduced_coefficients] # --- Collect some trivial data # Get rank of form from argument list (assuming not a mixed arity form) self.rank = len(self.original_form.arguments()) # Extract common geometric dimension (topological is not common!) self.geometric_dimension = self.original_form.integrals()[0].ufl_domain().geometric_dimension() # --- Build mapping from old incomplete element objects to new # well defined elements. This is to support the Expression # construct in dolfin which subclasses Coefficient but doesn't # provide an element, and the Constant construct that doesn't # provide the domain that a Coefficient is supposed to have. A # future design iteration in UFL/UFC/FFC/DOLFIN may allow removal # of this mapping with the introduction of UFL types for # Expression-like functions that can be evaluated in quadrature # points. self.element_replace_map = _compute_element_mapping(self.original_form) # Mappings from elements and coefficients that reside in form to # objects with canonical numbering as well as completed cells and # elements renumbered_coefficients, function_replace_map = \ _build_coefficient_replace_map(self.reduced_coefficients, self.element_replace_map) self.function_replace_map = function_replace_map # --- Store various lists of elements and sub elements (adds # members to self) _compute_form_data_elements(self, self.original_form.arguments(), renumbered_coefficients, self.original_form.ufl_domains()) # --- Store number of domains for integral types # TODO: Group this by domain first. For now keep a backwards # compatible data structure. self.max_subdomain_ids = _compute_max_subdomain_ids(self.integral_data) # --- Checks _check_elements(self) _check_facet_geometry(self.integral_data) # TODO: This is a very expensive check... Replace with something # faster! preprocessed_form = reconstruct_form_from_integral_data(self.integral_data) # If in real mode, remove complex nodes entirely. if not complex_mode: preprocessed_form = remove_complex_nodes(preprocessed_form) check_form_arity(preprocessed_form, self.original_form.arguments(), complex_mode) # Currently testing how fast this is # TODO: This member is used by unit tests, change the tests to # remove this! self.preprocessed_form = preprocessed_form return self
def compile_element(expression, dual_space=None, parameters=None, name="evaluate"): """Generate code for point evaluations. :arg expression: A UFL expression (may contain up to one coefficient, or one argument) :arg dual_space: if the expression has an argument, should we also distribute residual data? :returns: Some coffee AST """ if parameters is None: parameters = default_parameters() else: _ = default_parameters() _.update(parameters) parameters = _ expression = tsfc.ufl_utils.preprocess_expression(expression) # # Collect required coefficients try: arg, = extract_coefficients(expression) argument_multiindices = () coefficient = True if expression.ufl_shape: tensor_indices = tuple(gem.Index() for s in expression.ufl_shape) else: tensor_indices = () except ValueError: arg, = extract_arguments(expression) finat_elem = create_element(arg.ufl_element()) argument_multiindices = (finat_elem.get_indices(), ) argument_multiindex, = argument_multiindices value_shape = finat_elem.value_shape if value_shape: tensor_indices = argument_multiindex[-len(value_shape):] else: tensor_indices = () coefficient = False # Replace coordinates (if any) builder = firedrake_interface.KernelBuilderBase() domain = expression.ufl_domain() # Translate to GEM cell = domain.ufl_cell() dim = cell.topological_dimension() point = gem.Variable('X', (dim,)) point_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('X', rank=(dim,))) config = dict(interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, argument_multiindices=argument_multiindices) context = tsfc.fem.GemPointContext(**config) # Abs-simplification expression = tsfc.ufl_utils.simplify_abs(expression) # Translate UFL -> GEM if coefficient: assert dual_space is None f_arg = [builder._coefficient(arg, "f")] else: f_arg = [] translator = tsfc.fem.Translator(context) result, = map_expr_dags(translator, [expression]) b_arg = [] if coefficient: if expression.ufl_shape: return_variable = gem.Indexed(gem.Variable('R', expression.ufl_shape), tensor_indices) result_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('R', rank=expression.ufl_shape)) result = gem.Indexed(result, tensor_indices) else: return_variable = gem.Indexed(gem.Variable('R', (1,)), (0,)) result_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('R', rank=(1,))) else: return_variable = gem.Indexed(gem.Variable('R', finat_elem.index_shape), argument_multiindex) result = gem.Indexed(result, tensor_indices) if dual_space: elem = create_element(dual_space.ufl_element()) if elem.value_shape: var = gem.Indexed(gem.Variable("b", elem.value_shape), tensor_indices) b_arg = [ast.Decl(SCALAR_TYPE, ast.Symbol("b", rank=elem.value_shape))] else: var = gem.Indexed(gem.Variable("b", (1, )), (0, )) b_arg = [ast.Decl(SCALAR_TYPE, ast.Symbol("b", rank=(1, )))] result = gem.Product(result, var) result_arg = ast.Decl(SCALAR_TYPE, ast.Symbol('R', rank=finat_elem.index_shape)) # Unroll max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent result, = gem.optimise.unroll_indexsum([result], predicate=predicate) # Translate GEM -> COFFEE result, = gem.impero_utils.preprocess_gem([result]) impero_c = gem.impero_utils.compile_gem([(return_variable, result)], tensor_indices) body = generate_coffee(impero_c, {}, parameters["precision"]) # Build kernel tuple kernel_code = builder.construct_kernel("pyop2_kernel_" + name, [result_arg] + b_arg + f_arg + [point_arg], body) return kernel_code