Repository: sympy/sympy
Pull request: Fix Idx bounds assumptions
This PR fixes #12780 

Problem statement:
Idx should not require integer assumptions
```py
>>> n = symbols('n')
>>> Idx('i', (0, n))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./sympy/tensor/indexed.py", line 585, in __new__
    raise TypeError("Idx object requires integer bounds.")
TypeError: Idx object requires integer bounds.
```

Requiring assumptions is an antipattern. It should only fail if `is_integer` if False.

Hints from issue discussion:
Hi @asmeurer,

Going through the `Idx` docstring, it states that 

> Note: the ``Idx`` constructor is rather pedantic in that it only accepts
    integer arguments.  The only exception is that you can use ``-oo`` and
    ``oo`` to specify an unbounded range.  For all other cases, both label and
    bounds must be declared as integers, e.g. if ``n`` is given as an argument
    then ``n.is_integer`` must return ``True``.

Therefore it appears that you must declare your symbols as integers, in your case:

```python
n = symbol('n', integer=True)
```

in which case `Idx('i', (0, n))` will return i.

Is this the solution that you were looking for? If not could you guide me through a possible solution, as I'm fairly new to sympy.
Yes, this is the current behavior. But it should work with just `symbols('n')`. It should only check if `is_integer` if `False`, and not fail if it is `None`.

Commit message: Update docs, add test for issue 12780

Patch to review:
diff --git a/sympy/tensor/indexed.py b/sympy/tensor/indexed.py
index 7e40f1554c7e..debcffa38029 100644
--- a/sympy/tensor/indexed.py
+++ b/sympy/tensor/indexed.py
@@ -510,11 +510,9 @@ class Idx(Expr):
         * ``tuple``: The two elements are interpreted as the lower and upper
           bounds of the range, respectively.
 
-    Note: the ``Idx`` constructor is rather pedantic in that it only accepts
-    integer arguments.  The only exception is that you can use ``-oo`` and
-    ``oo`` to specify an unbounded range.  For all other cases, both label and
-    bounds must be declared as integers, e.g. if ``n`` is given as an argument
-    then ``n.is_integer`` must return ``True``.
+    Note: bounds of the range are assumed to be either integer or infinite (oo
+    and -oo are allowed to specify an unbounded range). If ``n`` is given as a
+    bound, then ``n.is_integer`` must not return false.
 
     For convenience, if the label is given as a string it is automatically
     converted to an integer symbol.  (Note: this conversion is not done for
@@ -581,7 +579,7 @@ def __new__(cls, label, range=None, **kw_args):
                 raise ValueError(filldedent("""
                     Idx range tuple must have length 2, but got %s""" % len(range)))
             for bound in range:
-                if not (bound.is_integer or abs(bound) is S.Infinity):
+                if not (bound.is_integer != False or abs(bound) is S.Infinity):
                     raise TypeError("Idx object requires integer bounds.")
             args = label, Tuple(*range)
         elif isinstance(range, Expr):
diff --git a/sympy/tensor/tests/test_indexed.py b/sympy/tensor/tests/test_indexed.py
index 78716a3c64b8..09f2b2fe19cc 100644
--- a/sympy/tensor/tests/test_indexed.py
+++ b/sympy/tensor/tests/test_indexed.py
@@ -14,7 +14,7 @@ def test_Idx_construction():
     assert Idx(i, a) == Idx(i, (0, a - 1))
     assert Idx(i, oo) == Idx(i, (0, oo))
 
-    x = symbols('x')
+    x = symbols('x', integer=False)
     raises(TypeError, lambda: Idx(x))
     raises(TypeError, lambda: Idx(0.5))
     raises(TypeError, lambda: Idx(i, x))
@@ -362,3 +362,9 @@ def test_issue_12533():
     assert d[0].subs(d, range(5)) == 0
     assert d[1].subs(d, range(5)) == 1
     assert Indexed(Range(5), 2) == 2
+
+
+def test_issue_12780():
+    n = symbols("n")
+    i = Idx("i", (0, n))
+    raises(TypeError, lambda: i.subs(n, 1.5))


Code review comments:
diff_hunk: @@ -581,7 +579,7 @@ def __new__(cls, label, range=None, **kw_args):
                 raise ValueError(filldedent("""
                     Idx range tuple must have length 2, but got %s""" % len(range)))
             for bound in range:
-                if not (bound.is_integer or abs(bound) is S.Infinity):
+                if not (bound.is_integer != False or abs(bound) is S.Infinity):
path: sympy/tensor/indexed.py
text: @user1:
Since `oo.is_integer == None` it's enough to write: `if bound.is_integer is False:`