# Path: drudge/term.py
# class Range:
#     """A symbolic range that can be summed over.
# 
#     This class is for symbolic ranges that is going to be summed over in
#     tensors.  Each range should have a label, and optionally lower and upper
#     bounds, which should be both given or absent.  The label can be any hashable
#     and ordered Python type.  The bounds will not be directly used for symbolic
#     computation, but rather designed for printers and conversion to SymPy
#     summation.  Note that ranges are assumed to be atomic and disjoint.  Even in
#     the presence of lower and upper bounds, unequal ranges are assumed to be
#     disjoint.
# 
#     .. warning::
# 
#         Equality comparison and hashing of ranges are *completely* based on the
#         label, without consideration of the bounds.  So ranges with the same
#         label but different bounds will be considered equal.  This is to
#         facilitate the usage of a same dummy set for ranges with slight
#         variation in the bounds.
# 
#     .. warning::
# 
#          Unequal ranges are always assumed to be disjoint.
# 
#     """
# 
#     __slots__ = [
#         '_label',
#         '_lower',
#         '_upper'
#     ]
# 
#     def __init__(self, label, lower=None, upper=None):
#         """Initialize the symbolic range.
#         """
#         self._label = label
#         self._lower = (
#             ensure_expr(lower, 'lower bound') if lower is not None else lower
#         )
# 
#         if self._lower is None:
#             if upper is not None:
#                 raise ValueError('lower range has not been given.')
#             else:
#                 self._upper = None
#         else:
#             if upper is None:
#                 raise ValueError('upper range has not been given.')
#             else:
#                 self._upper = ensure_expr(upper, 'upper bound')
# 
#     @property
#     def label(self):
#         """The label of the range."""
#         return self._label
# 
#     @property
#     def lower(self):
#         """The lower bound of the range."""
#         return self._lower
# 
#     @property
#     def upper(self):
#         """The upper bound of the range."""
#         return self._upper
# 
#     @property
#     def size(self):
#         """The size of the range.
# 
#         This property given None for unbounded ranges.  For bounded ranges, it
#         is the difference between the lower and upper bound.  Note that this
#         contradicts the deeply entrenched mathematical convention of including
#         other ends for a range.  But it does gives a lot of convenience and
#         elegance.
#         """
# 
#         return self._upper - self._lower if self.bounded else None
# 
#     @property
#     def bounded(self):
#         """If the range is explicitly bounded."""
#         return self._lower is not None
# 
#     @property
#     def args(self):
#         """The arguments for range creation.
# 
#         When the bounds are present, we have a triple, or we have a singleton
#         tuple of only the label.
#         """
# 
#         if self.bounded:
#             return self._label, self._lower, self._upper
#         else:
#             return self._label,
# 
#     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])
#         return key
# 
#     def replace_label(self, new_label):
#         """Replace the label of a given range.
# 
#         The bounds will be the same as the original range.
#         """
# 
#         return Range(new_label, self._lower, self._upper)
# 
#     def __lt__(self, other):
#         """Compare two ranges.
# 
#         This method is meant to skip explicit calling of the sort key when it is
#         not convenient.
#         """
# 
#         if not isinstance(other, Range):
#             raise TypeError('Invalid range to compare', other)
# 
#         return self.sort_key < other.sort_key
# 
#     def __getitem__(self, item):
#         """Get a new range with explicit bounds set.
# 
#         This can be used on both symbolic range to give them explicit bounds,
#         and on bounded range to set new bounds for them.
#         """
#         if not isinstance(item, tuple) or len(item) != 2:
#             raise TypeError(
#                 'Invalid bounds for range, expecting upper and lower range',
#                 item
#             )
# 
#         lower, upper = [sympify(i) for i in item]
#         return Range(self._label, lower=lower, upper=upper)
# 
#     def map(self, func):
#         """Map the given function to the bounds of the range.
# 
#         For ranges without bounds, the same range is simply returned.
#         """
#         if self.bounded:
#             return self[func(self.lower), func(self.upper)]
#         else:
#             return self


from sympy import sympify
from drudge import Range
"""Tests for the range class."""




def test_range_has_basic_operations():
    """Test the basic operations on ranges."""

    a_symb = sympify('a')
    b_symb = sympify('b')


# Path: drudge/term.py
# class Range:
#     """A symbolic range that can be summed over.
# 
#     This class is for symbolic ranges that is going to be summed over in
#     tensors.  Each range should have a label, and optionally lower and upper
#     bounds, which should be both given or absent.  The label can be any hashable
#     and ordered Python type.  The bounds will not be directly used for symbolic
#     computation, but rather designed for printers and conversion to SymPy
#     summation.  Note that ranges are assumed to be atomic and disjoint.  Even in
#     the presence of lower and upper bounds, unequal ranges are assumed to be
#     disjoint.
# 
#     .. warning::
# 
#         Equality comparison and hashing of ranges are *completely* based on the
#         label, without consideration of the bounds.  So ranges with the same
#         label but different bounds will be considered equal.  This is to
#         facilitate the usage of a same dummy set for ranges with slight
#         variation in the bounds.
# 
#     .. warning::
# 
#          Unequal ranges are always assumed to be disjoint.
# 
#     """
# 
#     __slots__ = [
#         '_label',
#         '_lower',
#         '_upper'
#     ]
# 
#     def __init__(self, label, lower=None, upper=None):
#         """Initialize the symbolic range.
#         """
#         self._label = label
#         self._lower = (
#             ensure_expr(lower, 'lower bound') if lower is not None else lower
#         )
# 
#         if self._lower is None:
#             if upper is not None:
#                 raise ValueError('lower range has not been given.')
#             else:
#                 self._upper = None
#         else:
#             if upper is None:
#                 raise ValueError('upper range has not been given.')
#             else:
#                 self._upper = ensure_expr(upper, 'upper bound')
# 
#     @property
#     def label(self):
#         """The label of the range."""
#         return self._label
# 
#     @property
#     def lower(self):
#         """The lower bound of the range."""
#         return self._lower
# 
#     @property
#     def upper(self):
#         """The upper bound of the range."""
#         return self._upper
# 
#     @property
#     def size(self):
#         """The size of the range.
# 
#         This property given None for unbounded ranges.  For bounded ranges, it
#         is the difference between the lower and upper bound.  Note that this
#         contradicts the deeply entrenched mathematical convention of including
#         other ends for a range.  But it does gives a lot of convenience and
#         elegance.
#         """
# 
#         return self._upper - self._lower if self.bounded else None
# 
#     @property
#     def bounded(self):
#         """If the range is explicitly bounded."""
#         return self._lower is not None
# 
#     @property
#     def args(self):
#         """The arguments for range creation.
# 
#         When the bounds are present, we have a triple, or we have a singleton
#         tuple of only the label.
#         """
# 
#         if self.bounded:
#             return self._label, self._lower, self._upper
#         else:
#             return self._label,
# 
#     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])
#         return key
# 
#     def replace_label(self, new_label):
#         """Replace the label of a given range.
# 
#         The bounds will be the same as the original range.
#         """
# 
#         return Range(new_label, self._lower, self._upper)
# 
#     def __lt__(self, other):
#         """Compare two ranges.
# 
#         This method is meant to skip explicit calling of the sort key when it is
#         not convenient.
#         """
# 
#         if not isinstance(other, Range):
#             raise TypeError('Invalid range to compare', other)
# 
#         return self.sort_key < other.sort_key
# 
#     def __getitem__(self, item):
#         """Get a new range with explicit bounds set.
# 
#         This can be used on both symbolic range to give them explicit bounds,
#         and on bounded range to set new bounds for them.
#         """
#         if not isinstance(item, tuple) or len(item) != 2:
#             raise TypeError(
#                 'Invalid bounds for range, expecting upper and lower range',
#                 item
#             )
# 
#         lower, upper = [sympify(i) for i in item]
#         return Range(self._label, lower=lower, upper=upper)
# 
#     def map(self, func):
#         """Map the given function to the bounds of the range.
# 
#         For ranges without bounds, the same range is simply returned.
#         """
#         if self.bounded:
#             return self[func(self.lower), func(self.upper)]
#         else:
#             return self


# Path: tests/range_test.py
from sympy import sympify
from drudge import Range

"""Tests for the range class."""




def test_range_has_basic_operations():
    """Test the basic operations on ranges."""

    a_symb = sympify('a')
    b_symb = sympify('b')


    bound0 = Range('B', 'a', 'b')