Repository: OpenMined/PySyft

Issue:
Implement Default Absolute Value Functions in 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 the elementwise absolute value of a Tensor of arbitrary type. abs() should return a new tensor and abs_ should perform the operation inline. For a great reference on how 

**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 of abs() and abs_() 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.
Implement Default addmm Functionality in 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 each operation on a Tensor of arbitrary type. addmm_() should return a new tensor and addmm_() 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 of addmm() and addmm_() 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
@@ -84,6 +84,19 @@ def __itruediv__(self, arr_like):
         self.data = self.data / arr_like
         return self.data
 
+    def abs(self):
+        """Returns absolute value of tensor as a new tensor"""
+        if self.encrypted:
+            return NotImplemented
+        return np.absolute(self.data)
+    
+    def abs_(self):
+        """Replaces tensor values with its absolute value"""
+        if self.encrypted:
+            return NotImplemented
+        self.data=np.absolute(self.data)
+        return self.data
+
     def shape(self):
         """Returns a tuple of input array dimensions."""
         if self.encrypted:
@@ -100,3 +113,33 @@ def sum(self, dim=None):
             return self.data.sum()
         else:
             return self.data.sum(axis=dim)
+     
+     def addmm(self,tensor2,mat,beta=1,alpha=1):
+        """Performs ((Mat*Beta)+((Tensor1.Tensor2)*Alpha)) and  returns the result as a Tensor
+            Tensor1.Tensor2 is performed as Matrix product of two array The behavior depends on the arguments in the following way.
+            *If both tensors are 1-dimensional, their dot product is returned.
+            *If both arguments are 2-D they are multiplied like conventional matrices.
+            *If either argument is N-D, N > 2, it is treated as a stack of matrices residing in the last two indexes and broadcast accordingly.
+            *If the first argument is 1-D, it is promoted to a matrix by prepending a 1 to its dimensions. After matrix multiplication the prepended 1 is removed.
+            *If the second argument is 1-D, it is promoted to a matrix by appending a 1 to its dimensions. After matrix multiplication the appended 1 is removed.
+            """
+        if self.encrypted or tensor2.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            return TensorBase(np.array((mat*beta)+((np.matmul(self.data,tensor2.data))*alpha)))
+
+    def addmm_(self,tensor2,mat,beta=1,alpha=1):
+        """Performs ((Mat*Beta)+((Tensor1.Tensor2)*Alpha)) and updates Tensor1 with result and reurns it
+            Tensor1.Tensor2 is performed as Matrix product of two array The behavior depends on the arguments in the following way.
+            *If both tensors are 1-dimensional, their dot product is returned.
+            *If both arguments are 2-D they are multiplied like conventional matrices.
+            *If either argument is N-D, N > 2, it is treated as a stack of matrices residing in the last two indexes and broadcast accordingly.
+            *If the first argument is 1-D, it is promoted to a matrix by prepending a 1 to its dimensions. After matrix multiplication the prepended 1 is removed.
+            *If the second argument is 1-D, it is promoted to a matrix by appending a 1 to its dimensions. After matrix multiplication the appended 1 is removed.
+            """
+        if self.encrypted is True or tensor2.encrypted is True or mat.encrypted is True:
+            return NotImplemented
+        else:
+            self.data=np.array((mat*beta)+((np.matmul(self.data,tensor2.data))*alpha))
+            return self
+


Test patch:
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
--- a/tests/test_tensor.py
+++ b/tests/test_tensor.py
@@ -59,6 +59,15 @@ def testScalar(self):
         t = TensorBase(np.array([2,4,6]))
         self.assertTrue(np.array_equal(t / 2, [1, 2, 3]))
 
+class AbsTests(unittest.TestCase):
+       
+    def testabs(self):
+        t = TensorBase(np.array([-1,-2,3]))
+        self.assertTrue(np.array_equal(t.abs(),[1,2,3]))
+    def testabs_(self):
+        t = TensorBase(np.array([-1,-2,3]))
+        self.assertTrue(np.array_equal(t.abs_(),t.data))  
+
 class ShapeTests(unittest.TestCase):
     def testShape(self):
         t = TensorBase(np.array([[0, 1], [0, 5]]))
@@ -73,6 +82,35 @@ def testDimIsNotNoneInt(self):
         t = TensorBase(np.array([[0, 1], [0, 5]]))
         self.assertTrue(np.array_equal(t.sum(dim=1), [1, 5]))
 
+class addmm(unittest.TestCase):
+    def testaddmm1d(self):
+        t1=TensorBase(np.array([1,2,3]))
+        t2=TensorBase(np.array([2,3,4]))
+        mat=TensorBase(np.array([5]))
+        out=t1.addmm(t2,mat,beta=2,alpha=2)
+        self.assertTrue(np.array_equal(out.data,[50]))
+    
+    def testaddmm2d(self):
+        t1=TensorBase(np.array([[1,2],[1,2]]))
+        t2=TensorBase(np.array([[1,2],[1,2]]))
+        mat=TensorBase(np.array([[2,3],[3,4]]))
+        out=t1.addmm(t2,mat,beta=2,alpha=2)
+        self.assertTrue(np.array_equal(out.data,[[10,18],[12,20]]))
+    
+    def testaddmm_1d(self):
+        t1=TensorBase(np.array([1,2,3]))
+        t2=TensorBase(np.array([2,3,4]))
+        mat=TensorBase(np.array([5]))
+        t1.addmm_(t2,mat,beta=2,alpha=2)
+        self.assertTrue(np.array_equal(t1.data,[50]))
+    
+    def testaddmm_2d(self):
+        t1=TensorBase(np.array([[1,2],[1,2]]))
+        t2=TensorBase(np.array([[1,2],[1,2]]))
+        mat=TensorBase(np.array([[2,3],[3,4]]))
+        t1.addmm_(t2,mat,beta=2,alpha=2)
+        self.assertTrue(np.array_equal(t1.data,[[10,18],[12,20]]))
+
 def main():
     unittest.main()