# 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_
)

    def __hash__(self):
        """Hash the symbolic range.
        """
        return hash(self._label)

    def __eq__(self, other):
        """Compare equality of two ranges.
        """
        return isinstance(other, type(self)) and (
                self._label == other.label
        )

    def __repr__(self):
        """Form the representative string.
        """
        return ''.join([
            'Range(', ', '.join(repr(i) for i in self.args), ')'
        ])

    def __str__(self):
        """Form readable string representation.
        """
        return str(self._label)

    @property
    def sort_key(self):
        """The sort key for the range."""
        key = [self._label]
        if self.bounded:

# 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_
)


    def __hash__(self):
        """Hash the symbolic range.
        """
        return hash(self._label)

    def __eq__(self, other):
        """Compare equality of two ranges.
        """
        return isinstance(other, type(self)) and (
                self._label == other.label
        )

    def __repr__(self):
        """Form the representative string.
        """
        return ''.join([
            'Range(', ', '.join(repr(i) for i in self.args), ')'
        ])

    def __str__(self):
        """Form readable string representation.
        """
        return str(self._label)

    @property
    def sort_key(self):
        """The sort key for the range."""
        key = [self._label]
        if self.bounded:

            key.extend(sympy_key(i) for i in [self._lower, self._upper])