Repository: chainer/chainer

Issue:
Behavior of 0-dimensional array
Behavior of 0-dimensional array is strange.

```
>>> x = numpy.array(1)
>>> x
array(1)
>>> x + x
2
```

So, infix operators of `Variable` also have the same problem.

```
>>> x = chainer.Variable(numpy.array(1))
>>> x.data
array(1)
>>> y = x + x
>>> y.data
2
```

We have two choices:
- Forbid 0-dimensional array. Instead use 1-dimension array which has only one element.
- Check type of an array in `Function`s. When an array is converted to a scalar, we need to re-convert the value to 0-dimensional array.

This issue is related to #100 



Discussion:
We are assuming that `Variable.data` is `ndarray` or `GPUArray`, but this problem breaks this assumption. (related to #101)


Resolving patch:
diff --git a/chainer/functions/basic_math.py b/chainer/functions/basic_math.py
--- a/chainer/functions/basic_math.py
+++ b/chainer/functions/basic_math.py
@@ -5,6 +5,7 @@
 
 from chainer import cuda
 from chainer import function
+from chainer import utils
 from chainer import variable
 
 
@@ -12,14 +13,13 @@
 # Arithmetic
 # ------------------------------------------------------------------------------
 
-
 class Neg(function.Function):
 
     def forward(self, x):
-        return -x[0],
+        return utils.force_array(-x[0]),
 
     def backward(self, x, gy):
-        return -gy[0],
+        return utils.force_array(-gy[0]),
 
 
 def neg(x):  # -x
@@ -29,7 +29,8 @@ def neg(x):  # -x
 class Add(function.Function):
 
     def forward(self, x):
-        return x[0] + x[1],
+        y = utils.force_array(x[0] + x[1])
+        return y,
 
     def backward(self, x, gy):
         return gy[0], gy[0]
@@ -41,7 +42,7 @@ def __init__(self, value):
         self.value = value
 
     def forward(self, x):
-        return x[0] + self.value,
+        return utils.force_array(x[0] + self.value),
 
     def backward(self, x, gy):
         return gy[0],
@@ -56,10 +57,10 @@ def add(lhs, rhs):  # lhs + rhs
 class Sub(function.Function):
 
     def forward(self, x):
-        return x[0] - x[1],
+        return utils.force_array(x[0] - x[1]),
 
     def backward(self, x, gy):
-        return gy[0], -gy[0]
+        return gy[0], utils.force_array(-gy[0])
 
 
 def sub(lhs, rhs):  # lhs - rhs
@@ -74,10 +75,10 @@ def __init__(self, value):
         self.value = value
 
     def forward(self, x):
-        return self.value - x[0],
+        return utils.force_array(self.value - x[0]),
 
     def backward(self, x, gy):
-        return -gy[0],
+        return utils.force_array(-gy[0]),
 
 
 def rsub(lhs, rhs):  # rhs - lhs
@@ -89,10 +90,10 @@ def rsub(lhs, rhs):  # rhs - lhs
 class Mul(function.Function):
 
     def forward(self, x):
-        return x[0] * x[1],
+        return utils.force_array(x[0] * x[1]),
 
     def backward_cpu(self, x, gy):
-        return gy[0] * x[1], gy[0] * x[0]
+        return utils.force_array(gy[0] * x[1]), utils.force_array(gy[0] * x[0])
 
     def backward_gpu(self, x, gy):
         gx0 = cuda.empty_like(x[0])
@@ -114,10 +115,10 @@ def __init__(self, value):
         self.value = value
 
     def forward(self, x):
-        return self.value * x[0],
+        return utils.force_array(self.value * x[0]),
 
     def backward(self, x, gy):
-        return self.value * gy[0],
+        return utils.force_array(self.value * gy[0]),
 
 
 def mul(lhs, rhs):  # lhs * rhs
@@ -129,11 +130,11 @@ def mul(lhs, rhs):  # lhs * rhs
 class Div(function.Function):
 
     def forward(self, x):
-        return x[0] / x[1],
+        return utils.force_array(x[0] / x[1]),
 
     def backward_cpu(self, x, gy):
-        gx0 = gy[0] / x[1]
-        return gx0, -gx0 * x[0] / x[1]
+        gx0 = utils.force_array(gy[0] / x[1])
+        return gx0, utils.force_array(-gx0 * x[0] / x[1])
 
     def backward_gpu(self, x, gy):
         gx0 = cuda.empty_like(x[0])
@@ -161,10 +162,10 @@ def __init__(self, value):
         self.value = value
 
     def forward(self, x):
-        return self.value / x[0],
+        return utils.force_array(self.value / x[0]),
 
     def backward_cpu(self, x, gy):
-        return -self.value * gy[0] / (x[0] ** 2),
+        return utils.force_array(-self.value * gy[0] / (x[0] ** 2)),
 
     def backward_gpu(self, x, gy):
         gx = cuda.empty_like(x[0])
@@ -196,15 +197,15 @@ def rdiv(lhs, rhs):  # rhs / lhs
 class PowVarVar(function.Function):
 
     def forward_cpu(self, x):
-        self.y = x[0] ** x[1]
+        self.y = utils.force_array(x[0] ** x[1])
         return self.y,
 
     def forward_gpu(self, x):
         return x[0] ** x[1],
 
     def backward_cpu(self, x, gy):
-        gx0 = x[1] * (x[0] ** (x[1] - 1)) * gy[0]
-        gx1 = numpy.log(x[0]) * self.y * gy[0]
+        gx0 = utils.force_array(x[1] * (x[0] ** (x[1] - 1)) * gy[0])
+        gx1 = utils.force_array(numpy.log(x[0]) * self.y * gy[0])
         return gx0, gx1
 
     def backward_gpu(self, x, gy):
@@ -227,10 +228,11 @@ def __init__(self, value):
         self.value = value
 
     def forward(self, x):
-        return x[0] ** self.value,
+        return utils.force_array(x[0] ** self.value),
 
     def backward_cpu(self, x, gy):
-        return self.value * (x[0] ** (self.value - 1)) * gy[0],
+        gx = self.value * (x[0] ** (self.value - 1)) * gy[0]
+        return utils.force_array(gx),
 
     def backward_gpu(self, x, gy):
         gx = cuda.empty_like(x[0])
@@ -265,7 +267,7 @@ def __init__(self, value):
         self.value = value
 
     def forward_cpu(self, x):
-        self.y = self.value ** x[0]
+        self.y = utils.force_array(self.value ** x[0])
         return self.y,
 
     def forward_gpu(self, x):
@@ -281,7 +283,7 @@ def forward_gpu(self, x):
         return y,
 
     def backward_cpu(self, x, gy):
-        return numpy.log(self.value) * self.y * gy[0],
+        return utils.force_array(numpy.log(self.value) * self.y * gy[0]),
 
     def backward_gpu(self, x, gy):
         gx = cuda.empty_like(x[0])
@@ -334,7 +336,7 @@ def install_variable_arithmetics():
 class Exp(function.Function):
 
     def forward_cpu(self, x):
-        self.y = numpy.exp(x[0])
+        self.y = utils.force_array(numpy.exp(x[0]))
         return self.y,
 
     def forward_gpu(self, x):
@@ -342,7 +344,7 @@ def forward_gpu(self, x):
         return self.y,
 
     def backward(self, x, gy):
-        return self.y * gy[0],
+        return utils.force_array(self.y * gy[0]),
 
 
 def exp(x):
@@ -353,13 +355,13 @@ def exp(x):
 class Log(function.Function):
 
     def forward_cpu(self, x):
-        return numpy.log(x[0]),
+        return utils.force_array(numpy.log(x[0])),
 
     def forward_gpu(self, x):
         return cuda.cumath.log(x[0]),
 
     def backward(self, x, gy):
-        return gy[0] / x[0],
+        return utils.force_array(gy[0] / x[0]),
 
 
 def log(x):
diff --git a/chainer/utils/__init__.py b/chainer/utils/__init__.py
--- a/chainer/utils/__init__.py
+++ b/chainer/utils/__init__.py
@@ -0,0 +1,12 @@
+import numpy
+
+
+def force_array(x):
+    # numpy returns a float value (scalar) when a return value of an operator
+    # is a 0-dimension array.
+    # We need to convert such a value to a 0-dimension array because `Function`
+    # object needs to return an `numpy.ndarray`.
+    if numpy.isscalar(x):
+        return numpy.array(x, x.dtype)
+    else:
+        return x


Test patch:
diff --git a/tests/functions_tests/test_basic_math.py b/tests/functions_tests/test_basic_math.py
--- a/tests/functions_tests/test_basic_math.py
+++ b/tests/functions_tests/test_basic_math.py
@@ -13,12 +13,13 @@
     cuda.init()
 
 
-class TestBinaryOp(unittest.TestCase):
+class BinaryOpTestBase(object):
+
+    def make_data(self):
+        raise NotImplementedError()
 
     def setUp(self):
-        self.x1 = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
-        self.x2 = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
-        self.gy = numpy.random.uniform(-1, 1, (3, 2)).astype(numpy.float32)
+        self.x1, self.x2, self.gy = self.make_data()
 
     def check_forward(self, op, x1_data, x2_data):
         x1 = chainer.Variable(x1_data)
@@ -170,12 +171,31 @@ def test_pow_backward_gpu(self):
         self.backward_gpu(lambda x, y: x ** y, atol=1e-4)
 
 
-class TestVariableConstantOp(unittest.TestCase):
+class TestBinaryOpSimple(BinaryOpTestBase, unittest.TestCase):
+
+    def make_data(self):
+        x1 = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
+        x2 = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
+        gy = numpy.random.uniform(-1, 1, (3, 2)).astype(numpy.float32)
+        return x1, x2, gy
+
+
+class TestBinaryOpZeroDimension(BinaryOpTestBase, unittest.TestCase):
+
+    def make_data(self):
+        x1 = numpy.random.uniform(.5, 1, ()).astype(numpy.float32)
+        x2 = numpy.random.uniform(.5, 1, ()).astype(numpy.float32)
+        gy = numpy.random.uniform(-1, 1, ()).astype(numpy.float32)
+        return x1, x2, gy
+
+
+class VariableConstantOpTestBase(object):
+
+    def make_date(self):
+        raise NotImplementedError()
 
     def setUp(self):
-        self.x = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
-        self.gy = numpy.random.uniform(-1, 1, (3, 2)).astype(numpy.float32)
-        self.value = .5
+        self.x, self.gy, self.value = self.make_data()
 
     def check_forward(self, op, x_data):
         x = chainer.Variable(x_data)
@@ -348,6 +368,26 @@ def test_rpow_backward_gpu(self):
         self.backward_gpu(lambda x, y: y ** x)
 
 
+class TestVariableConstantOpSimple(VariableConstantOpTestBase,
+                                   unittest.TestCase):
+
+    def make_data(self):
+        x = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
+        gy = numpy.random.uniform(-1, 1, (3, 2)).astype(numpy.float32)
+        value = .5
+        return x, gy, value
+
+
+class TestVariableConstantOpZeroDimension(VariableConstantOpTestBase,
+                                          unittest.TestCase):
+
+    def make_data(self):
+        x = numpy.random.uniform(.5, 1, ()).astype(numpy.float32)
+        gy = numpy.random.uniform(-1, 1, ()).astype(numpy.float32)
+        value = .5
+        return x, gy, value
+
+
 class TestVariableConstantArrayOp(unittest.TestCase):
 
     def setUp(self):
@@ -534,11 +574,13 @@ def test_rpow_backward_gpu(self):
         self.backward_gpu(lambda x, y: y ** x, positive=True)
 
 
-class TestUnaryFunctions(unittest.TestCase):
+class UnaryFunctionsTestBase(object):
+
+    def make_data(self):
+        raise NotImplementedError()
 
     def setUp(self):
-        self.x = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
-        self.gy = numpy.random.uniform(-1, 1, (3, 2)).astype(numpy.float32)
+        self.x, self.gy = self.make_data()
 
     def check_forward(self, op, op_np, x_data):
         x = chainer.Variable(x_data)
@@ -549,6 +591,9 @@ def check_forward(self, op, op_np, x_data):
     def forward_cpu(self, op, op_np):
         self.check_forward(op, op_np, self.x)
 
+    def test_neg_forward_cpu(self):
+        self.forward_cpu(lambda x: -x, lambda x: -x)
+
     def test_exp_forward_cpu(self):
         self.forward_cpu(F.exp, numpy.exp)
 
@@ -558,6 +603,10 @@ def test_log_forward_cpu(self):
     def forward_gpu(self, op, op_np):
         self.check_forward(op, op_np, cuda.to_gpu(self.x))
 
+    @attr.gpu
+    def test_neg_forward_gpu(self):
+        self.forward_gpu(lambda x: -x, lambda x: -x)
+
     @attr.gpu
     def test_exp_forward_gpu(self):
         self.forward_gpu(F.exp, numpy.exp)
@@ -581,6 +630,9 @@ def check_backward(self, op, x_data, y_grad):
     def backward_cpu(self, op):
         self.check_backward(op, self.x, self.gy)
 
+    def test_neg_backward_cpu(self):
+        self.backward_cpu(lambda x: -x)
+
     def test_exp_backward_cpu(self):
         self.backward_cpu(F.exp)
 
@@ -590,6 +642,10 @@ def test_log_backward_cpu(self):
     def backward_gpu(self, op):
         self.check_backward(op, cuda.to_gpu(self.x), cuda.to_gpu(self.gy))
 
+    @attr.gpu
+    def test_neg_backward_gpu(self):
+        self.backward_gpu(lambda x: -x)
+
     @attr.gpu
     def test_exp_backward_gpu(self):
         self.backward_gpu(F.exp)
@@ -597,3 +653,20 @@ def test_exp_backward_gpu(self):
     @attr.gpu
     def test_log_backward_gpu(self):
         self.backward_gpu(F.log)
+
+
+class TestUnaryFunctionsSimple(UnaryFunctionsTestBase, unittest.TestCase):
+
+    def make_data(self):
+        x = numpy.random.uniform(.5, 1, (3, 2)).astype(numpy.float32)
+        gy = numpy.random.uniform(-1, 1, (3, 2)).astype(numpy.float32)
+        return x, gy
+
+
+class TestUnaryFunctionsZeroDimension(UnaryFunctionsTestBase,
+                                      unittest.TestCase):
+
+    def make_data(self):
+        x = numpy.random.uniform(.5, 1, ()).astype(numpy.float32)
+        gy = numpy.random.uniform(-1, 1, ()).astype(numpy.float32)
+        return x, gy