Repository: sympy/sympy

Issue:
elementary_row_op() raise a false ValueError saying "This matrix doesn't have a row '5'"
I was using sympy for elementary row operations when it raised an expected ValueError. The code is like this:
```python
from sympy import Matrix, pprint

C = Matrix([
    [3,4,-1,1],
    [9,12,-3,3],
    [0,2,1,3],
    [2,3,0,-2],
    [0,3,3,-5],
    [8,15,0,6],
])
pprint(C)
pprint(C.elementary_row_op('n<->m', row1=2, row2=5))
```

This code will raise `ValueError: This matrix doesn't have a row '5'`, but I'm pretty sure that the matrix does have a row 5. It works fine if I do `C.elementary_row_op('n<->m', row1=2, row2=4)`.

Strangely, if I add an additional column and doesn't change the count of rows at all, it will work fine:

```python
from sympy import Matrix, pprint

C = Matrix([
    [3,4,-1,1,0],
    [9,12,-3,3,0],
    [0,2,1,3,0],
    [2,3,0,-2,0],
    [0,3,3,-5,0],
    [8,15,0,6,0],
])
pprint(C)
pprint(C.elementary_row_op('n<->m', row1=2, row2=5))
```

I'm novice both in sympy and in linear algebra. Is it a bug or did I miss out something?

p.s. Screenshot attached:
<img width="940" alt="Screenshot 2019-10-31 at 15 33 14" src="https://user-images.githubusercontent.com/12994473/67927566-dfb02b00-fbf3-11e9-9521-a824ee7b08a2.png">
Running in macOS 10.14, python 3.7, sympy 1.4.

Discussion:

Patch:
diff --git a/sympy/matrices/matrices.py b/sympy/matrices/matrices.py
index 1cb55f88a6..270f5fa464 100644
--- a/sympy/matrices/matrices.py
+++ b/sympy/matrices/matrices.py
@@ -602,13 +602,16 @@ def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"):
             raise ValueError("Unknown {} operation '{}'. Valid col operations "
                              "are 'n->kn', 'n<->m', 'n->n+km'".format(error_str, op))
 
+        # define self_col according to error_str
+        self_cols = self.cols if error_str == 'col' else self.rows
+
         # normalize and validate the arguments
         if op == "n->kn":
             col = col if col is not None else col1
             if col is None or k is None:
                 raise ValueError("For a {0} operation 'n->kn' you must provide the "
                                  "kwargs `{0}` and `k`".format(error_str))
-            if not 0 <= col <= self.cols:
+            if not 0 <= col < self_cols:
                 raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col))
 
         if op == "n<->m":
@@ -623,9 +626,9 @@ def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"):
                 raise ValueError("For a {0} operation 'n<->m' you must provide the "
                                  "kwargs `{0}1` and `{0}2`".format(error_str))
             col1, col2 = cols
-            if not 0 <= col1 <= self.cols:
+            if not 0 <= col1 < self_cols:
                 raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col1))
-            if not 0 <= col2 <= self.cols:
+            if not 0 <= col2 < self_cols:
                 raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col2))
 
         if op == "n->n+km":
@@ -637,9 +640,9 @@ def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"):
             if col == col2:
                 raise ValueError("For a {0} operation 'n->n+km' `{0}` and `{0}2` must "
                                  "be different.".format(error_str))
-            if not 0 <= col <= self.cols:
+            if not 0 <= col < self_cols:
                 raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col))
-            if not 0 <= col2 <= self.cols:
+            if not 0 <= col2 < self_cols:
                 raise ValueError("This matrix doesn't have a {} '{}'".format(error_str, col2))
 
         return op, col, k, col1, col2


Test patch:
diff --git a/sympy/matrices/tests/test_matrices.py b/sympy/matrices/tests/test_matrices.py
index 5938387867..00b974e661 100644
--- a/sympy/matrices/tests/test_matrices.py
+++ b/sympy/matrices/tests/test_matrices.py
@@ -3822,3 +3822,24 @@ def test_gramschmidt_conjugate_dot():
     mat = Matrix([[1, I], [1, -I]])
     Q, R = mat.QRdecomposition()
     assert Q * Q.H == Matrix.eye(2)
+
+def test_issue_17827():
+    C = Matrix([
+        [3, 4, -1, 1],
+        [9, 12, -3, 3],
+        [0, 2, 1, 3],
+        [2, 3, 0, -2],
+        [0, 3, 3, -5],
+        [8, 15, 0, 6]
+    ])
+    # Tests for row/col within valid range
+    D = C.elementary_row_op('n<->m', row1=2, row2=5)
+    E = C.elementary_row_op('n->n+km', row1=5, row2=3, k=-4)
+    F = C.elementary_row_op('n->kn', row=5, k=2)
+    assert(D[5, :] == Matrix([[0, 2, 1, 3]]))
+    assert(E[5, :] == Matrix([[0, 3, 0, 14]]))
+    assert(F[5, :] == Matrix([[16, 30, 0, 12]]))
+    # Tests for row/col out of range
+    raises(ValueError, lambda: C.elementary_row_op('n<->m', row1=2, row2=6))
+    raises(ValueError, lambda: C.elementary_row_op('n->kn', row=7, k=2))
+    raises(ValueError, lambda: C.elementary_row_op('n->n+km', row1=-1, row2=5, k=2))