示例#1
0
    def dual_evaluation(self, fn):
        tQ, x = self.dual_basis
        expr = fn(x)
        # Apply targeted sum factorisation and delta elimination to
        # the expression
        sum_indices, factors = delta_elimination(*traverse_product(expr))
        expr = sum_factorise(sum_indices, factors)
        # NOTE: any shape indices in the expression are because the
        # expression is tensor valued.
        assert expr.shape == self.value_shape

        scalar_i = self.base_element.get_indices()
        scalar_vi = self.base_element.get_value_indices()
        tensor_i = tuple(gem.Index(extent=d) for d in self._shape)
        tensor_vi = tuple(gem.Index(extent=d) for d in self._shape)

        if self._transpose:
            index_ordering = tensor_i + scalar_i + tensor_vi + scalar_vi
        else:
            index_ordering = scalar_i + tensor_i + tensor_vi + scalar_vi

        tQi = tQ[index_ordering]
        expri = expr[tensor_i + scalar_vi]
        evaluation = gem.IndexSum(tQi * expri, x.indices + scalar_vi + tensor_i)
        # This doesn't work perfectly, the resulting code doesn't have
        # a minimal memory footprint, although the operation count
        # does appear to be minimal.
        evaluation = gem.optimise.contraction(evaluation)
        return evaluation, scalar_i + tensor_vi
示例#2
0
    def dual_evaluation(self, fn):
        '''Get a GEM expression for performing the dual basis evaluation at
        the nodes of the reference element. Currently only works for flat
        elements: tensor elements are implemented in
        :class:`TensorFiniteElement`.

        :param fn: Callable representing the function to dual evaluate.
                   Callable should take in an :class:`AbstractPointSet` and
                   return a GEM expression for evaluation of the function at
                   those points.
        :returns: A tuple ``(dual_evaluation_gem_expression, basis_indices)``
                  where the given ``basis_indices`` are those needed to form a
                  return expression for the code which is compiled from
                  ``dual_evaluation_gem_expression`` (alongside any argument
                  multiindices already encoded within ``fn``)
        '''
        Q, x = self.dual_basis

        expr = fn(x)
        # Apply targeted sum factorisation and delta elimination to
        # the expression
        sum_indices, factors = delta_elimination(*traverse_product(expr))
        expr = sum_factorise(sum_indices, factors)
        # NOTE: any shape indices in the expression are because the
        # expression is tensor valued.
        assert expr.shape == Q.shape[len(Q.shape) - len(expr.shape):]
        shape_indices = gem.indices(len(expr.shape))
        basis_indices = gem.indices(len(Q.shape) - len(expr.shape))
        Qi = Q[basis_indices + shape_indices]
        expri = expr[shape_indices]
        evaluation = gem.IndexSum(Qi * expri, x.indices + shape_indices)
        # Now we want to factorise over the new contraction with x,
        # ignoring any shape indices to avoid hitting the sum-
        # factorisation index limit (this is a bit of a hack).
        # Really need to do a more targeted job here.
        evaluation = gem.optimise.contraction(evaluation, shape_indices)
        return evaluation, basis_indices
示例#3
0
def _collect_monomials(expression, self):
    """Refactorises an expression into a sum-of-products form, using
    distributivity rules (i.e. a*(b + c) -> a*b + a*c).  Expansion
    proceeds until all "compound" expressions are broken up.

    :arg expression: a GEM expression to refactorise
    :arg self: function for recursive calls

    :returns: :py:class:`MonomialSum`

    :raises FactorisationError: Failed to break up some "compound"
                                expressions with expansion.
    """

    # Phase 1: Collect and categorise product terms
    def stop_at(expr):
        # Break up compounds only
        return self.classifier(expr) != COMPOUND

    common_indices, terms = traverse_product(expression, stop_at=stop_at)
    common_indices = tuple(common_indices)

    common_atomics = []
    common_others = []
    compounds = []
    for term in terms:
        label = self.classifier(term)
        if label == ATOMIC:
            common_atomics.append(term)
        elif label == COMPOUND:
            compounds.append(term)
        elif label == OTHER:
            common_others.append(term)
        else:
            raise ValueError("Classifier returned illegal value.")
    common_atomics = tuple(common_atomics)

    # Phase 2: Attempt to break up compound terms into summands
    sums = []
    for expr in compounds:
        summands = traverse_sum(expr, stop_at=stop_at)
        if len(summands) <= 1 and not isinstance(expr,
                                                 (Conditional, MathFunction)):
            # Compound term is not an addition, avoid infinite
            # recursion and fail gracefully raising an exception.
            raise FactorisationError(expr)
        # Recurse into each summand, concatenate their results
        sums.append(MonomialSum.sum(*map(self, summands)))

    # Phase 3: Expansion
    #
    # Each element of ``sums`` is a MonomialSum.  Expansion produces a
    # series (representing a sum) of products of monomials.
    result = MonomialSum()
    for s, a, r in MonomialSum.product(*sums, rename_map=self.rename_map):
        renamer = make_renamer(self.rename_map)
        renamer(common_indices)  # update current_set
        s_, applier = renamer(s)

        all_indices = common_indices + s_
        atomics = common_atomics + tuple(map(applier, a))

        # All free indices that appear in atomic terms
        atomic_indices = set().union(
            *[atomic.free_indices for atomic in atomics])

        # Sum indices that appear in atomic terms
        # (will go to the result :py:class:`Monomial`)
        sum_indices = tuple(index for index in all_indices
                            if index in atomic_indices)

        # Sum indices that do not appear in atomic terms
        # (can factorise them over atomic terms immediately)
        rest_indices = tuple(index for index in all_indices
                             if index not in atomic_indices)

        # Not really sum factorisation, but rather just an optimised
        # way of building a product.
        rest = sum_factorise(rest_indices, common_others + [applier(r)])

        result.add(sum_indices, atomics, rest)
    return result
示例#4
0
def _collect_monomials(expression, self):
    """Refactorises an expression into a sum-of-products form, using
    distributivity rules (i.e. a*(b + c) -> a*b + a*c).  Expansion
    proceeds until all "compound" expressions are broken up.

    :arg expression: a GEM expression to refactorise
    :arg self: function for recursive calls

    :returns: :py:class:`MonomialSum`

    :raises FactorisationError: Failed to break up some "compound"
                                expressions with expansion.
    """
    # Phase 1: Collect and categorise product terms
    def stop_at(expr):
        # Break up compounds only
        return self.classifier(expr) != COMPOUND
    common_indices, terms = traverse_product(expression, stop_at=stop_at)
    common_indices = tuple(common_indices)

    common_atomics = []
    common_others = []
    compounds = []
    for term in terms:
        label = self.classifier(term)
        if label == ATOMIC:
            common_atomics.append(term)
        elif label == COMPOUND:
            compounds.append(term)
        elif label == OTHER:
            common_others.append(term)
        else:
            raise ValueError("Classifier returned illegal value.")
    common_atomics = tuple(common_atomics)

    # Phase 2: Attempt to break up compound terms into summands
    sums = []
    for expr in compounds:
        summands = traverse_sum(expr, stop_at=stop_at)
        if len(summands) <= 1:
            # Compound term is not an addition, avoid infinite
            # recursion and fail gracefully raising an exception.
            raise FactorisationError(expr)
        # Recurse into each summand, concatenate their results
        sums.append(MonomialSum.sum(*map(self, summands)))

    # Phase 3: Expansion
    #
    # Each element of ``sums`` is a MonomialSum.  Expansion produces a
    # series (representing a sum) of products of monomials.
    result = MonomialSum()
    for s, a, r in MonomialSum.product(*sums, rename_map=self.rename_map):
        renamer = make_renamer(self.rename_map)
        renamer(common_indices)  # update current_set
        s_, applier = renamer(s)

        all_indices = common_indices + s_
        atomics = common_atomics + tuple(map(applier, a))

        # All free indices that appear in atomic terms
        atomic_indices = set().union(*[atomic.free_indices
                                       for atomic in atomics])

        # Sum indices that appear in atomic terms
        # (will go to the result :py:class:`Monomial`)
        sum_indices = tuple(index for index in all_indices
                            if index in atomic_indices)

        # Sum indices that do not appear in atomic terms
        # (can factorise them over atomic terms immediately)
        rest_indices = tuple(index for index in all_indices
                             if index not in atomic_indices)

        # Not really sum factorisation, but rather just an optimised
        # way of building a product.
        rest = sum_factorise(rest_indices, common_others + [applier(r)])

        result.add(sum_indices, atomics, rest)
    return result