<commit_before>from collections import OrderedDict

from sympy import Basic

from devito.tools import as_tuple, is_integer, memoized_meth
from devito.types import Dimension

__all__ = ['Vector', 'LabeledVector', 'vmin', 'vmax']


class Vector(tuple):

    """
    A representation of an object in Z^n.

    The elements of a Vector can be integers or any SymPy expression.

    Notes
    -----
    1) Vector-scalar comparison
    If a comparison between a vector and a non-vector is attempted, then the
    non-vector is promoted to a vector; if this is not possible, an exception
    is raised. This is handy because it turns a vector-scalar comparison into
    a vector-vector comparison with the scalar broadcasted to all vector entries.
    For example: ::

        (3, 4, 5) > 4 => (3, 4, 5) > (4, 4, 4) => False

    2) Comparing Vector entries when these are SymPy expression
    When we compare two symbolic (SymPy expressions) entries, it might not be
    possible to determine the truth value of the relation. For example, the
    truth value of `3*i < 4*j` cannot be determined (unless some information
    about `i` and `j` is available). In some cases, however, the comparison is
    feasible; for example, `i + 4 < i` is definitely False. A sufficient condition
    for two Vectors to be comparable is that their pair-wise indices are affine
    functions of the same variables, with identical coefficient.
    If the Vector is instantiated passing the keyword argument ``smart = True``,
    some manipulation will be attempted to infer the truth value of a non-trivial
    symbolic relation. This increases the cost of the comparison, while potentially
    being ineffective, so use it judiciously. By default, ``smart = False``.

    Raises
    ------
    TypeError
        If two Vectors cannot be compared, e.g. due to uncomparable symbolic entries.
    """

    def __new__(cls, *items, smart=False):
        if not all(is_integer(i) or isinstance(i, Basic) for i in items):
            raise TypeError("Illegal Vector element type")
        obj = super(Vector, cls).__new__(cls, items)
        obj.smart = smart
        return obj

    def _asvector(relax=False):
        def __asvector(func):
            def wrapper(self, other):
                if not isinstance(other, Vector):
                    try:
                        other = Vector(*other)
                    except TypeError:
                        # Not iterable
                        other = Vector(*(as_tuple(other)*len(self)))
                if relax is False and len(self) != len(other):
                    raise TypeError("Cannot operate with Vectors of different rank")
                return func(self, other)
            return wrapper
        return __asvector

    def __hash__(self):
        return super(Vector, self).__hash__()

    @_asvector()
    def __add__(self, other):
        return Vector(*[i + j for i, j in zip(self, other)], smart=self.smart)

    @_asvector()
    def __radd__(self, other):
        return self + other

    @_asvector()
    def __sub__(self, other):
        return Vector(*[i - j for i, j in zip(self, other)], smart=self.smart)

    @_asvector()
    def __rsub__(self, other):
        return self - other

    @_asvector(relax=True)
    def __eq__(self, other):
        return super(Vector, self).__eq__(other)

    @_asvector(relax=True)
    def __ne__(self, other):
        return super(Vector, self).__ne__(other)

    @_asvector()
    def __lt__(self, other):
        # This might raise an exception if the distance between the i-th entry
        # of `self` and `other` isn't integer, but rather a generic expression
        # not comparable to 0. However, the implementation is "smart", in the
        # sense that it will return as soon as the first two comparable entries
        # (i.e., such that their distance is a non-zero integer) are found
        for i in self.distance(other):
            try:
                val = int(i)
                if val < 0:
                    return True
                elif val > 0:
                    return False
            except TypeError:
                if self.smart:
                    # Note: the relationals below may be SymPy exprs, hence the ==
                    if (i < 0) == True:
                        return True
                    elif (i <= 0) == True:
                        # If `i` can assume the value 0 in at least one case, then
                        # definitely `i < 0` is generally False, so __lt__ must
                        # return False
                        return False
                    elif (i >= 0) == True:
                        return False
                raise TypeError("Non-comparable index functions")

        return False

    @_asvector()
    def __gt__(self, other):
        return other.__lt__(self)

    @_asvector()
    def __le__(self, other):
        if self.__eq__(other):
            return True

        # We cannot simply resort to `__lt__` as it might happen that:
        # * v0 < v1 --> False
        # * v0 == v1 --> False
        # But
        # * v0 <= v1 --> True
        #
        # For example, take `v0 = (a + 2)` and `v1 = (2)`; if `a` is attached
        # the property that definitely `a >= 0`, then surely `v1 <= v0`, even
        # though it can't be assumed anything about `v1 < 0` and `v1 == v0`
        for i in self.distance(other):
            try:
                val = int(i)
                if val < 0:
                    return True
                elif val > 0:
                    return False
            except TypeError:
                if self.smart:
                    # Note: the relationals below may be SymPy exprs, hence the ==
                    if (i < 0) == True:
                        return True
                    elif (i <= 0) == True:
                        continue
                    elif (i > 0) == True:
                        return False
                    elif (i >= 0) == True:
                        # See analogous considerations in __lt__
                        return False
                raise TypeError("Non-comparable index functions")
<commit_msg>ir: Use sympy.BooleanTrue for vector entry comparison<commit_after>from collections import OrderedDict

from sympy import Basic, true

from devito.tools import as_tuple, is_integer, memoized_meth
from devito.types import Dimension

__all__ = ['Vector', 'LabeledVector', 'vmin', 'vmax']


class Vector(tuple):

    """
    A representation of an object in Z^n.

    The elements of a Vector can be integers or any SymPy expression.

    Notes
    -----
    1) Vector-scalar comparison
    If a comparison between a vector and a non-vector is attempted, then the
    non-vector is promoted to a vector; if this is not possible, an exception
    is raised. This is handy because it turns a vector-scalar comparison into
    a vector-vector comparison with the scalar broadcasted to all vector entries.
    For example: ::

        (3, 4, 5) > 4 => (3, 4, 5) > (4, 4, 4) => False

    2) Comparing Vector entries when these are SymPy expression
    When we compare two symbolic (SymPy expressions) entries, it might not be
    possible to determine the truth value of the relation. For example, the
    truth value of `3*i < 4*j` cannot be determined (unless some information
    about `i` and `j` is available). In some cases, however, the comparison is
    feasible; for example, `i + 4 < i` is definitely False. A sufficient condition
    for two Vectors to be comparable is that their pair-wise indices are affine
    functions of the same variables, with identical coefficient.
    If the Vector is instantiated passing the keyword argument ``smart = True``,
    some manipulation will be attempted to infer the truth value of a non-trivial
    symbolic relation. This increases the cost of the comparison, while potentially
    being ineffective, so use it judiciously. By default, ``smart = False``.

    Raises
    ------
    TypeError
        If two Vectors cannot be compared, e.g. due to uncomparable symbolic entries.
    """

    def __new__(cls, *items, smart=False):
        if not all(is_integer(i) or isinstance(i, Basic) for i in items):
            raise TypeError("Illegal Vector element type")
        obj = super(Vector, cls).__new__(cls, items)
        obj.smart = smart
        return obj

    def _asvector(relax=False):
        def __asvector(func):
            def wrapper(self, other):
                if not isinstance(other, Vector):
                    try:
                        other = Vector(*other)
                    except TypeError:
                        # Not iterable
                        other = Vector(*(as_tuple(other)*len(self)))
                if relax is False and len(self) != len(other):
                    raise TypeError("Cannot operate with Vectors of different rank")
                return func(self, other)
            return wrapper
        return __asvector

    def __hash__(self):
        return super(Vector, self).__hash__()

    @_asvector()
    def __add__(self, other):
        return Vector(*[i + j for i, j in zip(self, other)], smart=self.smart)

    @_asvector()
    def __radd__(self, other):
        return self + other

    @_asvector()
    def __sub__(self, other):
        return Vector(*[i - j for i, j in zip(self, other)], smart=self.smart)

    @_asvector()
    def __rsub__(self, other):
        return self - other

    @_asvector(relax=True)
    def __eq__(self, other):
        return super(Vector, self).__eq__(other)

    @_asvector(relax=True)
    def __ne__(self, other):
        return super(Vector, self).__ne__(other)

    @_asvector()
    def __lt__(self, other):
        # This might raise an exception if the distance between the i-th entry
        # of `self` and `other` isn't integer, but rather a generic expression
        # not comparable to 0. However, the implementation is "smart", in the
        # sense that it will return as soon as the first two comparable entries
        # (i.e., such that their distance is a non-zero integer) are found
        for i in self.distance(other):
            try:
                val = int(i)
                if val < 0:
                    return True
                elif val > 0:
                    return False
            except TypeError:
                if self.smart:
                    if (i < 0) == true:
                        return True
                    elif (i <= 0) == true:
                        # If `i` can assume the value 0 in at least one case, then
                        # definitely `i < 0` is generally False, so __lt__ must
                        # return False
                        return False
                    elif (i >= 0) == true:
                        return False
                raise TypeError("Non-comparable index functions")

        return False

    @_asvector()
    def __gt__(self, other):
        return other.__lt__(self)

    @_asvector()
    def __le__(self, other):
        if self.__eq__(other):
            return True

        # We cannot simply resort to `__lt__` as it might happen that:
        # * v0 < v1 --> False
        # * v0 == v1 --> False
        # But
        # * v0 <= v1 --> True
        #
        # For example, take `v0 = (a + 2)` and `v1 = (2)`; if `a` is attached
        # the property that definitely `a >= 0`, then surely `v1 <= v0`, even
        # though it can't be assumed anything about `v1 < 0` and `v1 == v0`
        for i in self.distance(other):
            try:
                val = int(i)
                if val < 0:
                    return True
                elif val > 0:
                    return False
            except TypeError:
                if self.smart:
                    if (i < 0) == true:
                        return True
                    elif (i <= 0) == true:
                        continue
                    elif (i > 0) == true:
                        return False
                    elif (i >= 0) == true:
                        # See analogous considerations in __lt__
                        return False
                raise TypeError("Non-comparable index functions")

{'fraction_non_alphanumeric': 0.08394637894392676, 'fraction_numerical': 0.007520026156612718, 'mean_word_length': 1.7414295317051312, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1178167', 'n_tokens_mistral': 3318, 'n_tokens_neox': 3103, 'n_words': 1321}