def extract_terms(form: Form) -> SplitTimeForm: """Extract terms from a :class:`~ufl.Form`. This splits a form (a sum of integrals) into those integrals which do contain a :class:`~.TimeDerivative` and those that don't. :arg form: The form to split. :returns: a :class:`~.SplitTimeForm` tuple. :raises ValueError: if the form does not apply anything other than first-order time derivatives to a single coefficient. """ time_terms = [] rest_terms = [] for integral in form.integrals(): integrand = integral.integrand() rest = remove_if(integrand, lambda o: isinstance(o, TimeDerivative)) time = remove_if(integrand, partial(contains, summands(rest))) if not isinstance(time, Zero): time_terms.append(integral.reconstruct(integrand=time)) if not isinstance(rest, Zero): rest_terms.append(integral.reconstruct(integrand=rest)) time_terms = check_integrals(time_terms, expect_time_derivative=True) rest_terms = check_integrals(rest_terms, expect_time_derivative=False) return SplitTimeForm(time=Form(time_terms), remainder=Form(rest_terms))
def transform_integrands(form, transform, domain_type=None): """Apply transform(expression) to each integrand expression in form, or to form if it is an Expr.""" if isinstance(form, Form): newintegrals = [] for itg in form.integrals(): integrand = itg.integrand() if domain_type is None or domain_type == itg.domain_type(): integrand = transform(integrand) if not isinstance(integrand, Zero): newitg = itg.reconstruct(integrand) newintegrals.append(newitg) if not newintegrals: debug( "No integrals left after transformation, returning empty form." ) return Form(newintegrals) elif isinstance(form, Integral): integral = form integrand = transform(integral.integrand()) new_integral = integral.reconstruct(integrand) return new_integral elif isinstance(form, Expr): expr = form return transform(expr) else: error("Expecting Form or Expr.")
def map_integrands(function, form, only_integral_type=None): """Apply transform(expression) to each integrand expression in form, or to form if it is an Expr. """ if isinstance(form, Form): mapped_integrals = [ map_integrands(function, itg, only_integral_type) for itg in form.integrals() ] nonzero_integrals = [ itg for itg in mapped_integrals if not isinstance(itg.integrand(), Zero) ] return Form(nonzero_integrals) elif isinstance(form, Integral): itg = form if (only_integral_type is None) or (itg.integral_type() in only_integral_type): return itg.reconstruct(function(itg.integrand())) else: return itg elif isinstance(form, Expr): integrand = form return function(integrand) else: error("Expecting Form, Integral or Expr.")
def derivative(form, coefficient, argument=None, coefficient_derivatives=None): """UFL form operator: Compute the Gateaux derivative of *form* w.r.t. *coefficient* in direction of *argument*. If the argument is omitted, a new ``Argument`` is created in the same space as the coefficient, with argument number one higher than the highest one in the form. The resulting form has one additional ``Argument`` in the same finite element space as the coefficient. A tuple of ``Coefficient`` s may be provided in place of a single ``Coefficient``, in which case the new ``Argument`` argument is based on a ``MixedElement`` created from this tuple. An indexed ``Coefficient`` from a mixed space may be provided, in which case the argument should be in the corresponding subspace of the coefficient space. If provided, *coefficient_derivatives* should be a mapping from ``Coefficient`` instances to their derivatives w.r.t. *coefficient*. """ coefficients, arguments = _handle_derivative_arguments(form, coefficient, argument) if coefficient_derivatives is None: coefficient_derivatives = ExprMapping() else: cd = [] for k in sorted_expr(coefficient_derivatives.keys()): cd += [as_ufl(k), as_ufl(coefficient_derivatives[k])] coefficient_derivatives = ExprMapping(*cd) # Got a form? Apply derivatives to the integrands in turn. if isinstance(form, Form): integrals = [] for itg in form.integrals(): if not isinstance(coefficient, SpatialCoordinate): fd = CoefficientDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) else: fd = CoordinateDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) integrals.append(itg.reconstruct(fd)) return Form(integrals) elif isinstance(form, Expr): # What we got was in fact an integrand if not isinstance(coefficient, SpatialCoordinate): return CoefficientDerivative(form, coefficients, arguments, coefficient_derivatives) else: return CoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) error("Invalid argument type %s." % str(type(form)))
def strip_dt_form(F): if isinstance(F, Zero): # Avoid applying the time derivative stripper to zero forms return F stripper = Memoizer(strip_dt) # Strip dt from all the integrals in the form Fnew = Form([ i.reconstruct(integrand=stripper(i.integrand())) for i in F.integrals() ]) # Return the form stripped of its time derivatives return Fnew
def group_form_integrals(form, domains): """Group integrals by domain and type, performing canonical simplification. :arg form: the :class:`~.Form` to group the integrals of. :arg domains: an iterable of :class:`~.Domain`\s. :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] for domain in domains: for integral_type in ufl.measure.integral_types(): # Get integrals with this domain and type ddt_integrals = integrals_by_domain_and_type.get( (domain, integral_type)) if ddt_integrals is None: continue # Group integrals by subdomain id, after splitting e.g. # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = \ rearrange_integrals_by_single_subdomains(ddt_integrals) for subdomain_id, ss_integrals in sorted_by_key( single_subdomain_integrals): # Accumulate integrands of integrals that share the # same compiler data integrands_and_cds = \ accumulate_integrands_with_same_metadata(ss_integrals) for integrand, metadata in integrands_and_cds: integrals.append( Integral(integrand, integral_type, domain, subdomain_id, metadata, None)) return Form(integrals)
def reconstruct_form_from_integral_data(integral_data): integrals = [] for ida in integral_data: integrals.extend(ida.integrals) return Form(integrals)
def group_form_integrals(form, domains, do_append_everywhere_integrals=True): """Group integrals by domain and type, performing canonical simplification. :arg form: the :class:`~.Form` to group the integrals of. :arg domains: an iterable of :class:`~.Domain`s. :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] for domain in domains: for integral_type in ufl.measure.integral_types(): # Get integrals with this domain and type ddt_integrals = integrals_by_domain_and_type.get( (domain, integral_type)) if ddt_integrals is None: continue # Group integrals by subdomain id, after splitting e.g. # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = \ rearrange_integrals_by_single_subdomains(ddt_integrals, do_append_everywhere_integrals) for subdomain_id, ss_integrals in sorted_by_key( single_subdomain_integrals): # strip the coordinate derivatives from all integrals # this yields a list of the form [(coordinate derivative, integral), ...] stripped_integrals_and_coordderivs = strip_coordinate_derivatives( ss_integrals) # now group the integrals by the coordinate derivative def calc_hash(cd): return sum( sum(tuple_elem._ufl_compute_hash_() for tuple_elem in tuple_) for tuple_ in cd) coordderiv_integrals_dict = {} for integral, coordderiv in stripped_integrals_and_coordderivs: coordderivhash = calc_hash(coordderiv) if coordderivhash in coordderiv_integrals_dict: coordderiv_integrals_dict[coordderivhash][1].append( integral) else: coordderiv_integrals_dict[coordderivhash] = ( coordderiv, [integral]) # cd_integrals_dict is now a dict of the form # { hash: (CoordinateDerivative, [integral, integral, ...]), ... } # we can now put the integrals back together and then afterwards # apply the CoordinateDerivative again for cdhash, samecd_integrals in sorted_by_key( coordderiv_integrals_dict): # Accumulate integrands of integrals that share the # same compiler data integrands_and_cds = \ accumulate_integrands_with_same_metadata(samecd_integrals[1]) for integrand, metadata in integrands_and_cds: integral = Integral(integrand, integral_type, domain, subdomain_id, metadata, None) integral = attach_coordinate_derivatives( integral, samecd_integrals[0]) integrals.append(integral) return Form(integrals)
def assemble(self, form, arity): '''Assemble a biliner(2), linear(1) form''' reduced_integrals = self.select_integrals(form) #! Selector # Signal to xii.assemble if not reduced_integrals: return None components = [] for integral in form.integrals(): # Delegate to friend if integral not in reduced_integrals: components.append( xii.assembler.xii_assembly.assemble(Form([integral]))) continue reduced_mesh = integral.ufl_domain().ufl_cargo() integrand = integral.integrand() # Split arguments in those that need to be and those that are # already restricted. terminals = set(traverse_unique_terminals(integrand)) # FIXME: is it enough info (in general) to decide terminals_to_restrict = sorted( self.restriction_filter(terminals, reduced_mesh), key=lambda t: self.is_compatible(t, reduced_mesh)) # You said this is a trace ingral! assert terminals_to_restrict # Let's pick a guy for restriction terminal = terminals_to_restrict.pop() # We have some assumption on the candidate assert self.is_compatible(terminal, reduced_mesh) data = self.reduction_matrix_data(terminal) integrand = ufl2uflcopy(integrand) # With sane inputs we can get the reduced element and setup the # intermediate function space where the reduction of terminal # lives V = terminal.function_space() TV = self.reduced_space(V, reduced_mesh) #! Space construc # Setup the matrix to from space of the trace_terminal to the # intermediate space. FIXME: normal and trace_mesh #! mat construct df.info('\tGetting reduction op') rop_timer = df.Timer('rop') T = self.reduction_matrix(V, TV, reduced_mesh, data) df.info('\tDone (reduction op) %g' % rop_timer.stop()) # T if is_test_function(terminal): replacement = df.TestFunction(TV) # Passing the args to get the comparison a make substitution integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) if arity == 2: # Make attempt on the substituted form A = xii.assembler.xii_assembly.assemble(trace_form) components.append(block_transpose(T) * A) else: b = xii.assembler.xii_assembly.assemble(trace_form) Tb = df.Function(V).vector() # Alloc and apply T.transpmult(b, Tb) components.append(Tb) if is_trial_function(terminal): assert arity == 2 replacement = df.TrialFunction(TV) # Passing the args to get the comparison integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) A = xii.assembler.xii_assembly.assemble(trace_form) components.append(A * T) # Okay, then this guy might be a function if isinstance(terminal, df.Function): replacement = df.Function(TV) # Replacement is not just a placeholder T.mult(terminal.vector(), replacement.vector()) # Substitute integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) components.append( xii.assembler.xii_assembly.assemble(trace_form)) # The whole form is then the sum of integrals return reduce(operator.add, components)
def __rmul__(self, integrand): """Multiply a scalar expression with measure to construct a form with a single integral. This is to implement the notation form = integrand * self Integration properties are taken from this Measure object. """ # Avoid circular imports from ufl.integral import Integral from ufl.form import Form # Allow python literals: 1*dx and 1.0*dx if isinstance(integrand, (int, float)): integrand = as_ufl(integrand) # Let other types implement multiplication with Measure if # they want to (to support the dolfin-adjoint TimeMeasure) if not isinstance(integrand, Expr): return NotImplemented # Allow only scalar integrands if not is_true_ufl_scalar(integrand): error("Can only integrate scalar expressions. The integrand is a " "tensor expression with value shape %s and free indices with labels %s." % (integrand.ufl_shape, integrand.ufl_free_indices)) # If we have a tuple of domain ids, delegate composition to # Integral.__add__: subdomain_id = self.subdomain_id() if isinstance(subdomain_id, tuple): return sum(integrand*self.reconstruct(subdomain_id=d) for d in subdomain_id) # Check that we have an integer subdomain or a string # ("everywhere" or "otherwise", any more?) if not isinstance(subdomain_id, (str, numbers.Integral,)): error("Expecting integer or string domain id.") # If we don't have an integration domain, try to find one in # integrand domain = self.ufl_domain() if domain is None: domains = extract_domains(integrand) if len(domains) == 1: domain, = domains elif len(domains) == 0: error("This integral is missing an integration domain.") else: error("Multiple domains found, making the choice of integration domain ambiguous.") # Otherwise create and return a one-integral form integral = Integral(integrand=integrand, integral_type=self.integral_type(), domain=domain, subdomain_id=subdomain_id, metadata=self.metadata(), subdomain_data=self.subdomain_data()) return Form([integral])
def __rmul__(self, integrand): # Let other types implement multiplication with Measure # if they want to (to support the dolfin-adjoint TimeMeasure) if not isinstance(integrand, Expr): return NotImplemented # Allow only scalar integrands if not is_true_ufl_scalar(integrand): error("Trying to integrate expression of rank %d with free indices %r." \ % (integrand.rank(), integrand.free_indices())) # Is the measure in a state where multiplication is not allowed? if self._domain_description == Measure.DOMAIN_ID_UNDEFINED: error("Missing domain id. You need to select a subdomain, " +\ "e.g. M = f*dx(0) for subdomain 0.") #else: # TODO: Do it this way instead, and move all logic below into preprocess: # # Return a one-integral form: # from ufl.form import Form # return Form( [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data())] ) # # or if we move domain data into Form instead: # integrals = [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata())] # domain_data = { self.domain_type(): self.domain_data() } # return Form(integrals, domain_data) # TODO: How to represent all kinds of domain descriptions is still a bit unclear # Create form if we have a sufficient domain description elif ( # We have a complete measure with domain description isinstance(self._domain_description, DomainDescription) # Is the measure in a basic state 'foo*dx'? or self._domain_description == Measure.DOMAIN_ID_UNIQUE # Is the measure over everywhere? or self._domain_description == Measure.DOMAIN_ID_EVERYWHERE # Is the measure in a state not allowed prior to preprocessing? or self._domain_description == Measure.DOMAIN_ID_OTHERWISE): # Create and return a one-integral form from ufl.form import Form return Form([ Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data()) ]) # Did we get several ids? elif isinstance(self._domain_description, tuple): # FIXME: Leave this analysis to preprocessing return sum(integrand * self.reconstruct(domain_id=d) for d in self._domain_description) # Did we get a name? elif isinstance(self._domain_description, str): # FIXME: Leave this analysis to preprocessing # Get all domains and regions from integrand to analyse domains = extract_domains(integrand) # Get domain or region with this name from integrand, error if multiple found name = self._domain_description candidates = set() for TD in domains: if TD.name() == name: candidates.add(TD) ufl_assert( len(candidates) > 0, "Found no domain with name '%s' in integrand." % name) ufl_assert( len(candidates) == 1, "Multiple distinct domains with same name encountered in integrand." ) D, = candidates # Reconstruct measure with the found named domain or region measure = self.reconstruct(domain_id=D) return integrand * measure # Did we get a number? elif isinstance(self._domain_description, int): # FIXME: Leave this analysis to preprocessing # Get all top level domains from integrand to analyse domains = extract_top_domains(integrand) # Get domain from integrand, error if multiple found if len(domains) == 0: # This is the partially defined integral from dolfin expression mess cell = integrand.cell() D = None if cell is None else as_domain(cell) elif len(domains) == 1: D, = domains else: error( "Ambiguous reference to integer subdomain with multiple top domains in integrand." ) if D is None: # We have a number but not a domain? Leave it to preprocess... # This is the case with badly formed forms which can occur from dolfin # Create and return a one-integral form from ufl.form import Form return Form([ Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data()) ]) else: # Reconstruct measure with the found numbered subdomain measure = self.reconstruct( domain_id=D[self._domain_description]) return integrand * measure # Provide error to user else: error("Invalid domain id %s." % str(self._domain_description))