Repository: OpenMined/PySyft

Issue:
Implement Default ge Functionality for Base Tensor Type
**User Story A:** As a Data Scientist using Syft's Base Tensor type, we want to implement a default method for computing operations on a Tensor of arbitrary type. For this ticket to be complete, ge() should return a new tensor and ge_() should perform the operation inline. For a reference on the operation this performs check out [PyTorch](http://pytorch.org/docs/master/tensors.html)'s documentation.

**Acceptance Criteria:**
- If the Base Tensor type's attribute "encrypted" is set to True, it should return a NotImplemented error.
- a unit test demonstrating the correct operation on the Base Tensor type implemented over int and float Tensors.
- inline documentation in the python code. For inspiration on inline documentation, please check out [PyTorch](http://pytorch.org/docs/master/tensors.html)'s documentation for this operator.

Discussion:

Patch:
diff --git a/syft/tensor.py b/syft/tensor.py
index 2bb335111e..a5f21740c9 100644
--- a/syft/tensor.py
+++ b/syft/tensor.py
@@ -725,19 +725,72 @@ class TensorBase(object):
         else:
             return [TensorBase(x) for x in np.array_split(self.data, n, dim)]
 
-    def gt(self, t):
+    def gt(self, other):
         """Returns a new Tensor having boolean True values where an element of the calling tensor is greater than the second Tensor, False otherwise.
         The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
-        if self.encrypted:
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
             return NotImplemented
-        return TensorBase(np.greater(self.data, _ensure_tensorbase(t).data))
+        return TensorBase(np.greater(self.data, other.data))
 
-    def gt_(self, t):
+    def gt_(self, other):
         """Writes in-place, boolean True values where an element of the calling tensor is greater than the second Tensor, False otherwise.
         The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
-        if self.encrypted:
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
+            return NotImplemented
+        self.data = np.greater(self.data, other.data)
+        return self
+
+    def lt(self, other):
+        """Returns a new Tensor having boolean True values where an element of the calling tensor is less than the second Tensor, False otherwise.
+        The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
+            return NotImplemented
+        return TensorBase(np.less(self.data, other.data))
+
+    def lt_(self, other):
+        """Writes in-place, boolean True values where an element of the calling tensor is less than the second Tensor, False otherwise.
+        The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
+            return NotImplemented
+        self.data = np.less(self.data, other.data)
+        return self
+
+    def ge(self, other):
+        """Returns a new Tensor having boolean True values where an element of the calling tensor is greater or equal than the second Tensor, False otherwise.
+        The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
+            return NotImplemented
+        return TensorBase(np.greater_equal(self.data, other.data))
+
+    def ge_(self, other):
+        """Writes in-place, boolean True values where an element of the calling tensor is greater or equal than the second Tensor, False otherwise.
+        The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
+            return NotImplemented
+        self.data = np.greater_equal(self.data, other.data)
+        return self
+
+    def le(self, other):
+        """Returns a new Tensor having boolean True values where an element of the calling tensor is less or equal than the second Tensor, False otherwise.
+        The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
+            return NotImplemented
+        return TensorBase(np.less_equal(self.data, other.data))
+
+    def le_(self, other):
+        """Writes in-place, boolean True values where an element of the calling tensor is less or equal than the second Tensor, False otherwise.
+        The second Tensor can be a number or a tensor whose shape is broadcastable with the calling Tensor."""
+        other = _ensure_tensorbase(other)
+        if self.encrypted or other.encrypted:
             return NotImplemented
-        self.data = np.greater(self.data, _ensure_tensorbase(t).data)
+        self.data = np.less_equal(self.data, other.data)
         return self
 
     def bernoulli(self, p):


Test patch:
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
index 62fc6336bb..55a16ceab9 100644
--- a/tests/test_tensor.py
+++ b/tests/test_tensor.py
@@ -560,28 +560,107 @@ class chunkTests(unittest.TestCase):
         self.assertEqual(t2.shape(), t3.shape())
 
 
-class gtTests(unittest.TestCase):
+class inequalityTest(unittest.TestCase):
+    def setUp(self):
+        self.a1 = np.array([-2, -1, 0, 1, 2])
+        self.a2 = np.array([-4, -1, 5, 2, 2])
+
+        self.t1 = TensorBase(self.a1)
+        self.t2 = TensorBase(self.a2)
+
+        self.enc = TensorBase(self.a1, encrypted=True)
+
+
+class gtTests(inequalityTest):
     def testGtWithTensor(self):
-        t1 = TensorBase(np.arange(10))
-        t2 = TensorBase(np.arange(10)[-1::-1])
-        truth_values = t1.gt(t2)
-        self.assertEqual(truth_values, [False, False, False, False, False, True, True, True, True, True])
+        self.assertEqual(self.t1.gt(self.t2), self.a1 > self.a2)
 
     def testGtWithNumber(self):
-        t1 = TensorBase(np.arange(10))
-        truth_values = t1.gt(-1)
-        self.assertEqual(truth_values, [True] * 10)
+        self.assertEqual(self.t1.gt(1), self.a1 > 1)
 
     def testGtInPlaceWithTensor(self):
-        t1 = TensorBase(np.arange(10))
-        t2 = TensorBase(np.arange(10)[-1::-1])
-        t1.gt_(t2)
-        self.assertEqual(t1, [False, False, False, False, False, True, True, True, True, True])
+        self.t1.gt_(self.t2)
+        self.assertEqual(self.t1, self.a1 > self.a2)
 
     def testGtInPlaceWithNumber(self):
-        t1 = TensorBase(np.arange(10))
-        t1.gt_(-1)
-        self.assertEqual(t1, [True] * 10)
+        self.t1.gt_(1)
+        self.assertEqual(self.t1, self.a1 > 1)
+
+    def testWithEncrypted(self):
+        res = self.t1.gt(self.enc)
+        self.assertEqual(res, NotImplemented)
+
+        res = self.enc.gt(self.t1)
+        self.assertEqual(res, NotImplemented)
+
+
+class geTests(inequalityTest):
+    def testGeWithTensor(self):
+        self.assertEqual(self.t1.ge(self.t2), self.a1 >= self.a2)
+
+    def testGeWithNumber(self):
+        self.assertEqual(self.t1.ge(1), self.a1 >= 1)
+
+    def testGeInPlaceWithTensor(self):
+        self.t1.ge_(self.t2)
+        self.assertEqual(self.t1, self.a1 >= self.a2)
+
+    def testGeInPlaceWithNumber(self):
+        self.t1.ge_(1)
+        self.assertEqual(self.t1, self.a1 >= 1)
+
+    def testWithEncrypted(self):
+        res = self.t1.ge(self.enc)
+        self.assertEqual(res, NotImplemented)
+
+        res = self.enc.ge(self.t1)
+        self.assertEqual(res, NotImplemented)
+
+
+class ltTests(inequalityTest):
+    def testLtWithTensor(self):
+        self.assertEqual(self.t1.lt(self.t2), self.a1 < self.a2)
+
+    def testLtWithNumber(self):
+        self.assertEqual(self.t1.lt(1), self.a1 < 1)
+
+    def testLtInPlaceWithTensor(self):
+        self.t1.lt_(self.t2)
+        self.assertEqual(self.t1, self.a1 < self.a2)
+
+    def testLtInPlaceWithNumber(self):
+        self.t1.lt_(1)
+        self.assertEqual(self.t1, self.a1 < 1)
+
+    def testWithEncrypted(self):
+        res = self.t1.lt(self.enc)
+        self.assertEqual(res, NotImplemented)
+
+        res = self.enc.lt(self.t1)
+        self.assertEqual(res, NotImplemented)
+
+
+class leTests(inequalityTest):
+    def testLeWithTensor(self):
+        self.assertEqual(self.t1.le(self.t2), self.a1 <= self.a2)
+
+    def testLeWithNumber(self):
+        self.assertEqual(self.t1.le(1), self.a1 <= 1)
+
+    def testLeInPlaceWithTensor(self):
+        self.t1.le_(self.t2)
+        self.assertEqual(self.t1, self.a1 <= self.a2)
+
+    def testLeInPlaceWithNumber(self):
+        self.t1.le_(1)
+        self.assertEqual(self.t1, self.a1 <= 1)
+
+    def testWithEncrypted(self):
+        res = self.t1.le(self.enc)
+        self.assertEqual(res, NotImplemented)
+
+        res = self.enc.le(self.t1)
+        self.assertEqual(res, NotImplemented)
 
 
 class bernoulliTests(unittest.TestCase):