def generateCode(predefined, expressions, coefficients=None, tempVars=True): expressions = [applyRestrictions(expand_indices(apply_derivatives(apply_algebra_lowering(expr)))) for expr in expressions] generator = CodeGenerator(predefined, coefficients, tempVars) results = map_expr_dags(generator, expressions) l = list(generator.using) l.sort() return l + generator.code, results
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 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 gatherDerivatives(form, arguments=None): if arguments is None: arguments = form.arguments() form = apply_derivatives(apply_algebra_lowering(form)) gatherer = DerivativeGatherer(arguments) map_expr_dags(gatherer, [i.integrand() for i in form.integrals()]) return [ sorted(list(gatherer.derivatives[arg]), key=lambda d: len(d.ufl_shape)) for arg in arguments ]
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_apply_algebra_lowering_complex(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) v = TestFunction(element) u = TrialFunction(element) gv = grad(v) gu = grad(u) a = dot(gu, gv) b = inner(gv, gu) c = outer(gu, gv) lowered_a = apply_algebra_lowering(a) lowered_b = apply_algebra_lowering(b) lowered_c = apply_algebra_lowering(c) lowered_a_index = lowered_a.index() lowered_b_index = lowered_b.index() lowered_c_indices = lowered_c.indices() assert lowered_a == gu[lowered_a_index] * gv[lowered_a_index] assert lowered_b == gv[lowered_b_index] * conj(gu[lowered_b_index]) assert lowered_c == as_tensor(conj(gu[lowered_c_indices[0]]) * gv[lowered_c_indices[1]], (lowered_c_indices[0],) + (lowered_c_indices[1],))
def splitForm(form, arguments=None): if arguments is None: arguments = form.arguments() form = applyRestrictions(form) form = expand_indices(apply_derivatives(apply_algebra_lowering(form))) form = applyRestrictions(form) integrals = {} for integral in form.integrals(): right = splitMultiLinearExpr(integral.integrand(), arguments) left = integrals.get(integral.integral_type()) if left is not None: right = sumTensorMaps(left, right) integrals[integral.integral_type()] = right return integrals
def expand_derivatives(form, **kwargs): """Expand all derivatives of expr. In the returned expression g which is mathematically equivalent to expr, there are no VariableDerivative or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. """ # For a deprecation period (I see that dolfin-adjoint passes some # args here) if kwargs: warning("Deprecation: expand_derivatives no longer takes any keyword arguments") # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) # Apply differentiation form = apply_derivatives(form) return form
def expand_derivatives(form, **kwargs): """Expand all derivatives of expr. In the returned expression g which is mathematically equivalent to expr, there are no VariableDerivative or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. """ # For a deprecation period (I see that dolfin-adjoint passes some # args here) if kwargs: warning( "Deprecation: expand_derivatives no longer takes any keyword arguments" ) # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) # Apply differentiation form = apply_derivatives(form) return form
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
def expand_compounds(e): return apply_algebra_lowering(e)