Beispiel #1
0
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.")
Beispiel #3
0
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.")
Beispiel #4
0
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)))
Beispiel #5
0
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
Beispiel #6
0
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)
Beispiel #7
0
def reconstruct_form_from_integral_data(integral_data):
    integrals = []
    for ida in integral_data:
        integrals.extend(ida.integrals)
    return Form(integrals)
Beispiel #8
0
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)
Beispiel #10
0
    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))