Repository: OpenMined/PySyft (python)

Issue:
Implement Default log 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, log(), log1p(), and log_normal() should return a new tensor and log_(), log1p_(), and log_normal_() 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.

Pull request description:
implemented log(),log1p(),normal_log_()
fixes #58

Patch:
diff --git a/syft/tensor.py b/syft/tensor.py
index f9eff20a64..ee9cf8715d 100644
--- a/syft/tensor.py
+++ b/syft/tensor.py
@@ -518,3 +518,41 @@ class TensorBase(object):
         if self.encrypted:
             return NotImplemented
         self.data = 1 / np.array(self.data)
+
+    def log(self):
+        """performs elementwise logarithm operation
+        and returns a new Tensor"""
+        if self.encrypted:
+            return NotImplemented
+        out = np.log(self.data)
+        return TensorBase(out)
+
+    def log_(self):
+        """performs elementwise logarithm operation inplace"""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.log(self.data)
+        return self
+
+    def log1p(self):
+        """performs elementwise log(1+x) operation
+        and returns new tensor"""
+        if self.encrypted:
+            return NotImplemented
+        out = np.log1p(self.data)
+        return TensorBase(out)
+
+    def log1p_(self):
+        """performs elementwise log(1+x) operation inplace"""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.log1p(self.data)
+        return self
+
+    def log_normal_(self, mean=0, stdev=1.0):
+        """Fills give tensor with samples from a lognormal distribution
+        with given mean and stdev"""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.random.lognormal(mean, stdev, self.shape())
+        return self


Test patch:
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
index 1ae53eb8ce..d73c443827 100644
--- a/tests/test_tensor.py
+++ b/tests/test_tensor.py
@@ -2,6 +2,7 @@ from syft import TensorBase
 import syft
 import unittest
 import numpy as np
+import math
 
 
 # Here's our "unit tests".
@@ -374,3 +375,21 @@ class reciprocalTests(unittest.TestCase):
         t1 = TensorBase(np.array([2, 3, 4]))
         t1.reciprocal_()
         self.assertTrue(np.allclose(t1.data, [0.5, 0.33333333, 0.25]))
+
+
+class logTests(unittest.TestCase):
+    def testLog(self):
+        t1 = TensorBase(np.array([math.exp(1), math.exp(2), math.exp(3)]))
+        self.assertTrue(np.array_equal((t1.log()).data, [1., 2., 3.]))
+
+    def testLog_(self):
+        t1 = TensorBase(np.array([math.exp(1), math.exp(2), math.exp(3)]))
+        self.assertTrue(np.array_equal((t1.log_()).data, [1., 2., 3.]))
+
+    def testLog1p(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(np.allclose((t1.log1p()).data, [0.69314718, 1.09861229, 1.38629436]))
+
+    def testLog1p_(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(np.allclose((t1.log1p_()).data, [0.69314718, 1.09861229, 1.38629436]))