Repository: chainer/chainer

Issue:
Comparison operation involving Variable
Following result is at least not intuitive.

```
>>> a = Variable([1,2,3,4])
>>> b = np.array([4,3,2,1])
>>> a > b
array([ True,  True,  True,  True], dtype=bool)
```

```
>>> a = Variable([1,2,3,4])
>>> b = Variable([4,3,2,1])
>>> a > b
False
```

We should determine operations to be defined and what are desired result of them



Discussion:

Resolving patch:
diff --git a/chainer/computational_graph.py b/chainer/computational_graph.py
--- a/chainer/computational_graph.py
+++ b/chainer/computational_graph.py
@@ -165,13 +165,25 @@ def build_computational_graph(outputs, remove_split=True):
     nodes = set()
     push_count = [0]
 
+    # This class is for object that has not been implemented __eq__
+    class HashableObject(object):
+
+        def __init__(self, v):
+            self.v = v
+
+        def __hash__(self):
+            return self.v.__hash__()
+
+        def __eq__(self, r):
+            return self.v is r.v
+
     def add_cand(cand):
         heapq.heappush(cands, (-cand.rank, push_count[0], cand))
         push_count[0] += 1
 
     for o in outputs:
         add_cand(o)
-        nodes.add(o)
+        nodes.add(HashableObject(o))
 
     while cands:
         _, _, cand = heapq.heappop(cands)
@@ -185,15 +197,15 @@ def add_cand(cand):
             if creator is not None and (creator, cand) not in seen_edges:
                 add_cand(creator)
                 seen_edges.add((creator, cand))
-                nodes.add(creator)
-                nodes.add(cand)
+                nodes.add(HashableObject(creator))
+                nodes.add(HashableObject(cand))
         elif isinstance(cand, function.Function):
             if remove_split and isinstance(cand, function.Split):
                 next_cand = creator.inputs[0]
                 add_cand(next_cand)
                 continue
             for input_ in cand.inputs:
-                if input_ != cand and (input_, cand) not in seen_edges:
+                if input_ is not cand and (input_, cand) not in seen_edges:
                     creator = input_.creator
                     if remove_split and \
                        creator is not None and \
@@ -201,6 +213,6 @@ def add_cand(cand):
                         input_ = creator.inputs[0]
                     add_cand(input_)
                     seen_edges.add((input_, cand))
-                    nodes.add(input_)
-                    nodes.add(cand)
-    return ComputationalGraph(list(nodes), list(seen_edges))
+                    nodes.add(HashableObject(input_))
+                    nodes.add(HashableObject(cand))
+    return ComputationalGraph(list(i.v for i in nodes), list(seen_edges))
diff --git a/chainer/variable.py b/chainer/variable.py
--- a/chainer/variable.py
+++ b/chainer/variable.py
@@ -157,7 +157,7 @@ def add_cand(cand):
             outputs = tuple(y() for y in func.outputs)  # access via weak ref
 
             in_data = tuple(x.data for x in func.inputs)
-            out_grad = tuple(y and y.grad for y in outputs)
+            out_grad = tuple(None if y is None else y.grad for y in outputs)
             func._check_data_type_backward(in_data, out_grad)
             with cuda.using_device(*(in_data + out_grad)):
                 gxs = func.backward(in_data, out_grad)
@@ -165,7 +165,7 @@ def add_cand(cand):
 
             if not retain_grad:
                 for y in outputs:
-                    if y is not None and y != self:
+                    if y is not None and y is not self:
                         y.grad = None
             for x, gx in zip(func.inputs, gxs):
                 x.grad = gx
@@ -199,4 +199,31 @@ def add_cand(cand):
                 add_cand(var.creator)
             func.unchain()
 
+    def __lt__(self, other):
+        raise NotImplementedError()
+
+    def __le__(self, other):
+        raise NotImplementedError()
+
+    def __eq__(self, other):
+        raise NotImplementedError()
+
+    def __ne__(self, other):
+        raise NotImplementedError()
+
+    def __gt__(self, other):
+        raise NotImplementedError()
+
+    def __ge__(self, other):
+        raise NotImplementedError()
+
+    def __nonzero__(self):
+        raise NotImplementedError()
+
+    def __bool__(self):
+        raise NotImplementedError()
+
+    def __hash__(self):
+        return super(Variable, self).__hash__()
+
     __array_priority__ = 200


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
@@ -898,4 +898,40 @@ def test_gpu(self):
         self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.gy))
 
 
+class TestNotSupportOperation(unittest.TestCase):
+
+    def setUp(self):
+        self.x = chainer.Variable(numpy.zeros(10))
+        self.y = chainer.Variable(numpy.zeros(10))
+
+    def test_lt(self):
+        with self.assertRaises(NotImplementedError):
+            self.x < self.y
+
+    def test_le(self):
+        with self.assertRaises(NotImplementedError):
+            self.x <= self.y
+
+    def test_eq(self):
+        with self.assertRaises(NotImplementedError):
+            self.x == self.y
+
+    def test_ne(self):
+        with self.assertRaises(NotImplementedError):
+            self.x != self.y
+
+    def test_gt(self):
+        with self.assertRaises(NotImplementedError):
+            self.x > self.y
+
+    def test_ge(self):
+        with self.assertRaises(NotImplementedError):
+            self.x >= self.y
+
+    def test_nonzero(self):
+        with self.assertRaises(NotImplementedError):
+            if self.x:
+                pass
+
+
 testing.run_module(__name__, __file__)