Repository: Theano/Theano

Issue:
Numpy 1.17.0 issues
I maintain the Theano package for the Fedora Linux distribution.  We recently built all packages for the upcoming Fedora 31, and the Theano package, version 1.0.4, failed its tests.  There was 1 ERROR and 4 FAILs.  I tracked the issue down to the introduction of numpy 1.17.0 in Fedora.  See https://bugzilla.redhat.com/show_bug.cgi?id=1737011 for more information.  Here is what I have figured out so far.

The ERROR is because of changes to numpy's ceil, floor, and trunc ufuncs.  To fix it, class `_tensor_py_operators` in `theano/tensor/var.py` needs `__ceil__`, `__floor__`, and `__trunc__` methods that do exactly what the current `ceil`, `floor`, and `trunc` methods do.

The FAIL in `theano/gof/tests/test_compute_test_value.py` is because there is now one extra frame on the stack.  Line 289 of `theano/gof/tests/test_compute_test_value.py` reads:

```
                frame_info = traceback.extract_tb(tb)[-5]
```

but the -5 has to be a -6 for numpy 1.17.0.

The FAIL in `theano/tensor/tests/test_basic.py` is because the behavior of numpy's clip function changed.  With numpy 1.16.4:

```
>>> np.clip(3, 5, 2)
5
```

but with numpy 1.17.0:

```
>>> np.clip(3, 5, 2)
2
```

I think the order of comparing x to min and max in `Clip.impl` (`theano/scalar/basic.py`) has to be reversed for numpy 1.17.0, if theano's clip is to behave like numpy's, as this test indicates.

I do not know what is causing the last two FAILs.  Two random number tests used to raise ValueError and now do not raise any error at all.


Discussion:
Thanks for bringing this to our attention.In the past, if numpy semantic was changing (as is the case here), we where following it.
But as Theano is very lightly maintained now, this would cause old code to behave differently depending of the NumPy version.
We do not want that now as people do not maintain well old codes expecially for a not-well-maintained software.
So I see 2 options:
- Just disable those functions. But this would break old codes, so this is not great.
- Fix our test to make it pass with newer and older numpy version, but keep the old semantic. I would also print a warning that the function use the old numpy semantic in those cases.

What do you think of that? Do you have time to work on a PR that does the second option?

I'm not sure I have the knowledge necessary for that second option.  I can give you a PR that fixes the ERROR, but I am not at all certain what should be done about the 4 FAILs.
Can you show all the errors?
See https://github.com/Theano/Theano/pull/6721 for a resolution to the ERROR.  Here are the 4 FAILs:
```
======================================================================
FAIL: test_scan_err1 (theano.gof.tests.test_compute_test_value.TestComputeTestValue)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/builddir/build/BUILD/Theano-rel-1.0.4/theano/gof/tests/test_compute_test_value.py", line 283, in test_scan_err1
    n_steps=k)
ValueError: shapes (5,3) and (5,3) not aligned: 3 (dim 1) != 5 (dim 0)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/builddir/build/BUILD/Theano-rel-1.0.4/theano/gof/tests/test_compute_test_value.py", line 292, in test_scan_err1
    assert os.path.split(frame_info[0])[1] == expected, frame_info
AssertionError: <FrameSummary file /builddir/build/BUILD/Theano-rel-1.0.4/theano/tensor/basic.py, line 6105 in dot>

======================================================================
FAIL: test_good (theano.tensor.tests.test_basic.ClipTester)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/builddir/build/BUILD/Theano-rel-1.0.4/theano/tensor/tests/test_basic.py", line 425, in test_good
    np.allclose(variable, expected)))
AssertionError: Test Elemwise{clip,no_inplace}::correct7: Output 0 gave the wrong value. With inputs [array([[-1.70586872,  2.46664369, -0.52189608,  4.84714482,  1.9208813 ],
       [ 1.28983643, -2.38914901, -1.67933224,  2.10808673,  3.58489085],
       [ 4.45900732, -3.70413992, -3.7683781 ,  1.87495988, -2.58993616],
       [-2.6853165 ,  2.19925062,  2.69281351,  1.39211517,  1.29298677],
       [ 4.43245431,  3.63622689, -2.58812035, -3.32239248,  0.24149971]]), array(1.), array(-1.)], expected [[-1. -1. -1. -1. -1.]
 [-1. -1. -1. -1. -1.]
 [-1. -1. -1. -1. -1.]
 [-1. -1. -1. -1. -1.]
 [-1. -1. -1. -1. -1.]] (dtype float64), got [[ 1. -1.  1. -1. -1.]
 [-1.  1.  1. -1. -1.]
 [-1.  1.  1. -1.  1.]
 [ 1. -1. -1. -1. -1.]
 [-1. -1.  1.  1.  1.]] (dtype float64). eps=0.000000 np.allclose returns False False

======================================================================
FAIL: test_vector_arguments (theano.tensor.tests.test_raw_random.T_random_function)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/builddir/build/BUILD/Theano-rel-1.0.4/theano/tensor/tests/test_raw_random.py", line 681, in test_vector_arguments
    self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1, 0], [1])
AssertionError: ValueError not raised by <theano.compile.function_module.Function object at 0x7fcab1fdf890>

======================================================================
FAIL: test_vector_arguments (theano.tensor.tests.test_shared_randomstreams.T_SharedRandomStreams)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/builddir/build/BUILD/Theano-rel-1.0.4/theano/tensor/tests/test_shared_randomstreams.py", line 469, in test_vector_arguments
    self.assertRaises(ValueError, fc, [-4., -2], [-1, 0], [1])
AssertionError: ValueError not raised by <theano.compile.function_module.Function object at 0x7fcab178de90>

----------------------------------------------------------------------
```

Resolving patch:
diff --git a/theano/tensor/sort.py b/theano/tensor/sort.py
--- a/theano/tensor/sort.py
+++ b/theano/tensor/sort.py
@@ -45,6 +45,10 @@ def make_node(self, input, axis=-1):
     def perform(self, node, inputs, output_storage):
         a = inputs[0]
         axis = inputs[1]
+        if axis is not None:
+            if axis != int(axis):
+                raise ValueError("sort axis must be an integer or None")
+            axis = int(axis)
         z = output_storage[0]
         z[0] = np.sort(a, axis, self.kind, self.order)
 
@@ -172,6 +176,10 @@ def make_node(self, input, axis=-1):
     def perform(self, node, inputs, output_storage):
         a = inputs[0]
         axis = inputs[1]
+        if axis is not None:
+            if axis != int(axis):
+                raise ValueError("sort axis must be an integer or None")
+            axis = int(axis)
         z = output_storage[0]
         z[0] = theano._asarray(np.argsort(a, axis, self.kind, self.order),
                                dtype=node.outputs[0].dtype)
diff --git a/theano/tensor/var.py b/theano/tensor/var.py
--- a/theano/tensor/var.py
+++ b/theano/tensor/var.py
@@ -247,6 +247,15 @@ def __rdivmod__(self, other):
     def __rpow__(self, other):
         return theano.tensor.basic.pow(other, self)
 
+    def __ceil__(self):
+        return theano.tensor.ceil(self)
+
+    def __floor__(self):
+        return theano.tensor.floor(self)
+
+    def __trunc__(self):
+        return theano.tensor.trunc(self)
+
     # TRANSPOSE
     T = property(lambda self: theano.tensor.basic.transpose(self))
 


Test patch:
diff --git a/theano/gof/tests/test_compute_test_value.py b/theano/gof/tests/test_compute_test_value.py
--- a/theano/gof/tests/test_compute_test_value.py
+++ b/theano/gof/tests/test_compute_test_value.py
@@ -285,12 +285,13 @@ def fx(prior_result, A):
             except ValueError:
                 # Get traceback
                 tb = sys.exc_info()[2]
-                # Get frame info 4 layers up
-                frame_info = traceback.extract_tb(tb)[-5]
+                frame_infos = traceback.extract_tb(tb)
                 # We should be in the "fx" function defined above
                 expected = 'test_compute_test_value.py'
-                assert os.path.split(frame_info[0])[1] == expected, frame_info
-                assert frame_info[2] == 'fx'
+                assert any((os.path.split(
+                    frame_info[0])[1] == expected and
+                    frame_info[2] == 'fx') for frame_info in
+                    frame_infos), frame_infos
 
         finally:
             theano.config.compute_test_value = orig_compute_test_value
diff --git a/theano/sparse/tests/test_basic.py b/theano/sparse/tests/test_basic.py
--- a/theano/sparse/tests/test_basic.py
+++ b/theano/sparse/tests/test_basic.py
@@ -160,7 +160,15 @@ def _rand():
     if unsorted_indices:
         for idx in range(n):
             d = data[idx]
-            d = d[list(range(d.shape[0]))]
+            # these flip the matrix, but it's random anyway
+            if format == 'csr':
+                d = scipy.sparse.csr_matrix(
+                    (d.data, d.shape[1] - 1 - d.indices, d.indptr),
+                    shape=d.shape)
+            if format == 'csc':
+                d = scipy.sparse.csc_matrix(
+                    (d.data, d.shape[0] - 1 - d.indices, d.indptr),
+                    shape=d.shape)
             assert not d.has_sorted_indices
             data[idx] = d
     if explicit_zero:
diff --git a/theano/tensor/tests/test_basic.py b/theano/tensor/tests/test_basic.py
--- a/theano/tensor/tests/test_basic.py
+++ b/theano/tensor/tests/test_basic.py
@@ -2835,11 +2835,7 @@ def test_cast_from_complex_to_real_raises_error(self):
               correct6=(randint(5, 5).astype('int64'),
                         np.array(-1, dtype='int64'),
                         np.array(1, dtype='int64')),
-              # min > max. messed up behaviour, but
-              # should be same as NumPy's
-              correct7=((5 * rand(5, 5)).astype('float64'),
-                        np.array(1, dtype='float64'),
-                        np.array(-1, dtype='float64')),
+              # min > max case moved below as numpy has changed
               correct8=(randint(0, 5).astype('uint8'),
                         np.array(2, dtype='uint8'),
                         np.array(4, dtype='uint8')),
@@ -2850,6 +2846,18 @@ def test_cast_from_complex_to_real_raises_error(self):
     )
 
 
+# min > max case - numpy.clip has changed but we haven't
+# https://github.com/Theano/Theano/issues/6715
+BackwardsClipTester = makeTester(
+    name='BackwardsClipTester',
+    op=clip,
+    expected=lambda x, y, z: np.where(x < y, y, np.minimum(x, z)),
+    good=dict(correct7=((5 * rand(5, 5)).astype('float64'),
+                        np.array(1, dtype='float64'),
+                        np.array(-1, dtype='float64')),)
+    )
+
+
 class T_Clip(unittest.TestCase):
     def test_complex_value(self):
         for dtype in ['complex64', 'complex128']:
diff --git a/theano/tensor/tests/test_raw_random.py b/theano/tensor/tests/test_raw_random.py
--- a/theano/tensor/tests/test_raw_random.py
+++ b/theano/tensor/tests/test_raw_random.py
@@ -678,10 +678,10 @@ def as_floatX(thing):
         numpy_val1c = as_floatX(numpy_rng.uniform(low=[-4.], high=[-1]))
         assert np.all(val0c == numpy_val0c)
         assert np.all(val1c == numpy_val1c)
-        self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1, 0], [1])
+        #self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1, 0], [1])
         self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1, 0], [1, 2])
         self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1, 0], [2, 1])
-        self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1], [1])
+        #self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1], [1])
         # TODO: do we want that?
         #self.assertRaises(ValueError, fc, post1c, [-4., -2], [-1], [2])
 
diff --git a/theano/tensor/tests/test_shared_randomstreams.py b/theano/tensor/tests/test_shared_randomstreams.py
--- a/theano/tensor/tests/test_shared_randomstreams.py
+++ b/theano/tensor/tests/test_shared_randomstreams.py
@@ -466,10 +466,10 @@ def test_vector_arguments(self):
         numpy_val1c = numpy_rng.uniform(low=[-4.], high=[-1])
         assert np.all(val0c == numpy_val0c)
         assert np.all(val1c == numpy_val1c)
-        self.assertRaises(ValueError, fc, [-4., -2], [-1, 0], [1])
+        #self.assertRaises(ValueError, fc, [-4., -2], [-1, 0], [1])
         self.assertRaises(ValueError, fc, [-4., -2], [-1, 0], [1, 2])
         self.assertRaises(ValueError, fc, [-4., -2], [-1, 0], [2, 1])
-        self.assertRaises(ValueError, fc, [-4., -2], [-1], [1])
+        #self.assertRaises(ValueError, fc, [-4., -2], [-1], [1])
         # TODO: do we want that?
         #self.assertRaises(ValueError, fc, [-4., -2], [-1], [2])