Repository: chainer/chainer

Issue:
0-dim `Variable` fails to `GetItem.backward` for boolean mask
* Conditions
  - numpy: 1.12.1
* Code to reproduce
```
>>> import chainer; import numpy as np
>>> x = chainer.Variable(np.ones((), np.float32))
>>> y = x[True]
>>> y
variable([ 1.])
>>> y.backward()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/tos/GitHub/chainer/chainer/variable.py", line 963, in backward
    self._backward_main(retain_grad, loss_scale)
  File "/Users/tos/GitHub/chainer/chainer/variable.py", line 1092, in _backward_main
    target_input_indexes, out_grad, in_grad)
  File "/Users/tos/GitHub/chainer/chainer/function_node.py", line 545, in backward_accumulate
    gxs = self.backward(target_input_indexes, grad_outputs)
  File "/Users/tos/GitHub/chainer/chainer/functions/array/get_item.py", line 41, in backward
    self.slices, self.inputs[0].shape, self.inputs[0].dtype).apply(gy)
  File "/Users/tos/GitHub/chainer/chainer/function_node.py", line 257, in apply
    outputs = self.forward(in_data)
  File "/Users/tos/GitHub/chainer/chainer/functions/array/get_item.py", line 55, in forward
    numpy.add.at(gx, self.slices, inputs[0])
IndexError: in the future, 0-d boolean arrays will be interpreted as a valid boolean index
```


Discussion:

Resolving patch:
diff --git a/chainer/functions/array/get_item.py b/chainer/functions/array/get_item.py
--- a/chainer/functions/array/get_item.py
+++ b/chainer/functions/array/get_item.py
@@ -8,6 +8,10 @@
 from chainer import variable
 
 
+_numpy_supports_0d_bool_index = \
+    numpy.lib.NumpyVersion(numpy.__version__) >= '1.13.0'
+
+
 class GetItem(function_node.FunctionNode):
 
     """Function that slices array and extract elements."""
@@ -49,10 +53,35 @@ def __init__(self, slices, in_shape, in_dtype):
         self._in_dtype = in_dtype
 
     def forward(self, inputs):
+        gy, = inputs
         xp = cuda.get_array_module(*inputs)
         gx = xp.zeros(self._in_shape, self._in_dtype)
         if xp is numpy:
-            numpy.add.at(gx, self.slices, inputs[0])
+            try:
+                numpy.add.at(gx, self.slices, gy)
+            except IndexError:
+                done = False
+                # In numpy<1.13, 0-dim boolean index is not supported in
+                # numpy.add.at and it's supported for 0-dim arr in
+                # arr.__getitem__.
+                if not _numpy_supports_0d_bool_index and len(self.slices) == 1:
+                    idx = numpy.asanyarray(self.slices[0])
+                    if idx.dtype == numpy.dtype(bool):
+                        # Convert the array and the mask to 1-dim.
+                        # numpy.add.at with them is supported in older numpy.
+                        numpy.add.at(gx[None], idx[None], gy)
+                        done = True
+
+                if not done:
+                    msg = '''
+GetItem does not support backward for this slices. The slices argument is not
+supported by numpy.add.at, while it is supported by numpy.ndarray.__getitem__.
+
+Please report this error to the issue tracker with the stack trace,
+the information of your environment, and your script:
+https://github.com/chainer/chainer/issues/new.
+'''
+                    raise IndexError(msg)
         else:
             gx.scatter_add(self.slices, inputs[0])
         return gx,


Test patch:
diff --git a/tests/chainer_tests/functions_tests/array_tests/test_get_item.py b/tests/chainer_tests/functions_tests/array_tests/test_get_item.py
--- a/tests/chainer_tests/functions_tests/array_tests/test_get_item.py
+++ b/tests/chainer_tests/functions_tests/array_tests/test_get_item.py
@@ -124,13 +124,19 @@ def test_double_backward_gpu(self):
     {'slices': numpy.array([False, False, False, False]),
         'sliced_shape': (0, 3, 2)},
     {'slices': (3, 2, Ellipsis, 1),
-     'sliced_shape': ()},
+        'sliced_shape': ()},
+    {'slices': (numpy.array(False)),
+        'input_shape': (), 'sliced_shape': (0,)},
+    {'slices': (numpy.array(True)),
+        'input_shape': (), 'sliced_shape': (1,)},
 )
 class TestGetItemAdvanced(unittest.TestCase):
 
+    input_shape = (4, 3, 2)
+
     def setUp(self):
         self.x_data = numpy.random.uniform(
-            -1, 1, (4, 3, 2)).astype(numpy.float32)
+            -1, 1, self.input_shape).astype(numpy.float32)
         self.gy_data = numpy.random.uniform(
             -1, 1, self.sliced_shape).astype(numpy.float32)