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
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
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