Repository: chainer/chainer

Issue:
"Report to the issue tracker" error on backward with broadcasted var-const operators
It seems binary operators taking a pair of a variable and a constant (`***Constant` Functions in basic_math.py) do not correctly handle the broadcasting of the variable argument. When the variable is broadcasted, it raises an error on backward with the "report this error to the issue tracker" message.

Here is a minimal code to reproduce this (on the `__add__` operator):

``` python
import chainer, numpy
x = chainer.Variable(numpy.array(1, dtype='f'))
y = numpy.array([1], dtype='f')
(x + y).backward()
```

Note that broadcasting of the constant argument is (and should be) allowed for simple coding (e.g., to allow multiplication to any variables by constant scalars). We may have to forbid an implicit variable broadcasting, or correctly support it.



Discussion:

Resolving patch:
diff --git a/chainer/functions/math/basic_math.py b/chainer/functions/math/basic_math.py
--- a/chainer/functions/math/basic_math.py
+++ b/chainer/functions/math/basic_math.py
@@ -33,6 +33,18 @@ def _check_constant_type(value):
             'value must be scalar, ndarray, or Variable')
 
 
+def _preprocess_const(x, value):
+    xp = cuda.get_array_module(x)
+    if not numpy.isscalar(value) and cuda.get_array_module(value) != xp:
+        # TODO(unno): We can transfer arrays automatically
+        raise TypeError('Cannot mix cupy.ndarray and numpy.ndarray')
+
+    b = xp.broadcast(x, value)
+    if b.shape != x.shape:
+        raise ValueError('Failed to broadcast arrays')
+    return utils.force_type(x.dtype, value)
+
+
 class Neg(function.Function):
 
     @property
@@ -115,7 +127,7 @@ def check_type_forward(self, in_types):
         type_check.expect(in_types.size() == 1)
 
     def forward(self, x):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(x[0] + value),
 
     def backward(self, x, gy):
@@ -169,7 +181,7 @@ def check_type_forward(self, in_types):
         type_check.expect(in_types.size() == 1)
 
     def forward(self, x):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(value - x[0]),
 
     def backward(self, x, gy):
@@ -217,11 +229,11 @@ def check_type_forward(self, in_types):
         type_check.expect(in_types.size() == 1)
 
     def forward(self, x):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(value * x[0]),
 
     def backward(self, x, gy):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(value * gy[0]),
 
 
@@ -284,15 +296,15 @@ def check_type_forward(self, in_types):
         type_check.expect(in_types[0].dtype.kind == 'f')
 
     def forward(self, x):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(value / x[0]),
 
     def backward_cpu(self, x, gy):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(-value * gy[0] / (x[0] ** 2)),
 
     def backward_gpu(self, x, gy):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         gx = cuda.elementwise('T x, T gy, T value', 'T gx',
                               'gx = -value * gy / (x * x)',
                               'div_from_const_bwd')(x[0], gy[0], value)
@@ -356,16 +368,16 @@ def check_type_forward(self, in_types):
         type_check.expect(in_types[0].dtype.kind == 'f')
 
     def forward(self, x):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array((x[0] ** value).astype(x[0].dtype)),
 
     def backward_cpu(self, x, gy):
-        val_1 = utils.force_type(x[0].dtype, self.value - 1)
+        val_1 = _preprocess_const(x[0], self.value - 1)
         gx = utils.force_type(x[0].dtype, self.value) * (x[0] ** val_1) * gy[0]
         return utils.force_array(gx),
 
     def backward_gpu(self, x, gy):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         gx = cuda.elementwise(
             'T x, T gy, T value', 'T gx',
             'gx = value * pow(x, value - 1) * gy',
@@ -394,17 +406,17 @@ def check_type_forward(self, in_types):
         type_check.expect(in_types[0].dtype.kind == 'f')
 
     def forward(self, x):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         self.y = utils.force_array(value ** x[0])
         return self.y,
 
     def backward_cpu(self, x, gy):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         return utils.force_array(
             numpy.log(value, dtype=x[0].dtype) * self.y * gy[0]),
 
     def backward_gpu(self, x, gy):
-        value = utils.force_type(x[0].dtype, self.value)
+        value = _preprocess_const(x[0], self.value)
         gx = cuda.elementwise(
             'T x, T gy, T value', 'T gx',
             'gx = log(value) * pow(value, x) * gy',


Test patch:
diff --git a/tests/chainer_tests/functions_tests/math_tests/test_basic_math.py b/tests/chainer_tests/functions_tests/math_tests/test_basic_math.py
--- a/tests/chainer_tests/functions_tests/math_tests/test_basic_math.py
+++ b/tests/chainer_tests/functions_tests/math_tests/test_basic_math.py
@@ -1,6 +1,7 @@
 import unittest
 
 import numpy
+import six
 
 import chainer
 from chainer import basic_math
@@ -284,6 +285,11 @@ def _test_constant_array_gpu(self, func, exception=TypeError):
                 func, x_data, cuda.to_gpu(
                     numpy.array([3.0, 4.0, 5.0], self.dtype)))
 
+        with six.assertRaisesRegex(self, ValueError, 'broadcast'):
+            self._test_constant_array_gpu_one(
+                func, x_data, cuda.to_gpu(
+                    numpy.array([[3.0, 4.0], [5.0, 6.0]], self.dtype)))
+
     def test_add_constant(self):
         self._test_constant(lambda x, y: x + y)