Esempio n. 1
0
    def product(*args, **kwargs):
        """Product of multiple :py:class:`MonomialSum`s"""
        rename_map = kwargs.pop('rename_map', None)
        if rename_map is None:
            rename_map = make_rename_map()
        if kwargs:
            raise ValueError("Unrecognised keyword argument: " + kwargs.pop())

        result = MonomialSum()
        for monomials in product(*args):
            renamer = make_renamer(rename_map)
            sum_indices = []
            atomics = []
            rest = one
            for s, a, r in monomials:
                s_, applier = renamer(s)
                sum_indices.extend(s_)
                atomics.extend(map(applier, a))
                rest = Product(applier(r), rest)
            result.add(sum_indices, atomics, rest)
        return result
Esempio n. 2
0
    def product(*args, **kwargs):
        """Product of multiple :py:class:`MonomialSum`s"""
        rename_map = kwargs.pop('rename_map', None)
        if rename_map is None:
            rename_map = make_rename_map()
        if kwargs:
            raise ValueError("Unrecognised keyword argument: " + kwargs.pop())

        result = MonomialSum()
        for monomials in product(*args):
            renamer = make_renamer(rename_map)
            sum_indices = []
            atomics = []
            rest = one
            for s, a, r in monomials:
                s_, applier = renamer(s)
                sum_indices.extend(s_)
                atomics.extend(map(applier, a))
                rest = Product(applier(r), rest)
            result.add(sum_indices, atomics, rest)
        return result
Esempio n. 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
Esempio n. 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