def apply_geometry_lowering(form, preserve_types=()): """Change GeometricQuantity objects in expression to the lowest level GeometricQuantity objects. Assumes the expression is preprocessed or at least that derivatives have been expanded. @param form: An Expr or Form. """ if isinstance(form, Form): newintegrals = [ apply_geometry_lowering(integral, preserve_types) for integral in form.integrals() ] return Form(newintegrals) elif isinstance(form, Integral): integral = form if integral.integral_type() in (custom_integral_types + point_integral_types): automatic_preserve_types = [SpatialCoordinate, Jacobian] else: automatic_preserve_types = [CellCoordinate] preserve_types = set(preserve_types) | set(automatic_preserve_types) mf = GeometryLoweringApplier(preserve_types) newintegrand = map_expr_dag(mf, integral.integrand()) return integral.reconstruct(integrand=newintegrand) elif isinstance(form, Expr): expr = form mf = GeometryLoweringApplier(preserve_types) return map_expr_dag(mf, expr) else: error("Invalid type %s" % (form.__class__.__name__, ))
def apply_geometry_lowering(form, preserve_types=()): """Change GeometricQuantity objects in expression to the lowest level GeometricQuantity objects. Assumes the expression is preprocessed or at least that derivatives have been expanded. @param form: An Expr or Form. """ if isinstance(form, Form): newintegrals = [apply_geometry_lowering(integral, preserve_types) for integral in form.integrals()] return Form(newintegrals) elif isinstance(form, Integral): integral = form if integral.integral_type() in (custom_integral_types + point_integral_types): automatic_preserve_types = [SpatialCoordinate, Jacobian] else: automatic_preserve_types = [CellCoordinate] preserve_types = set(preserve_types) | set(automatic_preserve_types) mf = GeometryLoweringApplier(preserve_types) newintegrand = map_expr_dag(mf, integral.integrand()) return integral.reconstruct(integrand=newintegrand) elif isinstance(form, Expr): expr = form mf = GeometryLoweringApplier(preserve_types) return map_expr_dag(mf, expr) else: error("Invalid type %s" % (form.__class__.__name__,))
def compile_ufl(expression, interior_facet=False, **kwargs): context = Context(**kwargs) # Abs-simplification expression = simplify_abs(expression) # Collect modified terminals modified_terminals = [] map_expr_dag(CollectModifiedTerminals(modified_terminals), expression) # Collect maximal derivatives that needs tabulation max_derivs = collections.defaultdict(int) for mt in map(analyse_modified_terminal, modified_terminals): if isinstance(mt.terminal, FormArgument): ufl_element = mt.terminal.ufl_element() max_derivs[ufl_element] = max(mt.local_derivatives, max_derivs[ufl_element]) # Collect tabulations for all components and derivatives tabulation_manager = TabulationManager(context.points, context.integration_dim, context.entity_ids, context.epsilon) for ufl_element, max_deriv in max_derivs.items(): if ufl_element.family() != 'Real': tabulation_manager.tabulate(ufl_element, max_deriv) if interior_facet: expressions = [] for rs in itertools.product(("+", "-"), repeat=len(context.argument_indices)): expressions.append(map_expr_dag(PickRestriction(*rs), expression)) else: expressions = [expression] # Translate UFL to GEM, lowering finite element specific nodes translator = Translator(tabulation_manager, context) return map_expr_dags(translator, expressions)
def coarsen_form(form): """Return a coarse mesh version of a form :arg form: The :class:`~ufl.classes.Form` to coarsen. This maps over the form and replaces coefficients and arguments with their coarse mesh equivalents.""" if form is None: return None assert isinstance(form, ufl.Form), \ "Don't know how to coarsen %r" % type(form) mapper = CoarsenIntegrand() forms = [] # Ugh, visitors can't deal with measures (they're not actual # Exprs) so we need to map the transformer over the integrand and # reconstruct the integral by building the measure by hand. for it in form.integrals(): integrand = map_expr_dag(mapper, it.integrand()) mesh = it.ufl_domain() hierarchy, level = utils.get_level(mesh) new_mesh = hierarchy[level - 1] measure = ufl.Measure(it.integral_type(), domain=new_mesh, subdomain_id=it.subdomain_id(), subdomain_data=it.subdomain_data(), metadata=it.metadata()) forms.append(integrand * measure) return reduce(add, forms)
def physical_points(self, point_set, entity=None): """Converts point_set from reference to physical space""" expr = SpatialCoordinate(self.mt.terminal.ufl_domain()) point_shape, = point_set.expression.shape if entity is not None: e, _ = entity assert point_shape == e else: assert point_shape == expr.ufl_domain().topological_dimension() if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) config = {"point_set": point_set} config.update(self.config) if entity is not None: config.update({ name: getattr(self.interface, name) for name in ["integration_dim", "entity_ids"] }) context = PointSetContext(**config) expr = self.preprocess(expr, context) mapped = map_expr_dag(context.translator, expr) indices = tuple(gem.Index() for _ in mapped.shape) return gem.ComponentTensor(gem.Indexed(mapped, indices), point_set.indices + indices)
def coarsen_form(form): """Return a coarse mesh version of a form :arg form: The :class:`~ufl.classes.Form` to coarsen. This maps over the form and replaces coefficients and arguments with their coarse mesh equivalents.""" if form is None: return None assert isinstance(form, ufl.Form), \ "Don't know how to coarsen %r" % type(form) mapper = CoarsenIntegrand() forms = [] # Ugh, visitors can't deal with measures (they're not actual # Exprs) so we need to map the transformer over the integrand and # reconstruct the integral by building the measure by hand. for it in form.integrals(): integrand = map_expr_dag(mapper, it.integrand()) mesh = it.ufl_domain() hierarchy, level = utils.get_level(mesh) new_mesh = hierarchy[level-1] measure = ufl.Measure(it.integral_type(), domain=new_mesh, subdomain_id=it.subdomain_id(), subdomain_data=it.subdomain_data(), metadata=it.metadata()) forms.append(integrand * measure) return reduce(add, forms)
def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): o_ = o.ufl_operands key = (CoordinateDerivative, o_[0]) return CoordinateDerivative(map_expr_dag(self, o_[0], vcache=self.vcaches[key], rcache=self.rcaches[key]), o_[1], o_[2], o_[3])
def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): dummy, w, v, cd = o.ufl_operands rules = GateauxDerivativeRuleset(w, v, cd) key = (GateauxDerivativeRuleset, w, v, cd) return map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key])
def variable_derivative(self, o, f, dummy_v): op = o.ufl_operands[1] rules = VariableRuleset(op) key = (VariableRuleset, op) return map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key])
def coarsen_form(form, coefficient_mapping=None): """Return a coarse mesh version of a form :arg form: The :class:`~ufl.classes.Form` to coarsen. :kwarg mapping: an optional map from coefficients to their coarsened equivalents. This maps over the form and replaces coefficients and arguments with their coarse mesh equivalents.""" if form is None: return None mapper = CoarsenIntegrand(coefficient_mapping) integrals = [] for it in form.integrals(): integrand = map_expr_dag(mapper, it.integrand()) mesh = it.ufl_domain() hierarchy, level = utils.get_level(mesh) new_mesh = hierarchy[level-1] if isinstance(integrand, ufl.classes.Zero): continue if it.subdomain_data() is not None: raise CoarseningError("Don't know how to coarsen subdomain data") new_itg = it.reconstruct(integrand=integrand, domain=new_mesh) integrals.append(new_itg) return ufl.Form(integrals)
def split_coefficients(expression, split): """Split mixed coefficients, so mixed elements need not be implemented.""" if split is None: # Skip this step for DOLFIN return expression splitter = CoefficientSplitter(split) return map_expr_dag(splitter, expression)
def extract_tested_expressions(expr): """Extract scalar expression factors for each test function component. This is for internal usage and has several undocumented limitations. """ func = ScalarFactorizer() e = map_expr_dag(func, expr, compress=False) return e, func._arg
def restricted(self, o): "When hitting a restricted quantity, visit child with a separate restriction algorithm." # Assure that we have only two levels here, inside or outside # the Restricted node if self.current_restriction is not None: error("Cannot restrict an expression twice.") # Configure a propagator for this side and apply to subtree # FIXME: Reuse cache between these calls! return map_expr_dag(self._rp[o.side()], o.ufl_operands[0])
def check_integrand_arity(expr, arguments): arguments = tuple( sorted(set(arguments), key=lambda x: (x.number(), x.part()))) rules = ArityChecker(arguments) args = map_expr_dag(rules, expr, compress=False) if args != arguments: raise ArityMismatch( "Integrand arguments {0} differ from form arguments {1}.".format( args, arguments))
def change_to_reference_grad(e): """Change Grad objects in expression to products of JacobianInverse and ReferenceGrad. Assumes the expression is preprocessed or at least that derivatives have been expanded. @param e: An Expr or Form. """ mf = OLDChangeToReferenceGrad() # mf = NEWChangeToReferenceGrad() return map_expr_dag(mf, e)
def coordinate_derivative(self, o, f, w, v, cd): from ufl.algorithms import extract_unique_elements spaces = set(c.family() for c in extract_unique_elements(o)) unsupported_spaces = {"Argyris", "Bell", "Hermite", "Morley"} if spaces & unsupported_spaces: error("CoordinateDerivative is not supported for elements of type %s. " "This is because their pullback is not implemented in UFL." % unsupported_spaces) _, w, v, cd = o.ufl_operands rules = CoordinateDerivativeRuleset(w, v, cd) key = (CoordinateDerivativeRuleset, w, v, cd) return map_expr_dag(rules, f, vcache=self.vcache[key], rcache=self.rcache[key])
def detJ_at(self, point): expr = JacobianDeterminant(self.mt.terminal.ufl_domain()) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) config = {"point_set": PointSingleton(point)} config.update(self.config) context = PointSetContext(**config) expr = self.preprocess(expr, context) return map_expr_dag(context.translator, expr)
def coordinate_derivative(self, o): from ufl.algorithms import extract_unique_elements spaces = set(c.family() for c in extract_unique_elements(o)) unsupported_spaces = {"Argyris", "Bell", "Hermite", "Morley"} if spaces & unsupported_spaces: error("CoordinateDerivative is not supported for elements of type %s. " "This is because their pullback is not implemented in UFL." % unsupported_spaces) f, w, v, cd = o.ufl_operands f = self(f) # transform f rules = CoordinateDerivativeRuleset(w, v, cd) return map_expr_dag(rules, f)
def to_reference_coordinates(ufl_coordinate_element): # Set up UFL form cell = ufl_coordinate_element.cell() domain = ufl.Mesh(ufl_coordinate_element) K = ufl.JacobianInverse(domain) x = ufl.SpatialCoordinate(domain) x0_element = ufl.VectorElement("Real", cell, 0) x0 = ufl.Coefficient(ufl.FunctionSpace(domain, x0_element)) expr = ufl.dot(K, x - x0) # Translation to GEM C = ufl_utils.coordinate_coefficient(domain) expr = ufl_utils.preprocess_expression(expr) expr = ufl_utils.replace_coordinates(expr, C) expr = ufl_utils.simplify_abs(expr) builder = firedrake_interface.KernelBuilderBase() builder._coefficient(C, "C") builder._coefficient(x0, "x0") dim = cell.topological_dimension() point = gem.Variable('X', (dim, )) context = tsfc.fem.GemPointContext( interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, ) translator = tsfc.fem.Translator(context) ir = map_expr_dag(translator, expr) # Unroll result ir = [gem.Indexed(ir, alpha) for alpha in numpy.ndindex(ir.shape)] # Unroll IndexSums max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent ir = gem.optimise.unroll_indexsum(ir, predicate=predicate) # Translate to COFFEE ir = impero_utils.preprocess_gem(ir) return_variable = gem.Variable('dX', (dim, )) assignments = [(gem.Indexed(return_variable, (i, )), e) for i, e in enumerate(ir)] impero_c = impero_utils.compile_gem(assignments, ()) body = tsfc.coffee.generate(impero_c, {}, parameters["precision"]) body.open_scope = False return body
def to_reference_coordinates(ufl_coordinate_element, parameters): # Set up UFL form cell = ufl_coordinate_element.cell() domain = ufl.Mesh(ufl_coordinate_element) K = ufl.JacobianInverse(domain) x = ufl.SpatialCoordinate(domain) x0_element = ufl.VectorElement("Real", cell, 0) x0 = ufl.Coefficient(ufl.FunctionSpace(domain, x0_element)) expr = ufl.dot(K, x - x0) # Translation to GEM C = ufl.Coefficient(ufl.FunctionSpace(domain, ufl_coordinate_element)) expr = ufl_utils.preprocess_expression(expr) expr = ufl_utils.simplify_abs(expr) builder = firedrake_interface.KernelBuilderBase() builder.domain_coordinate[domain] = C builder._coefficient(C, "C") builder._coefficient(x0, "x0") dim = cell.topological_dimension() point = gem.Variable('X', (dim,)) context = tsfc.fem.GemPointContext( interface=builder, ufl_cell=cell, precision=parameters["precision"], point_indices=(), point_expr=point, ) translator = tsfc.fem.Translator(context) ir = map_expr_dag(translator, expr) # Unroll result ir = [gem.Indexed(ir, alpha) for alpha in numpy.ndindex(ir.shape)] # Unroll IndexSums max_extent = parameters["unroll_indexsum"] if max_extent: def predicate(index): return index.extent <= max_extent ir = gem.optimise.unroll_indexsum(ir, predicate=predicate) # Translate to COFFEE ir = impero_utils.preprocess_gem(ir) return_variable = gem.Variable('dX', (dim,)) assignments = [(gem.Indexed(return_variable, (i,)), e) for i, e in enumerate(ir)] impero_c = impero_utils.compile_gem(assignments, ()) body = tsfc.coffee.generate(impero_c, {}, parameters["precision"]) body.open_scope = False return body
def split_coefficients(expression, split): """Split mixed coefficients, so mixed elements need not be implemented. :arg split: A :py:class:`dict` mapping each mixed coefficient to a sequence of subcoefficients. If None, calling this function is a no-op. """ if split is None: return expression splitter = CoefficientSplitter(split) return map_expr_dag(splitter, expression)
def physical_edge_lengths(self): expr = ufl.classes.CellEdgeVectors(self.mt.terminal.ufl_domain()) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(3)]) expr = preprocess_expression(expr) config = {"point_set": PointSingleton([1/3, 1/3])} config.update(self.config) context = PointSetContext(**config) return map_expr_dag(context.translator, expr)
def jacobian_at(self, point): ps = PointSingleton(point) expr = Jacobian(self.mt.terminal.ufl_domain()) assert ps.expression.shape == ( expr.ufl_domain().topological_dimension(), ) if self.mt.restriction == '+': expr = PositiveRestricted(expr) elif self.mt.restriction == '-': expr = NegativeRestricted(expr) config = {"point_set": PointSingleton(point)} config.update(self.config) context = PointSetContext(**config) expr = self.preprocess(expr, context) return map_expr_dag(context.translator, expr)
def compile_ufl(expression, interior_facet=False, point_sum=False, **kwargs): context = PointSetContext(**kwargs) # Abs-simplification expression = simplify_abs(expression) if interior_facet: expressions = [] for rs in itertools.product(("+", "-"), repeat=len(context.argument_multiindices)): expressions.append(map_expr_dag(PickRestriction(*rs), expression)) else: expressions = [expression] # Translate UFL to GEM, lowering finite element specific nodes result = map_expr_dags(context.translator, expressions) if point_sum: result = [gem.index_sum(expr, context.point_indices) for expr in result] return result
def check_integrand_arity(expr, arguments, complex_mode=False): arguments = tuple(sorted(set(arguments), key=lambda x: (x.number(), x.part()))) rules = ArityChecker(arguments) arg_tuples = map_expr_dag(rules, expr, compress=False) args = tuple(a[0] for a in arg_tuples) if args != arguments: raise ArityMismatch("Integrand arguments {0} differ from form arguments {1}.".format(args, arguments)) if complex_mode: # Check that the test function is conjugated and that any # trial function is not conjugated. Further arguments are # treated as trial funtions (i.e. no conjugation) but this # might not be correct. for arg, conj in arg_tuples: if arg.number() == 0 and not conj: raise ArityMismatch("Failure to conjugate test function in complex Form") elif arg.number() > 0 and conj: raise ArityMismatch("Argument {0} is spuriously conjugated in complex Form".format(arg))
def coarsen_bc(bc): new_V = coarsen_thing(bc.function_space()) val = bc._original_val zeroed = bc._currently_zeroed subdomain = bc.sub_domain method = bc.method new_val = val if isinstance(val, firedrake.Expression): new_val = val if isinstance(val, (firedrake.Constant, firedrake.Function)): mapper = CoarsenIntegrand() new_val = map_expr_dag(mapper, val) new_bc = firedrake.DirichletBC(new_V, new_val, subdomain, method=method) if zeroed: new_bc.homogenize() return new_bc
def check_integrand_arity(expr, arguments, complex_mode=False): arguments = tuple( sorted(set(arguments), key=lambda x: (x.number(), x.part()))) rules = ArityChecker(arguments) arg_tuples = map_expr_dag(rules, expr, compress=False) args = tuple(a[0] for a in arg_tuples) if args != arguments: raise ArityMismatch( "Integrand arguments {0} differ from form arguments {1}.".format( args, arguments)) if complex_mode: # Check that the test function is conjugated and that any # trial function is not conjugated. Further arguments are # treated as trial funtions (i.e. no conjugation) but this # might not be correct. for arg, conj in arg_tuples: if arg.number() == 0 and not conj: raise ArityMismatch( "Failure to conjugate test function in complex Form") elif arg.number() > 0 and conj: raise ArityMismatch( "Argument {0} is spuriously conjugated in complex Form". format(arg))
def strip_terminal_data(o): """Return a new form where all terminals have been replaced by UFL-only equivalents. :arg o: The object to be stripped. This must either be a :class:`~.Form` or :class:`~.Integral`. :returns: A 2-tuple containing an equivalent UFL-only object and a mapping allowing the original form to be reconstructed using :func:`replace_terminal_data`. This function is useful for forms containing augmented UFL objects that hold references to large data structures. These objects are be extracted into the mapping allowing the form to be cached without leaking memory. """ # We need to keep track of two maps because integrals store references to the # domain and ``replace`` expects only a mapping containing ``Expr`` objects. if isinstance(o, Form): integrals = [] expr_map = {} domain_map = {} for integral in o.integrals(): itg, (emap, dmap) = strip_terminal_data(integral) integrals.append(itg) expr_map.update(emap) domain_map.update(dmap) return Form(integrals), (expr_map, domain_map) elif isinstance(o, Integral): handler = TerminalStripper() integrand = map_expr_dag(handler, o.integrand()) domain = strip_domain(o.ufl_domain()) # invert the mapping so it can be passed straight into replace_terminal_data expr_map = {v: k for k, v in handler.mapping.items()} domain_map = {domain: o.ufl_domain()} return o.reconstruct(integrand, domain=domain), (expr_map, domain_map) else: raise ValueError("Only Form or Integral inputs expected")
def coarse_expr(expr, self, coefficient_mapping=None): if expr is None: return None mapper = CoarsenIntegrand(self, coefficient_mapping) return map_expr_dag(mapper, expr)
# Adding test function and other expression TestFunction(U) + 1.0, # Multiplying test functions: TestFunction(U) * TestFunction(U), TestFunction(U) * TestFunction(V)[0], # Nonlinear operator applied to test function sin(TestFunction(U)), sin(TestFunction(V)[1]), ] from ufl.classes import Sum, Operator, Expr, Argument for expr in expressions: func = ScalarFactorizer() e = map_expr_dag(func, expr, compress=False) print if isinstance(e, dict): print str(expr), " = ", ", ".join( "%s[%d]: %s" % (func._arg, k, e[k]) for k in e) else: assert expr == e print str(expr), " = ", str(e) for expr in errors: func = ScalarFactorizer() try: e = map_expr_dag(func, expr, compress=False) ok = False except: ok = True
def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): o_ = o.ufl_operands return CoordinateDerivative(map_expr_dag(self, o_[0]), o_[1], o_[2], o_[3])
def variable_derivative(self, o, f, dummy_v): rules = VariableRuleset(o.ufl_operands[1]) return map_expr_dag(rules, f)
def grad(self, o, f): rules = GradRuleset(o.ufl_shape[-1]) return map_expr_dag(rules, f)
def coordinate_derivative(self, o): o = o.ufl_operands return CoordinateDerivative(map_expr_dag(self, o[0]), o[1], o[2], o[3])
def balance_modifiers(expr): mf = BalanceModifiers() return map_expr_dag(mf, expr)
def relabel_indices(expr): relabeller._reset() return map_expr_dag(relabeller, expr, compress=True)
def replace_coordinates(integrand, coordinate_coefficient): """Replace SpatialCoordinate nodes with Coefficients.""" return map_expr_dag(SpatialCoordinateReplacer(coordinate_coefficient), integrand)
def reference_grad(self, o, f): rules = ReferenceGradRuleset(o.ufl_shape[-1]) # FIXME: Look over this and test better. return map_expr_dag(rules, f)
def reference_grad(self, o, f): rules = ReferenceGradRuleset( o.ufl_shape[-1]) # FIXME: Look over this and test better. return map_expr_dag(rules, f)
def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): dummy, w, v, cd = o.ufl_operands rules = GateauxDerivativeRuleset(w, v, cd) return map_expr_dag(rules, f)
def expression2latex(expression, argument_names=None, coefficient_names=None): rules = Expression2LatexHandler(argument_names, coefficient_names) return map_expr_dag(rules, expression)
def expression2unicode(expression, argument_names=None, coefficient_names=None): rules = Expression2UnicodeHandler(argument_names, coefficient_names) return map_expr_dag(rules, expression)
def coordinate_derivative(self, o): f, w, v, cd = o.ufl_operands f = self(f) # transform f rules = CoordinateDerivativeRuleset(w, v, cd) return map_expr_dag(rules, f)
def extract_tested_expressions(expr): func = ScalarFactorizer() e = map_expr_dag(func, expr, compress=False) return e, func._arg
def map_integrand_dags(function, form, only_integral_type=None, compress=True): return map_integrands(lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type)