def preprocess_expression(expression, complex_mode=False, do_apply_restrictions=False): """Imitates the compute_form_data processing pipeline. :arg complex_mode: Are we in complex UFL mode? :arg do_apply_restrictions: Propogate restrictions to terminals? Useful, for example, to preprocess non-scalar expressions, which are not and cannot be forms. """ if complex_mode: expression = do_comparison_check(expression) else: expression = remove_complex_nodes(expression) expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) if not complex_mode: expression = remove_complex_nodes(expression) if do_apply_restrictions: expression = apply_restrictions(expression) return expression
def compute_integrand_scaling_factor(integral): """Change integrand geometry to the right representations.""" domain = integral.ufl_domain() integral_type = integral.integral_type() # co = CellOrientation(domain) weight = QuadratureWeight(domain) tdim = domain.topological_dimension() # gdim = domain.geometric_dimension() # Polynomial degree of integrand scaling degree = 0 if integral_type == "cell": detJ = JacobianDeterminant(domain) degree = estimate_total_polynomial_degree( apply_geometry_lowering(detJ)) # Despite the abs, |detJ| is polynomial except for # self-intersecting cells, where we have other problems. scale = abs(detJ) * weight elif integral_type.startswith("exterior_facet"): if tdim > 1: # Scaling integral by facet jacobian determinant and # quadrature weight detFJ = FacetJacobianDeterminant(domain) degree = estimate_total_polynomial_degree( apply_geometry_lowering(detFJ)) scale = detFJ * weight else: # No need to scale 'integral' over a vertex scale = 1 elif integral_type.startswith("interior_facet"): if tdim > 1: # Scaling integral by facet jacobian determinant from one # side and quadrature weight detFJ = FacetJacobianDeterminant(domain) degree = estimate_total_polynomial_degree( apply_geometry_lowering(detFJ)) scale = detFJ('+') * weight else: # No need to scale 'integral' over a vertex scale = 1 elif integral_type in custom_integral_types: # Scaling with custom weight, which includes eventual volume # scaling scale = weight elif integral_type in point_integral_types: # No need to scale 'integral' over a point scale = 1 else: error("Unknown integral type {}, don't know how to scale.".format( integral_type)) return scale, degree
def compute_integrand_scaling_factor(integral): """Change integrand geometry to the right representations.""" domain = integral.ufl_domain() integral_type = integral.integral_type() # co = CellOrientation(domain) weight = QuadratureWeight(domain) tdim = domain.topological_dimension() # gdim = domain.geometric_dimension() # Polynomial degree of integrand scaling degree = 0 if integral_type == "cell": detJ = JacobianDeterminant(domain) degree = estimate_total_polynomial_degree(apply_geometry_lowering(detJ)) # Despite the abs, |detJ| is polynomial except for # self-intersecting cells, where we have other problems. scale = abs(detJ) * weight elif integral_type.startswith("exterior_facet"): if tdim > 1: # Scaling integral by facet jacobian determinant and # quadrature weight detFJ = FacetJacobianDeterminant(domain) degree = estimate_total_polynomial_degree(apply_geometry_lowering(detFJ)) scale = detFJ * weight else: # No need to scale 'integral' over a vertex scale = 1 elif integral_type.startswith("interior_facet"): if tdim > 1: # Scaling integral by facet jacobian determinant from one # side and quadrature weight detFJ = FacetJacobianDeterminant(domain) degree = estimate_total_polynomial_degree(apply_geometry_lowering(detFJ)) scale = detFJ('+') * weight else: # No need to scale 'integral' over a vertex scale = 1 elif integral_type in custom_integral_types: # Scaling with custom weight, which includes eventual volume # scaling scale = weight elif integral_type in point_integral_types: # No need to scale 'integral' over a point scale = 1 else: error("Unknown integral type {}, don't know how to scale.".format(integral_type)) return scale, degree
def preprocess_expression(expression): """Imitates the compute_form_data processing pipeline. Useful, for example, to preprocess non-scalar expressions, which are not and cannot be forms. """ expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) return expression
def preprocess_expression(expression): """Imitates the compute_form_data processing pipeline. Useful, for example, to preprocess non-scalar expressions, which are not and cannot be forms. """ expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) expression = apply_geometry_lowering(expression, preserve_geometry_types) expression = apply_derivatives(expression) return expression
def test_index_simplification_reference_grad(self): mesh = Mesh(VectorElement("P", quadrilateral, 1)) i, = indices(1) A = as_tensor(Indexed(Jacobian(mesh), MultiIndex((i, i))), (i,)) expr = apply_derivatives(apply_geometry_lowering( apply_algebra_lowering(A[0]))) assert expr == ReferenceGrad(SpatialCoordinate(mesh))[0, 0] assert expr.ufl_free_indices == () assert expr.ufl_shape == ()
def change_integrand_geometry_representation(integrand, scale, integral_type): """Change integrand geometry to the right representations.""" integrand = apply_function_pullbacks(integrand) integrand = change_to_reference_grad(integrand) integrand = integrand * scale if integral_type == "quadrature": physical_coordinates_known = True else: physical_coordinates_known = False integrand = apply_geometry_lowering(integrand, physical_coordinates_known) return integrand
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 compile_ufl_kernel(expression, to_pts, to_element, fs): import collections from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms import extract_arguments, extract_coefficients from gem import gem, impero_utils from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.kernel_interface import (KernelBuilderBase, needs_cell_orientations, cell_orientations_coffee_arg) # Imitate the compute_form_data processing pipeline # # Unfortunately, we cannot call compute_form_data here, since # we only have an expression, not a form expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) expression = apply_geometry_lowering(expression) expression = apply_derivatives(expression) expression = apply_geometry_lowering(expression) expression = apply_derivatives(expression) # Replace coordinates (if any) if expression.ufl_domain(): assert fs.mesh() == expression.ufl_domain() expression = ufl_utils.replace_coordinates(expression, fs.mesh().coordinates) if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") builder = KernelBuilderBase() args = [] coefficients = extract_coefficients(expression) for i, coefficient in enumerate(coefficients): args.append(builder.coefficient(coefficient, "w_%d" % i)) point_index = gem.Index(name='p') ir = fem.process('cell', fs.mesh().ufl_cell(), to_pts, None, point_index, (), expression, builder.coefficient_mapper, collections.defaultdict(gem.Index)) assert len(ir) == 1 # Deal with non-scalar expressions tensor_indices = () if fs.shape: tensor_indices = tuple(gem.Index() for s in fs.shape) ir = [gem.Indexed(ir[0], tensor_indices)] # Build kernel body return_var = gem.Variable('A', (len(to_pts),) + fs.shape) return_expr = gem.Indexed(return_var, (point_index,) + tensor_indices) impero_c = impero_utils.compile_gem([return_expr], ir, [point_index]) body = generate_coffee(impero_c, index_names={point_index: 'p'}) oriented = needs_cell_orientations(ir) if oriented: args.insert(0, cell_orientations_coffee_arg) # Build kernel args.insert(0, ast.Decl("double", ast.Symbol('A', rank=(len(to_pts),) + fs.shape))) kernel_code = builder.construct_kernel("expression_kernel", args, body) return op2.Kernel(kernel_code, kernel_code.name), oriented, coefficients
def compile_ufl_kernel(expression, to_pts, to_element, fs): import collections from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms import extract_arguments, extract_coefficients from gem import gem, impero_utils from tsfc import fem, ufl_utils from tsfc.coffee import generate as generate_coffee from tsfc.kernel_interface import (KernelBuilderBase, needs_cell_orientations, cell_orientations_coffee_arg) # Imitate the compute_form_data processing pipeline # # Unfortunately, we cannot call compute_form_data here, since # we only have an expression, not a form expression = apply_algebra_lowering(expression) expression = apply_derivatives(expression) expression = apply_function_pullbacks(expression) expression = apply_geometry_lowering(expression) expression = apply_derivatives(expression) expression = apply_geometry_lowering(expression) expression = apply_derivatives(expression) # Replace coordinates (if any) if expression.ufl_domain(): assert fs.mesh() == expression.ufl_domain() expression = ufl_utils.replace_coordinates(expression, fs.mesh().coordinates) if extract_arguments(expression): return ValueError("Cannot interpolate UFL expression with Arguments!") builder = KernelBuilderBase() args = [] coefficients = extract_coefficients(expression) for i, coefficient in enumerate(coefficients): args.append(builder.coefficient(coefficient, "w_%d" % i)) point_index = gem.Index(name='p') ir = fem.compile_ufl(expression, cell=fs.mesh().ufl_cell(), points=to_pts, point_index=point_index, coefficient_mapper=builder.coefficient_mapper) assert len(ir) == 1 # Deal with non-scalar expressions tensor_indices = () if fs.shape: tensor_indices = tuple(gem.Index() for s in fs.shape) ir = [gem.Indexed(ir[0], tensor_indices)] # Build kernel body return_var = gem.Variable('A', (len(to_pts),) + fs.shape) return_expr = gem.Indexed(return_var, (point_index,) + tensor_indices) impero_c = impero_utils.compile_gem([return_expr], ir, [point_index]) body = generate_coffee(impero_c, index_names={point_index: 'p'}) oriented = needs_cell_orientations(ir) if oriented: args.insert(0, cell_orientations_coffee_arg) # Build kernel args.insert(0, ast.Decl("double", ast.Symbol('A', rank=(len(to_pts),) + fs.shape))) kernel_code = builder.construct_kernel("expression_kernel", args, body) return op2.Kernel(kernel_code, kernel_code.name), oriented, coefficients
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