Repository: numba/numba

Issue:
Calling `.ravel()` on a `DeviceNDArray` errors
<!--

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!

-->

<hr>

When trying to calling `.ravel()` on a `DeviceNDArray`, it appears this now fails.


```python
In [1]: import numpy                                                            

In [2]: import numba.cuda                                                       

In [3]: x_np = numpy.arange(10, dtype="u1")                                     

In [4]: x = numba.cuda.to_device(x_np)                                          

In [5]: x.ravel(order="C")                                                      
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-03768d2d4035> in <module>
----> 1 x.ravel(order="C")

/datasets/jkirkham/miniconda/envs/numba/lib/python3.8/site-packages/numba/cuda/cudadrv/devicearray.py in ravel(self, order, stream)
    505         stream = self._default_stream(stream)
    506         cls = type(self)
--> 507         newarr, extents = self._dummy.ravel(order=order)
    508 
    509         if extents == [self._dummy.extent]:

ValueError: too many values to unpack (expected 2)

In [6]: x.ravel()                                                               
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-6708f41f7dd0> in <module>
----> 1 x.ravel()

/datasets/jkirkham/miniconda/envs/numba/lib/python3.8/site-packages/numba/cuda/cudadrv/devicearray.py in ravel(self, order, stream)
    505         stream = self._default_stream(stream)
    506         cls = type(self)
--> 507         newarr, extents = self._dummy.ravel(order=order)
    508 
    509         if extents == [self._dummy.extent]:

ValueError: too many values to unpack (expected 2)
```


Discussion:
It looks like this problem as existed for a long time. `.ravel()` is failing on 1D inputs.
Ah! Thanks for the clarification. I must not have tried with 1-D data before. WDYT about special casing `.ravel()` on 1-D data to make it a no-op?

Resolving patch:
diff --git a/numba/cuda/cudadrv/devicearray.py b/numba/cuda/cudadrv/devicearray.py
--- a/numba/cuda/cudadrv/devicearray.py
+++ b/numba/cuda/cudadrv/devicearray.py
@@ -607,8 +607,9 @@ def reshape(self, *newshape, **kws):
 
     def ravel(self, order='C', stream=0):
         '''
-        Flatten the array without changing its contents, similar to
-        :meth:`numpy.ndarray.ravel`.
+        Flattens a contiguous array without changing its contents, similar to
+        :meth:`numpy.ndarray.ravel`. If the array is not contiguous, raises an
+        exception.
         '''
         stream = self._default_stream(stream)
         cls = type(self)
diff --git a/numba/misc/dummyarray.py b/numba/misc/dummyarray.py
--- a/numba/misc/dummyarray.py
+++ b/numba/misc/dummyarray.py
@@ -382,11 +382,8 @@ def ravel(self, order='C'):
         if order not in 'CFA':
             raise ValueError('order not C|F|A')
 
-        if self.ndim <= 1:
-            return self
-
-        elif (order in 'CA' and self.is_c_contig or
-                          order in 'FA' and self.is_f_contig):
+        if (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, CUDATestCase
+from numba.cuda.testing import unittest, CUDATestCase, skip_on_cudasim
 
 
 class TestArrayAttr(CUDATestCase):
+
     def test_contigous_2d(self):
         ary = np.arange(10)
         cary = ary.reshape(2, 5)
@@ -40,6 +41,30 @@ def test_contigous_4d(self):
         self.assertTrue(not dcary.is_f_contiguous())
         self.assertTrue(dfary.is_f_contiguous())
 
+    def test_ravel_1d(self):
+        ary = np.arange(60)
+        dary = cuda.to_device(ary)
+        for order in 'CFA':
+            expect = ary.ravel(order=order)
+            dflat = dary.ravel(order=order)
+            flat = dflat.copy_to_host()
+            self.assertTrue(dary is not dflat)  # ravel returns new array
+            self.assertEqual(flat.ndim, 1)
+            self.assertPreciseEqual(expect, flat)
+
+    @skip_on_cudasim('CUDA Array Interface is not supported in the simulator')
+    def test_ravel_stride_1d(self):
+        ary = np.arange(60)
+        dary = cuda.to_device(ary)
+        # No-copy stride device array
+        darystride = dary[::2]
+        dary_data = dary.__cuda_array_interface__['data'][0]
+        ddarystride_data = darystride.__cuda_array_interface__['data'][0]
+        self.assertEqual(dary_data, ddarystride_data)
+        # Fail on ravel on non-contiguous array
+        with self.assertRaises(NotImplementedError):
+            darystride.ravel()
+
     def test_ravel_c(self):
         ary = np.arange(60)
         reshaped = ary.reshape(2, 5, 2, 3)
@@ -48,6 +73,7 @@ def test_ravel_c(self):
         dary = cuda.to_device(reshaped)
         dflat = dary.ravel()
         flat = dflat.copy_to_host()
+        self.assertTrue(dary is not dflat)
         self.assertEqual(flat.ndim, 1)
         self.assertPreciseEqual(expect, flat)
 
@@ -57,9 +83,23 @@ def test_ravel_c(self):
             dary = cuda.to_device(reshaped)
             dflat = dary.ravel(order=order)
             flat = dflat.copy_to_host()
+            self.assertTrue(dary is not dflat)
             self.assertEqual(flat.ndim, 1)
             self.assertPreciseEqual(expect, flat)
 
+    @skip_on_cudasim('CUDA Array Interface is not supported in the simulator')
+    def test_ravel_stride_c(self):
+        ary = np.arange(60)
+        reshaped = ary.reshape(2, 5, 2, 3)
+
+        dary = cuda.to_device(reshaped)
+        darystride = dary[::2, ::2, ::2, ::2]
+        dary_data = dary.__cuda_array_interface__['data'][0]
+        ddarystride_data = darystride.__cuda_array_interface__['data'][0]
+        self.assertEqual(dary_data, ddarystride_data)
+        with self.assertRaises(NotImplementedError):
+            darystride.ravel()
+
     def test_ravel_f(self):
         ary = np.arange(60)
         reshaped = np.asfortranarray(ary.reshape(2, 5, 2, 3))
@@ -68,9 +108,22 @@ def test_ravel_f(self):
             dary = cuda.to_device(reshaped)
             dflat = dary.ravel(order=order)
             flat = dflat.copy_to_host()
+            self.assertTrue(dary is not dflat)
             self.assertEqual(flat.ndim, 1)
             self.assertPreciseEqual(expect, flat)
 
+    @skip_on_cudasim('CUDA Array Interface is not supported in the simulator')
+    def test_ravel_stride_f(self):
+        ary = np.arange(60)
+        reshaped = np.asfortranarray(ary.reshape(2, 5, 2, 3))
+        dary = cuda.to_device(reshaped)
+        darystride = dary[::2, ::2, ::2, ::2]
+        dary_data = dary.__cuda_array_interface__['data'][0]
+        ddarystride_data = darystride.__cuda_array_interface__['data'][0]
+        self.assertEqual(dary_data, ddarystride_data)
+        with self.assertRaises(NotImplementedError):
+            darystride.ravel()
+
     def test_reshape_c(self):
         ary = np.arange(10)
         expect = ary.reshape(2, 5)