# Path: drudge/canon.py
# def canon_factors(sums, factors, symms):
#     """Canonicalize the factors.
# 
#     The factors should be a iterable of factor/colour pairs, where the factor
#     can be anything with the ``base`` and ``indices`` methods implemented, or
#     ``func`` and ``args`` implemented.  It is designed primarily to work with
#     SymPy Indexed quantities and vectors, or general SymPy function expressions.
#     The colour should be totally ordered, and they only need to order within
#     factors correctly.
# 
#     The new canonicalized list of summations and canonicalized factors are
#     going to be returned.  Also returned is a coefficient that need to be
#     multiplied to the amplitude, which is from anti-commutative quantities.
# 
#     The symmetries should be given as a mapping from the *base* of the factors
#     to the actual symmetries.  A given valence can also be given by a tuple with
#     the actual base.
# 
#     """
# 
#     from .term import Vec
# 
#     # They need to be looped over multiple times.
#     sums = list(sums)
#     factors = list(factors)
# 
#     # TODO: make handling of empty eldags more elegant.
#     if len(factors) == 0 and len(sums) == 0:
#         return sums, factors, 1
# 
#     eldag, factor_idxes = _build_eldag(sums, factors, symms)
#     node_order, perms = eldag.canon()
# 
#     # Sums are guaranteed to be in the initial segment of the nodes, but they
#     # might not be at the beginning any more after the canonicalization.
#     sums_res = [sums[i] for i in node_order if
#                 eldag.colours[i][0] == _SUM]
# 
#     coeff = 1
#     factors_res = []
# 
#     for i, v in enumerate(factors):
# 
#         factor = v[0]
#         if hasattr(factor, 'indices'):
#             indices = factor.indices
#             is_indexed = True
#         else:
#             indices = factor.args
#             is_indexed = False
#         valency = len(indices)
#         perm = perms[factor_idxes[i]]
#         if_vector = isinstance(factor, Vec)
# 
#         if valency < 2 or perm is None:
#             factor_res = factor
#         else:
#             new_indices = tuple(indices[perm[i]] for i in range(valency))
#             if is_indexed:
#                 factor_res = factor.base[new_indices]
#             else:
#                 factor_res = factor.func(new_indices)
# 
#             acc = perm.acc
#             if acc & NEG:
#                 if if_vector:
#                     coeff *= -1
#                 else:
#                     factor_res = -factor_res
#             if acc & CONJ:
#                 # TODO: Allow vectors to have their own dagger form, maybe.
#                 if if_vector:
#                     raise ValueError(
#                         'Vector', factor, 'cannot have conjugation symmetry'
#                     )
#                 factor_res = conjugate(factor_res)
# 
#         factors_res.append(factor_res)
#         continue
# 
#     return sums_res, factors_res, coeff
# 
# Path: drudge/utils.py
# def ensure_symb(obj, role=''):
#     """Sympify the given object into a symbol."""
#     return ensure_sympify(obj, role, Symbol)
# 
# def ensure_expr(obj, role=''):
#     """Sympify the given object into an expression."""
#     return ensure_sympify(obj, role, Expr)
# 
# def sympy_key(expr):
#     """Get the key for ordering SymPy expressions.
# 
#     This function assumes that the given expression is already sympified.
#     """
# 
#     return count_ops(expr), default_sort_key(expr)
# 
# def is_higher(obj, priority):
#     """Test if the object has higher operation priority.
# 
#     When the given object does not have defined priority, it is considered
#     lower.
#     """
# 
#     return getattr(obj, '_op_priority', priority - 1) > priority
# 
# class NonsympifiableFunc(CantSympify):
#     """Utility for wrapping callable to be used for SymPy.
# 
#     Inside SymPy functions like replace, things will first be attempted to be
#     sympified, which can be very expensive.  By wrapping callable inside this
#     class, sympification attempts will be aborted very early on.
# 
#     """
# 
#     __slots__ = ['_func']
# 
#     def __init__(self, func):
#         """Initialize the object."""
#         self._func = func
# 
#     def __call__(self, *args, **kwargs):
#         """Dispatch to the wrapped callable."""
#         return self._func(*args, **kwargs)
# 
# def prod_(obj):
#     """Product the values in the given iterable.
# 
#     Similar to the summation utility function :py:func:`sum_`, here the initial
#     value for the reduction is the first element.  Different from the summation,
#     here a SymPy integer unity will be returned for empty iterator.
#     """
# 
#     i = iter(obj)
#     try:
#         init = next(i)
#     except StopIteration:
#         return Integer(1)
#     else:
#         return functools.reduce(operator.mul, i, init)


import abc
import collections
import functools
import itertools
import operator
import types
import typing
import warnings
from collections.abc import Iterable, Mapping, Callable, Sequence
from sympy import (
    sympify, Symbol, KroneckerDelta, Eq, solveset, S, Integer, Add, Mul, Number,
    Indexed, IndexedBase, Expr, Basic, Pow, Wild, conjugate, Sum, Piecewise,
    Intersection, expand_power_base, expand_power_exp
)
from sympy.core.sympify import CantSympify
from .canon import canon_factors
from .utils import (
    ensure_symb, ensure_expr, sympy_key, is_higher, NonsympifiableFunc, prod_
)
                subst_dict, sums=_cat_sums(i.sums, sum_i)
            ) for i in curr_inp_terms]

            res.extend(curr_terms)

            continue
        continue

    return res


def _parse_sums(args):
    """Parse the summation arguments passed to the sum interface.

    The result will be the decomposed form of the summations and substitutions
    from the arguments.  For either of them, each entry in the result is a list
    of pairs of the dummy with the actual range or symbolic expression.
    """

    sums = []
    substs = []

    for arg in args:

        if not isinstance(arg, Sequence):
            raise TypeError('Invalid summation', arg, 'expecting a sequence')
        if len(arg) < 2:
            raise ValueError('Invalid summation', arg,
                             'expecting dummy and range')


# Path: drudge/canon.py
# def canon_factors(sums, factors, symms):
#     """Canonicalize the factors.
# 
#     The factors should be a iterable of factor/colour pairs, where the factor
#     can be anything with the ``base`` and ``indices`` methods implemented, or
#     ``func`` and ``args`` implemented.  It is designed primarily to work with
#     SymPy Indexed quantities and vectors, or general SymPy function expressions.
#     The colour should be totally ordered, and they only need to order within
#     factors correctly.
# 
#     The new canonicalized list of summations and canonicalized factors are
#     going to be returned.  Also returned is a coefficient that need to be
#     multiplied to the amplitude, which is from anti-commutative quantities.
# 
#     The symmetries should be given as a mapping from the *base* of the factors
#     to the actual symmetries.  A given valence can also be given by a tuple with
#     the actual base.
# 
#     """
# 
#     from .term import Vec
# 
#     # They need to be looped over multiple times.
#     sums = list(sums)
#     factors = list(factors)
# 
#     # TODO: make handling of empty eldags more elegant.
#     if len(factors) == 0 and len(sums) == 0:
#         return sums, factors, 1
# 
#     eldag, factor_idxes = _build_eldag(sums, factors, symms)
#     node_order, perms = eldag.canon()
# 
#     # Sums are guaranteed to be in the initial segment of the nodes, but they
#     # might not be at the beginning any more after the canonicalization.
#     sums_res = [sums[i] for i in node_order if
#                 eldag.colours[i][0] == _SUM]
# 
#     coeff = 1
#     factors_res = []
# 
#     for i, v in enumerate(factors):
# 
#         factor = v[0]
#         if hasattr(factor, 'indices'):
#             indices = factor.indices
#             is_indexed = True
#         else:
#             indices = factor.args
#             is_indexed = False
#         valency = len(indices)
#         perm = perms[factor_idxes[i]]
#         if_vector = isinstance(factor, Vec)
# 
#         if valency < 2 or perm is None:
#             factor_res = factor
#         else:
#             new_indices = tuple(indices[perm[i]] for i in range(valency))
#             if is_indexed:
#                 factor_res = factor.base[new_indices]
#             else:
#                 factor_res = factor.func(new_indices)
# 
#             acc = perm.acc
#             if acc & NEG:
#                 if if_vector:
#                     coeff *= -1
#                 else:
#                     factor_res = -factor_res
#             if acc & CONJ:
#                 # TODO: Allow vectors to have their own dagger form, maybe.
#                 if if_vector:
#                     raise ValueError(
#                         'Vector', factor, 'cannot have conjugation symmetry'
#                     )
#                 factor_res = conjugate(factor_res)
# 
#         factors_res.append(factor_res)
#         continue
# 
#     return sums_res, factors_res, coeff
# 
# Path: drudge/utils.py
# def ensure_symb(obj, role=''):
#     """Sympify the given object into a symbol."""
#     return ensure_sympify(obj, role, Symbol)
# 
# def ensure_expr(obj, role=''):
#     """Sympify the given object into an expression."""
#     return ensure_sympify(obj, role, Expr)
# 
# def sympy_key(expr):
#     """Get the key for ordering SymPy expressions.
# 
#     This function assumes that the given expression is already sympified.
#     """
# 
#     return count_ops(expr), default_sort_key(expr)
# 
# def is_higher(obj, priority):
#     """Test if the object has higher operation priority.
# 
#     When the given object does not have defined priority, it is considered
#     lower.
#     """
# 
#     return getattr(obj, '_op_priority', priority - 1) > priority
# 
# class NonsympifiableFunc(CantSympify):
#     """Utility for wrapping callable to be used for SymPy.
# 
#     Inside SymPy functions like replace, things will first be attempted to be
#     sympified, which can be very expensive.  By wrapping callable inside this
#     class, sympification attempts will be aborted very early on.
# 
#     """
# 
#     __slots__ = ['_func']
# 
#     def __init__(self, func):
#         """Initialize the object."""
#         self._func = func
# 
#     def __call__(self, *args, **kwargs):
#         """Dispatch to the wrapped callable."""
#         return self._func(*args, **kwargs)
# 
# def prod_(obj):
#     """Product the values in the given iterable.
# 
#     Similar to the summation utility function :py:func:`sum_`, here the initial
#     value for the reduction is the first element.  Different from the summation,
#     here a SymPy integer unity will be returned for empty iterator.
#     """
# 
#     i = iter(obj)
#     try:
#         init = next(i)
#     except StopIteration:
#         return Integer(1)
#     else:
#         return functools.reduce(operator.mul, i, init)


# Path: drudge/term.py
import abc
import collections
import functools
import itertools
import operator
import types
import typing
import warnings
from collections.abc import Iterable, Mapping, Callable, Sequence
from sympy import (
    sympify, Symbol, KroneckerDelta, Eq, solveset, S, Integer, Add, Mul, Number,
    Indexed, IndexedBase, Expr, Basic, Pow, Wild, conjugate, Sum, Piecewise,
    Intersection, expand_power_base, expand_power_exp
)
from sympy.core.sympify import CantSympify
from .canon import canon_factors
from .utils import (
    ensure_symb, ensure_expr, sympy_key, is_higher, NonsympifiableFunc, prod_
)

                subst_dict, sums=_cat_sums(i.sums, sum_i)
            ) for i in curr_inp_terms]

            res.extend(curr_terms)

            continue
        continue

    return res


def _parse_sums(args):
    """Parse the summation arguments passed to the sum interface.

    The result will be the decomposed form of the summations and substitutions
    from the arguments.  For either of them, each entry in the result is a list
    of pairs of the dummy with the actual range or symbolic expression.
    """

    sums = []
    substs = []

    for arg in args:

        if not isinstance(arg, Sequence):
            raise TypeError('Invalid summation', arg, 'expecting a sequence')
        if len(arg) < 2:
            raise ValueError('Invalid summation', arg,
                             'expecting dummy and range')


        dumm = ensure_symb(arg[0], 'dummy')