Repository: OpenMined/PySyft

Issue:
Implement Default clamp 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, clamp() should return a new tensor and clamp_() 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:

Resolving patch:
diff --git a/syft/tensor.py b/syft/tensor.py
--- a/syft/tensor.py
+++ b/syft/tensor.py
@@ -621,6 +621,19 @@ def log_normal_(self, mean=0, stdev=1.0):
         self.data = np.random.lognormal(mean, stdev, self.shape())
         return self
 
+    def clamp(self, minimum=None, maximum=None):
+        """Returns a clamped tensor into the range [min, max], elementwise"""
+        if self.encrypted:
+            return NotImplemented
+        return TensorBase(np.clip(self.data, a_min=minimum, a_max=maximum))
+
+    def clamp_(self, minimum=None, maximum=None):
+        """Clamp the tensor, in-place, elementwise into the range [min, max]"""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.clip(self.data, a_min=minimum, a_max=maximum)
+        return self
+
     def uniform_(self, low=0, high=1):
         """Fills the tensor in-place with numbers sampled unifromly
         over the half-open interval [low,high) or from the uniform distribution"""


Test patch:
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
--- a/tests/test_tensor.py
+++ b/tests/test_tensor.py
@@ -469,6 +469,32 @@ def testLog1p_(self):
         self.assertTrue(np.allclose((t1.log1p_()).data, [0.69314718, 1.09861229, 1.38629436]))
 
 
+class clampTests(unittest.TestCase):
+    def testClampInt(self):
+        t1 = TensorBase(np.arange(10))
+        t2 = t1.clamp(minimum=2, maximum=7)
+        expected_tensor = TensorBase(np.array([2, 2, 2, 3, 4, 5, 6, 7, 7, 7]))
+        self.assertEqual(t2, expected_tensor)
+
+    def testClampFloat(self):
+        t1 = TensorBase(np.arange(1, step=0.1))
+        t2 = t1.clamp(minimum=0.2, maximum=0.7)
+        expected_tensor = TensorBase(np.array([0.2, 0.2, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.7, 0.7]))
+        self.assertEqual(t2, expected_tensor)
+
+    def testClampIntInPlace(self):
+        t1 = TensorBase(np.arange(10))
+        t1.clamp_(minimum=2, maximum=7)
+        expected_tensor = TensorBase(np.array([2, 2, 2, 3, 4, 5, 6, 7, 7, 7]))
+        self.assertEqual(t1, expected_tensor)
+
+    def testClampFloatInPlace(self):
+        t1 = TensorBase(np.arange(1, step=0.1))
+        t1.clamp_(minimum=0.2, maximum=0.7)
+        expected_tensor = TensorBase(np.array([0.2, 0.2, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.7, 0.7]))
+        self.assertEqual(t1, expected_tensor)
+
+
 class uniformTests(unittest.TestCase):
     def testUniform(self):
         t1 = TensorBase(np.zeros(4))