Repository: chainer/chainer

Issue:
cuda.to_gpu does not work for numpy scalar types
`cuda.to_gpu` only accepts `numpy.ndarray` or `cupy.ndarray`.
However, as NumPy seems to use `numpy.ndarray` and scalar types (such as `numpy.float32`) interchangeably, it would be preferrable if it accepts scalar types as well.

### Code:
```
import chainer, numpy

a = numpy.array(3, numpy.float32)
print(type(a), a.shape, a.dtype)

b = a + a
print(type(b), b.shape, b.dtype)

x = chainer.cuda.to_gpu(b)
```

### Result:
```
<class 'numpy.ndarray'> () float32
<class 'numpy.float32'> () float32
Traceback (most recent call last):
  File "test-numpy.py", line 9, in <module>
    x = chainer.cuda.to_gpu(b)
  File "/data/work/w/repos/chainer/chainer/cuda.py", line 259, in to_gpu
    '\nActual type: {0}.'.format(type(array)))
TypeError: The array sent to gpu must be numpy.ndarray or cupy.ndarray.
Actual type: <class 'numpy.float32'>.
```



Discussion:

Resolving patch:
diff --git a/chainer/cuda.py b/chainer/cuda.py
--- a/chainer/cuda.py
+++ b/chainer/cuda.py
@@ -253,9 +253,12 @@ def to_gpu(array, device=None, stream=None):
             'Please remove it.', DeprecationWarning)
 
     check_cuda_available()
+    if isinstance(array, (numpy.number, numpy.bool_)):
+        array = numpy.asarray(array)
     if not isinstance(array, (cupy.ndarray, numpy.ndarray)):
         raise TypeError(
-            'The array sent to gpu must be numpy.ndarray or cupy.ndarray.'
+            'The array sent to gpu must be numpy.ndarray or cupy.ndarray, '
+            'or a NumPy scalar.'
             '\nActual type: {0}.'.format(type(array)))
     with _get_device(device):
         array_dev = get_device_from_array(array)
@@ -312,11 +315,14 @@ def to_cpu(array, stream=None):
         check_cuda_available()
         with get_device_from_array(array):
             return array.get(stream)
+    elif isinstance(array, (numpy.number, numpy.bool_)):
+        return numpy.asarray(array)
     elif isinstance(array, numpy.ndarray):
         return array
     else:
         raise TypeError(
-            'The array sent to cpu must be numpy.ndarray or cupy.ndarray.'
+            'The array sent to cpu must be numpy.ndarray or cupy.ndarray, '
+            'or a NumPy scalar.'
             '\nActual type: {0}.'.format(type(array)))
 
 


Test patch:
diff --git a/tests/chainer_tests/test_cuda.py b/tests/chainer_tests/test_cuda.py
--- a/tests/chainer_tests/test_cuda.py
+++ b/tests/chainer_tests/test_cuda.py
@@ -188,6 +188,35 @@ def test_variable(self):
             cuda.to_cpu(x)
 
 
+@testing.parameterize(*testing.product({
+    'dtype': [
+        numpy.bool_, numpy.uint8, numpy.int8, numpy.uint16,
+        numpy.int16, numpy.uint32, numpy.int32, numpy.uint64,
+        numpy.int64, numpy.float16, numpy.float32, numpy.float64,
+        numpy.complex_],
+}))
+class TestToCPUScalar(unittest.TestCase):
+
+    def test_numpy_scalar(self):
+        dtype = self.dtype
+        if dtype is numpy.bool_:
+            x = dtype(True)
+        elif issubclass(dtype, numpy.complex_):
+            x = dtype(3.2 - 2.4j)
+        elif issubclass(dtype, numpy.integer):
+            x = dtype(3)
+        elif issubclass(dtype, numpy.floating):
+            x = dtype(3.2)
+        else:
+            assert False
+
+        y = cuda.to_cpu(x)
+        assert isinstance(y, numpy.ndarray)
+        assert y.shape == ()
+        assert y.dtype == dtype
+        assert y == x
+
+
 class TestWorkspace(unittest.TestCase):
 
     def setUp(self):
@@ -303,4 +332,34 @@ def test_variable_gpu(self):
             cuda.to_gpu(x)
 
 
+@testing.parameterize(*testing.product({
+    'dtype': [
+        numpy.bool_, numpy.uint8, numpy.int8, numpy.uint16,
+        numpy.int16, numpy.uint32, numpy.int32, numpy.uint64,
+        numpy.int64, numpy.float16, numpy.float32, numpy.float64,
+        numpy.complex_],
+}))
+class TestToGPUScalar(unittest.TestCase):
+
+    @attr.gpu
+    def test_numpy_scalar(self):
+        dtype = self.dtype
+        if dtype is numpy.bool_:
+            x = dtype(True)
+        elif issubclass(dtype, numpy.complex_):
+            x = dtype(3.2 - 2.4j)
+        elif issubclass(dtype, numpy.integer):
+            x = dtype(3)
+        elif issubclass(dtype, numpy.floating):
+            x = dtype(3.2)
+        else:
+            assert False
+
+        y = cuda.to_gpu(x)
+        assert isinstance(y, cuda.ndarray)
+        assert y.shape == ()
+        assert y.dtype == dtype
+        assert y == x
+
+
 testing.run_module(__name__, __file__)