Repository: numba/numba

Issue:
Trouble using `.ravel("A")` on contiguous CUDA DeviceNDArrays
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I am using the latest released version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

When trying to use `.ravel("A")` on a device array that is C-contiguous (though have seen the same with F-contiguous device arrays), it complains that the array is not contiguous. However `"A"` (at least for NumPy) means the array could be C-contiguous or F-contiguous and still be raveled without a copy. Would figure this would be the case for Numba as well, but it appears not. Please let me know if I'm just doing something silly though.

```python
In [1]: import numba.cuda                                                       

In [2]: a = numba.cuda.device_array((10, 12))                                   

In [3]: a.ravel("C")                                                            
Out[3]: <numba.cuda.cudadrv.devicearray.DeviceNDArray at 0x7f4a88394710>

In [4]: a.ravel("A")                                                            
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-4-65fd138a7dd9> in <module>
----> 1 a.ravel("A")

~/miniconda/envs/cupy37/lib/python3.7/site-packages/numba/cuda/cudadrv/devicearray.py in ravel(self, order, stream)
    483         stream = self._default_stream(stream)
    484         cls = type(self)
--> 485         newarr, extents = self._dummy.ravel(order=order)
    486 
    487         if extents == [self._dummy.extent]:

~/miniconda/envs/cupy37/lib/python3.7/site-packages/numba/dummyarray.py in ravel(self, order)
    358 
    359         else:
--> 360             raise NotImplementedError("ravel on non-contiguous array")
    361 
    362 

NotImplementedError: ravel on non-contiguous array
```


Discussion:
FWIW using `.reshape()` seems to works without issues on either C or F contiguous arrays. Perhaps it would be worthwhile to reimplement `.ravel()` in terms of `.reshape()`?

```python
a.reshape((a.size,), order="A")
```

Resolving patch:
diff --git a/numba/dummyarray.py b/numba/dummyarray.py
--- a/numba/dummyarray.py
+++ b/numba/dummyarray.py
@@ -350,8 +350,8 @@ def ravel(self, order='C'):
         if self.ndim <= 1:
             return self
 
-        elif (order == 'C' and self.is_c_contig or
-                          order == 'F' and self.is_f_contig):
+        elif (order in 'CA' and self.is_c_contig or
+                          order in 'FA' and self.is_f_contig):
             newshape = (self.size,)
             newstrides = (self.itemsize,)
             arr = self.from_desc(self.extent.begin, newshape, newstrides,


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_array_attr.py b/numba/cuda/tests/cudadrv/test_array_attr.py
--- a/numba/cuda/tests/cudadrv/test_array_attr.py
+++ b/numba/cuda/tests/cudadrv/test_array_attr.py
@@ -1,9 +1,10 @@
 import numpy as np
 from numba import cuda
 from numba.cuda.testing import unittest, SerialMixin
+from numba.tests.support import TestCase
 
 
-class TestArrayAttr(SerialMixin, unittest.TestCase):
+class TestArrayAttr(SerialMixin, TestCase):
     def test_contigous_2d(self):
         ary = np.arange(10)
         cary = ary.reshape(2, 5)
@@ -43,22 +44,33 @@ def test_contigous_4d(self):
     def test_ravel_c(self):
         ary = np.arange(60)
         reshaped = ary.reshape(2, 5, 2, 3)
+
         expect = reshaped.ravel(order='C')
         dary = cuda.to_device(reshaped)
         dflat = dary.ravel()
         flat = dflat.copy_to_host()
-        self.assertTrue(flat.ndim == 1)
-        self.assertTrue(np.all(expect == flat))
+        self.assertEqual(flat.ndim, 1)
+        self.assertPreciseEqual(expect, flat)
+
+        # explicit order kwarg
+        for order in 'CA':
+            expect = reshaped.ravel(order=order)
+            dary = cuda.to_device(reshaped)
+            dflat = dary.ravel(order=order)
+            flat = dflat.copy_to_host()
+            self.assertEqual(flat.ndim, 1)
+            self.assertPreciseEqual(expect, flat)
 
     def test_ravel_f(self):
         ary = np.arange(60)
         reshaped = np.asfortranarray(ary.reshape(2, 5, 2, 3))
-        expect = reshaped.ravel(order='F')
-        dary = cuda.to_device(reshaped)
-        dflat = dary.ravel(order='F')
-        flat = dflat.copy_to_host()
-        self.assertTrue(flat.ndim == 1)
-        self.assertTrue(np.all(expect == flat))
+        for order in 'FA':
+            expect = reshaped.ravel(order=order)
+            dary = cuda.to_device(reshaped)
+            dflat = dary.ravel(order=order)
+            flat = dflat.copy_to_host()
+            self.assertEqual(flat.ndim, 1)
+            self.assertPreciseEqual(expect, flat)
 
     def test_reshape_c(self):
         ary = np.arange(10)
@@ -66,7 +78,7 @@ def test_reshape_c(self):
         dary = cuda.to_device(ary)
         dary_reshaped = dary.reshape(2, 5)
         got = dary_reshaped.copy_to_host()
-        self.assertTrue(np.all(expect == got))
+        self.assertPreciseEqual(expect, got)
 
     def test_reshape_f(self):
         ary = np.arange(10)
@@ -74,7 +86,7 @@ def test_reshape_f(self):
         dary = cuda.to_device(ary)
         dary_reshaped = dary.reshape(2, 5, order='F')
         got = dary_reshaped.copy_to_host()
-        self.assertTrue(np.all(expect == got))
+        self.assertPreciseEqual(expect, got)
 
 
 if __name__ == '__main__':