Repository: OpenMined/PySyft

Issue:
Implement Base Tensor Object
In this ticket, we want to create a new basic type called a "Tensor". A Tensor is a nested list of numbers with an arbitrary number of dimensions. With one dimension, it's a list of numbers known as a "vector". With two dimensions, it's a list of lists of numbers known as a "matrix". In this ticket, we want to build our implementation of Tensor with inspiration from PyTorch's  [Tensor](https://github.com/pytorch/pytorch/blob/master/torch/tensor.py) object ([Tensor Docs](http://pytorch.org/docs/master/tensors.html)). 

So, in this ticket, you should build a basic tensor class. You should be able to pass in as an argument an arbitrary shape (number of rows, columns, etc.) when you create it. Furthermore, we want this basic class to support elementwise addition, subtraction, multiplication, and division.

Acceptance Criteria:
- __init__ supports initializing a tensor with an arbitrary number of dimensions and arbitrary scalar type (test with float and int)
- elementwise operators for +-/* and their inline operators should be overridden
- elementwise operations should check to make sure both tensors are of identical dimension
- elementwise operators should also support (and automatically detect!) the input of a single number, which should be applied to the entire tensor (aka... multiply every number by "5").



Discussion:
Does this depend on #10?  Do we wanna extend this Tensor class off of the generic Tensor that will be created?
Ah1 I need to put this in the Contributor Quickstart Guide. Install the ZenHub plugin. zenhub.com That will show you that this ticket is a subset of #10 and in fact the first stepping stone to achieving #10 

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -10,8 +10,6 @@ def read(fname):
 
 requirements = read('requirements.txt').split()
 
-
-
 setup(
     name = "syft",
     version = "0.1.0",
@@ -29,4 +27,6 @@ def read(fname):
     ],
     scripts=['bin/syft_cmd'],
     install_requires=requirements,
+    setup_requires=['pytest-runner'],
+    tests_require=['pytest']
 )
diff --git a/syft/__init__.py b/syft/__init__.py
--- a/syft/__init__.py
+++ b/syft/__init__.py
@@ -1,26 +1 @@
-import importlib
-import pkgutil
-
-ignore_packages = set(['test'])
-
-def import_submodules(package, recursive=True):
-    """ Import all submodules of a module, recursively, including subpackages
-
-    :param package: package (name or actual module)
-    :type package: str | module
-    :rtype: dict[str, types.ModuleType]
-    """
-    if isinstance(package, str):
-        package = importlib.import_module(package)
-    results = {}
-    for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
-        # test submodule names are 'syft.test.*', so this matches the 'ignore_packages' above
-        if 'test' not in str(name):
-            full_name = package.__name__ + '.' + name
-            results[full_name] = importlib.import_module(full_name)
-            if recursive and is_pkg:
-                results.update(import_submodules(full_name))
-    return results
-
-# import submodules recursively
-import_submodules(__name__)
+from .tensor import TensorBase
diff --git a/syft/tensor.py b/syft/tensor.py
new file mode 100644
--- /dev/null
+++ b/syft/tensor.py
@@ -0,0 +1,85 @@
+import numpy as np
+
+def _ensure_ndarray(arr):
+    if not isinstance(arr, np.ndarray):
+        arr = np.array(arr)
+
+    return arr
+
+class TensorBase(object):
+    """
+    A base tensor class that perform basic element-wise operation such as 
+    addition, subtraction, multiplication and division
+    """
+
+    def __init__(self, arr_like, encrypted=False):
+        self.data = _ensure_ndarray(arr_like)
+        self.encrypted = encrypted
+
+    def __add__(self, arr_like):
+        """Performs element-wise addition between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        return self.data + arr_like
+
+    def __iadd__(self, arr_like):
+        """Performs in place element-wise addition between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        self.data = self.data + arr_like
+        return self.data
+
+    def __sub__(self, arr_like):
+        """Performs element-wise subtraction between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        return self.data - arr_like
+
+    def __isub__(self, arr_like):
+        """Performs in place element-wise subtraction between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        self.data = self.data - arr_like
+        return self.data
+
+    def __mul__(self, arr_like):
+        """Performs element-wise multiplication between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        return self.data * arr_like
+
+    def __imul__(self, arr_like):
+        """Performs in place element-wise multiplication between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        self.data = self.data * arr_like
+        return self.data
+
+    def __truediv__(self, arr_like):
+        """Performs element-wise division between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        return self.data / arr_like
+
+    def __itruediv__(self, arr_like):
+        """Performs in place element-wise subtraction between two array like objects"""
+        if self.encrypted:
+            return NotImplemented
+
+        arr_like = _ensure_ndarray(arr_like)
+        self.data = self.data / arr_like
+        return self.data


Test patch:
diff --git a/syft/test/__init__.py b/syft/test/__init__.py
deleted file mode 100644
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
new file mode 100644
--- /dev/null
+++ b/tests/test_tensor.py
@@ -0,0 +1,66 @@
+from syft import TensorBase
+import unittest
+import numpy as np
+
+# Here's our "unit tests".
+class AddTests(unittest.TestCase):
+    def testSimple(self):
+        t = TensorBase(np.array([1,2,3]))
+        self.assertTrue(np.array_equal(t + np.array([1,2,3]), [2,4,6]))
+    
+    def testInplace(self):
+        t = TensorBase(np.array([1,2,3]))
+        t += np.array([1,2,3])
+        self.assertTrue(np.array_equal(t.data, [2,4,6]))
+
+    def testScalar(self):
+        t = TensorBase(np.array([1,2,3]))
+        self.assertTrue(np.array_equal(t + 2, [3, 4, 5]))
+
+class SubTests(unittest.TestCase):
+    def testSimple(self):
+        t = TensorBase(np.array([1,2,3]))
+        self.assertTrue(np.array_equal(t - np.array([1,2,3]), [0,0,0]))
+    
+    def testInplace(self):
+        t = TensorBase(np.array([1,2,3]))
+        t -= np.array([1,2,3])
+        self.assertTrue(np.array_equal(t.data, [0,0,0]))
+
+    def testScalar(self):
+        t = TensorBase(np.array([1,2,3]))
+        self.assertTrue(np.array_equal(t - 1, [0, 1, 2]))
+
+class MultTests(unittest.TestCase):
+    def testSimple(self):
+        t = TensorBase(np.array([1,2,3]))
+        self.assertTrue(np.array_equal(t * np.array([1,2,3]), [1,4,9]))
+
+    def testInplace(self):
+        t = TensorBase(np.array([1,2,3]))
+        t *= np.array([1,2,3])
+        self.assertTrue(np.array_equal(t.data, [1,4,9]))
+
+    def testScalar(self):
+        t = TensorBase(np.array([1,2,3]))
+        self.assertTrue(np.array_equal(t * 2, [2, 4, 6]))
+
+class DivTests(unittest.TestCase):
+    def testSimple(self):
+        t = TensorBase(np.array([2,4,8]))
+        self.assertTrue(np.array_equal(t / np.array([2,2,2]), [1,2,4]))
+
+    def testInplace(self):
+        t = TensorBase(np.array([1,2,3]))
+        t *= np.array([1,2,3])
+        self.assertTrue(np.array_equal(t.data, [1,4,9]))
+
+    def testScalar(self):
+        t = TensorBase(np.array([2,4,6]))
+        self.assertTrue(np.array_equal(t / 2, [1, 2, 3]))
+    
+def main():
+    unittest.main()
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file