Repository: OpenMined/PySyft
Pull request #191 (2017-08-28 04:47:25)

Release changes to master


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
old mode 100644
new mode 100755
index 2463e5be82..ff70b43ba7
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,13 +7,6 @@ jobs:
     steps:
       - checkout
       - setup_remote_docker
-      - run:
-          name: Install dependencies
-          command: |
-            apk add --no-cache \
-              py-pip
-            pip install \
-              docker-compose
       - run:
           name: Build application Docker image
           command: |
@@ -21,12 +14,17 @@ jobs:
       - run:
           name: Run tests
           command: |
-            docker run -it pysyft pytest tests/test_tensor.py
+            docker run --rm pysyft pytest
+            docker run --rm pysyft pytest --flake8
       - deploy:
           name: Push application Docker image
           command: |
-            if [ "${CIRCLE_BRANCH}" == "master" ]; then
-              docker login -e "${DOCKERHUB_EMAIL}" -u "${DOCKERHUB_USER}" -p "${DOCKERHUB_PASS}"
-              docker tag pysyft "${DOCKERHUB_REPO}:${CIRCLE_TAG}"
-              docker push "${DOCKERHUB_REPO}:${CIRCLE_TAG}"
+            if [ "${CIRCLE_BRANCH}" == "develop" ]; then
+              docker login -u "${DOCKERHUB_USER}" -p "${DOCKERHUB_PASS}"
+              docker tag pysyft "${DOCKERHUB_REPO}:edge"
+              docker push "${DOCKERHUB_REPO}:edge"
+            elif [ "${CIRCLE_BRANCH}" == "master" ]; then
+              docker login -u "${DOCKERHUB_USER}" -p "${DOCKERHUB_PASS}"
+              docker tag pysyft "${DOCKERHUB_REPO}:latest"
+              docker push "${DOCKERHUB_REPO}:latest"
             fi
diff --git a/.gitignore b/.gitignore
index 92979d232e..e6b31dcc53 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,105 @@
+# Created by https://www.gitignore.io/api/python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
 build/
+develop-eggs/
 dist/
-syft.egg-info/
-*/**/__pycache__
-.ipynb_checkpoints/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+pytest_runner*
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# dotenv
+.env
+
+# virtualenv
+.venv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
 *.pickle
 lastfailed
-pytest_runner*
-.eggs/
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
old mode 100644
new mode 100755
index 348310e608..09efd8942d
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,10 @@
 FROM alpine:edge
-RUN ["apk", "add", "--no-cache", "python3", "python3-dev", "musl-dev", "linux-headers", "g++", "gmp-dev", "mpfr-dev", "mpc1-dev"]
+RUN ["apk", "add", "--no-cache", "python3", "python3-dev", "musl-dev", "linux-headers", "g++", "gmp-dev", "mpfr-dev", "mpc1-dev", "ca-certificates"]
 
-RUN ["mkdir", "/syft"]
-COPY requirements.txt /syft
+RUN ["mkdir", "/PySyft"]
+COPY requirements.txt /PySyft
 
-WORKDIR /syft
+WORKDIR /PySyft
 RUN ["pip3", "install", "-r", "requirements.txt"]
-COPY . /syft
-RUN ["python3", "setup.py", "install"]
\ No newline at end of file
+COPY . /PySyft
+RUN ["python3", "setup.py", "install"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000..e5c278a30d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+.PHONY: test run
+
+test:
+	pytest
+
+run:
+	docker run --rm -it -v $(PWD)/notebooks:/notebooks -w /notebooks -p 8888:8888 openmined/pysyft jupyter notebook --ip=0.0.0.0 --allow-root
diff --git a/README.md b/README.md
index d9ad6e3dcf..64c311f371 100755
--- a/README.md
+++ b/README.md
@@ -1,10 +1,24 @@
 # Syft
 
+[![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-7A5979.svg)](https://openmined.slack.com/)
+
 > Homomorphically Encrypted Deep Learning Library
 
 The goal of this library is to give the user the ability to efficiently train Deep Learning models in a homomorphically encrypted state without needing to be an expert in either. Furthermore, by understanding the characteristics of both Deep Learning and Homomorphic Encryption, we hope to find very performant combinations of the two.  See [notebooks](./notebooks) folder for tutorials on how to use the library.
 
-## Installation
+- [Setup](#setup-install-instructions)
+  - [Prerequisites](#prerequisites)
+  - [Installation](#installation)
+- [Usage](#usage)
+  - [Start](#start)
+  - [Tests](#tests)
+- [For Contributors](#for-contributors)
+- [Relevant Literature](#relevant-literature)
+- [License](#license)
+
+## Setup
+
+### Prerequisites
 
 You need to install this library locally before running any of the notebooks this repository or the [main demonstration](https://github.com/OpenMined/sonar):
 
@@ -15,64 +29,95 @@ pip install -r requirements.txt
 python setup.py install
 ```
 
-### For Anaconda Users:
+### Installation
+
+The recommended method is using Docker (works on all major operating systems).
+
+#### For Docker Users
+
+Install Docker from [its website](https://www.docker.com/).
+For macOS users with [Homebrew](https://brew.sh/) installed, use `brew cask install docker`.
+
+#### For Anaconda Users
 
 ```
 bash install_for_anaconda_users.sh
 ```
 
-### For Docker Users
+##### Windows
 
-Install Docker from https://www.docker.com/
-For macOS users with [Homebrew](https://brew.sh/) installed, use `brew cask install docker`
+```sh
+conda install -c conda-forge gmpy2
+pip install -r requirements.txt
+python setup.py install
+```
+
+## Usage
+
+### Start
 
 Then, run:
 
 ```sh
 git clone https://github.com/OpenMined/PySyft.git
-cd PySyft/notebooks/
-docker run --rm -it -v $PWD:/notebooks -w /notebooks -p 8888:8888 openmined/pysyft jupyter notebook --ip=0.0.0.0 --allow-root
+cd PySyft
+make run
+```
+
+### Tests
+
+```sh
+cd PySyft
+make test
 ```
 
 ## For Contributors
 
-If you are interested in contributing to Syft, first check out our [Contributor Quickstart Guide](https://github.com/OpenMined/Docs/blob/master/contributor_quickstart.md) and then checkout our [Project Roadmap](https://github.com/OpenMined/Syft/blob/master/ROADMAP.md) and sign into our Slack Team channel #syft to let us know which projects sound interesting to you! (or propose your own!).
+If you are interested in contributing to Syft, first check out our [Contributor Quickstart Guide](https://github.com/OpenMined/Docs/blob/master/contributing/quickstart.md) and then sign into our [Slack Team](https://openmined.slack.com/) channel #syft to let us know which projects sound interesting to you! (or propose your own!).
 
 ## Relevant Literature
 
 As both Homomorphic Encryption and Deep Learning are still somewhat sparsely known, below is a curated list of relevant reading materials to bring you up to speed with the major concepts and themes of these exciting fields.
 
 ### Encrypted Deep Learning - Recommended Reading:
-- How to build a fully encrypted AI model (trained on unencrypted data):  
-  - http://iamtrask.github.io/2017/03/17/safe-ai/
-- Simple secure protocol for federated machine learning (using a python-paillier library):  
-  - https://blog.n1analytics.com/distributed-machine-learning-and-partially-homomorphic-encryption-1/
-- Prototype for using encrypted AI to preserve user privacy (in python):  
-  - http://iamtrask.github.io/2017/06/05/homomorphic-surveillance/
-- Manual for Using Homomorphic Encryption for Bioinformatics (paper):  
-  - https://www.microsoft.com/en-us/research/wp-content/uploads/2015/11/ManualHE-3.pdf
+- [How to build a fully encrypted AI model (trained on unencrypted data)](http://iamtrask.github.io/2017/03/17/safe-ai/)
+- [Simple secure protocol for federated machine learning (using a python-paillier library)](https://blog.n1analytics.com/distributed-machine-learning-and-partially-homomorphic-encryption-1/)
+- [Prototype for using encrypted AI to preserve user privacy (in python)](http://iamtrask.github.io/2017/06/05/homomorphic-surveillance/)
+- [Manual for Using Homomorphic Encryption for Bioinformatics (paper)](https://www.microsoft.com/en-us/research/wp-content/uploads/2015/11/ManualHE-3.pdf)
 
 ### Homomorphic Encryption - Recommended Reading:
-- https://eprint.iacr.org/2014/062.pdf
-- http://heat-h2020-project.blogspot.co.uk/2017/02/homomorphic-encryption-api-software.html
-- http://heat-h2020-project.blogspot.co.uk/2017/
+- [A Comparison of the Homomorphic Encryption Schemes](https://eprint.iacr.org/2014/062.pdf)
+- [Homomorphic Encryption API Software Library](http://heat-h2020-project.blogspot.co.uk/2017/02/homomorphic-encryption-api-software.html)
+- [Faster Homomorphic Function Evaluation using Non-Integral Base Encoding](http://heat-h2020-project.blogspot.co.uk/2017/)
 
 ### Relevant Papers:
-- http://proceedings.mlr.press/v54/esperanca17a/esperanca17a.pdf
-- https://sites.google.com/view/psml/program/abstracts
-- https://arxiv.org/pdf/1508.06845.pdf
-- https://arxiv.org/pdf/1508.06574.pdf
-- https://eprint.iacr.org/2016/892
-- https://www1.cs.fau.de/filepool/publications/octavian_securescm/secfp-fc10.pdf
+- [Encrypted accelerated least squares regression](http://proceedings.mlr.press/v54/esperanca17a/esperanca17a.pdf)
+- [PSML 2017 - Abstracts](https://sites.google.com/view/psml/program/abstracts)
+- [Encrypted statistical machine learning: new privacy
+preserving methods](https://arxiv.org/pdf/1508.06845.pdf)
+- [A review of homomorphic encryption and software
+tools for encrypted statistical machine learning](https://arxiv.org/pdf/1508.06574.pdf)
+- [Privacy-Preserving Distributed Linear Regression on High-Dimensional Data](https://eprint.iacr.org/2016/892)
+- [Secure Computation With Fixed-Point Numbers](https://www1.cs.fau.de/filepool/publications/octavian_securescm/secfp-fc10.pdf)
+- [Scalable and secure logistic regression via homomorphic encryption](https://pdfs.semanticscholar.org/d24c/81f1e2904ba6ec3f341161865ef93247855b.pdf)
+- [ML Confidential: Machine Learning on Encrypted Data](https://eprint.iacr.org/2012/323.pdf)
+- [CryptoNets: Applying Neural Networks to Encrypted Data with High Throughput and Accuracy](http://proceedings.mlr.press/v48/gilad-bachrach16.pdf)
+- [Privacy-Preserving Visual Learning Using Doubly Permuted Homomorphic Encryption](https://arxiv.org/pdf/1704.02203.pdf)
 
 ### Related Libraries:
-- http://www.louisaslett.com/HomomorphicEncryption/#details
-- https://github.com/iamtrask/linreg-mpc
-- https://github.com/dask/dask-tutorial
+- [HomomorphicEncryption - An R package for fully homomorphic encryption](http://www.louisaslett.com/HomomorphicEncryption/#details)
+- [A Secure Multiparty Computation (MPC) protocol for computing linear regression on vertically distributed datasets](https://github.com/iamtrask/linreg-mpc)
+- [Dask Tutorial](https://github.com/dask/dask-tutorial)
+- [Charm-crypto](http://charm-crypto.io/)
 
 ### Related Blogs:
-- https://mortendahl.github.io/2017/04/17/private-deep-learning-with-mpc/
-- https://mortendahl.github.io/2017/06/04/secret-sharing-part1/
-- https://mortendahl.github.io/2017/06/24/secret-sharing-part2/
-- https://blog.n1analytics.com/distributed-machine-learning-and-partially-homomorphic-encryption-1/
-- https://blog.mturk.com/tutorial-how-to-verify-crowdsourced-training-data-using-a-known-answer-review-policy-85596fb55ed
+- [Private Deep Learning with MPC - A Simple Tutorial from Scratch](https://mortendahl.github.io/2017/04/17/private-deep-learning-with-mpc/)
+- [Secret Sharing, Part 1 - Distributing Trust and Work](https://mortendahl.github.io/2017/06/04/secret-sharing-part1/)
+- [Secret Sharing, Part 2 - Efficient Sharing with the Fast Fourier Transform](https://mortendahl.github.io/2017/06/24/secret-sharing-part2/)
+- [Distributed machine learning and partially homomorphic encryption (Part 1)](https://blog.n1analytics.com/distributed-machine-learning-and-partially-homomorphic-encryption-1/)
+- [Distributed machine learning and partially homomorphic encryption (Part 2)](https://blog.n1analytics.com/distributed-machine-learning-and-partially-homomorphic-encryption-2/)
+- [Tutorial: How to verify crowdsourced training data using a Known Answer Review Policy](https://blog.mturk.com/tutorial-how-to-verify-crowdsourced-training-data-using-a-known-answer-review-policy-85596fb55ed)
+
+## License
+
+[Apache-2.0](https://github.com/OpenMined/PySyft/blob/develop/LICENSE) by OpenMined contributors
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index e44884c089..0000000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Syft Project Roadmap
-
-
-- [ ] Demo
-    - [x] Initial Homomorphic Encryption Support (syft/he)
-    - [x] Basic Linear Model (syft/nn/linear.py)
-    - [x] Initial Benchmark Testing Suite
-- [ ] Proof of Concept
-    - [ ] Homomorphic Encryption (syft/he)
-      - [ ] Add Generic Tensor as Basic Type ([ISSUE 10](https://github.com/OpenMined/Syft/issues/10))
-      - [ ] Add Additive/Multiplicative Depth Tracking ([ISSUE 11](https://github.com/OpenMined/Syft/issues/11))
-      - [ ] Abstract Fixed Point Precision ([ISSUE 12](https://github.com/OpenMined/Syft/issues/12))
-      - [ ] CPU YASHE Support (ISSUE #)
-      - [ ] Initial Wrap of cuYASHE Support (ISSUE #)
-      - [ ] Internal, faster "from scratch" rebuild of Paillier ([ISSUE 13](https://github.com/OpenMined/Syft/issues/13))
-      - [ ] Key-Rotation Support for Additive HE ([ISSUE 14](https://github.com/OpenMined/Syft/issues/14))
-      - [ ] Key-Rotation Support for Add+Mul HE (ISSUE #)
-      - [ ] Full Unit-Testing Suite for All Features (ISSUE #)
-      - [ ] Benchmarking Suite for All Operations & Encryptions (ISSUE #)
-      - [ ] Development server for Fast Decryption/Re-Encryption
-      - [ ] RESEARCH: whether https://eprint.iacr.org/2016/870.pdf can be used to speedup YASHE/FV bootstrapping.
-    - [ ] Neural Networks
-      - [ ] Components
-        - [ ] Full Unit-Testing Suite for All Components (ISSUE #)
-        - [ ] Develop Abstraction for Encrypted vs Unencrypted Logic in a Layer (ISSUE #)
-        - [ ] Layers
-          - [ ] Linear Layer (ISSUE #)
-          - [ ] Convolutional Layer (ISSUE #)
-          - [ ] Embedding Layer (ISSUE #)
-          - [ ] Hashed Embedding Layer (variable length vocab) (ISSUE #)  
-          - [ ] Softmax Layer (ISSUE #)
-          - [ ] Sparsemax Layer (ISSUE #)
-          - [ ] Hierarchical Layer (softmax/sparsemax) (ISSUE #)
-          - [ ] Vanilla Recurrent Layer (ISSUE #)
-          - [ ] LSTM Layer (ISSUE #)
-        - [ ] Nonlinearities
-          - [ ] Add Support for ReLU (ISSUE #)
-          - [ ] Add Support for Sigmoid (ISSUE #)
-          - [ ] Add Support for Tanh (ISSUE #)
-          - [ ] Add Support for Hard Tanh (ISSUE #)
-          - [ ] Add Experimental Support for x**2 and x**3 (ISSUE #)
-        - [ ] Losses
-          - [ ] MSE Loss (ISSUE #)
-          - [ ] Cross Entropy Loss (ISSUE #)
-      - [ ] Pre-fab Classifiers
-        - Linear Classifier (ISSUE #)
-        - Convolutional Neural Network (ISSUE #)
-        - Word2vec Classifier (ISSUE #)
-        - FastText Classifier (ISSUE #)
-        - LSTM Classifier (ISSUE #)
-        - RL Model (ISSUE #)
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/bin/syft_cmd b/bin/syft_cmd
index 617a2c90c7..a5fc4b3214 100644
--- a/bin/syft_cmd
+++ b/bin/syft_cmd
@@ -54,7 +54,7 @@ with indent(4, quote='>>>'):
         folder_path = str(dict(args.grouped)['-path'][0])
 
         if(encryption == 'paillier'):
-            from syft.he.Paillier import KeyPair
+            from syft.he.paillier import KeyPair
             puts(colored.blue('Generating Keys...'))
             pubkey,prikey = KeyPair().generate(n_length=int(keylen))
             puts('DONE!')
diff --git a/notebooks/Packed+Paillier.ipynb b/notebooks/Packed+Paillier.ipynb
new file mode 100644
index 0000000000..69e867d248
--- /dev/null
+++ b/notebooks/Packed+Paillier.ipynb
@@ -0,0 +1,257 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Packing multiple numbers"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import phe\n",
+    "import struct\n",
+    "import random"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "publickey, privatekey = phe.generate_paillier_keypair(n_length=2048)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Pack 4 byte numbers into a single number"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "x = 20"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "800"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "endian = 'little'\n",
+    "packed_fmt = \"x4p\" * x\n",
+    "struct.calcsize(packed_fmt)*8"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def pack_numbers(numbers):\n",
+    "    p = []\n",
+    "    for n in numbers:\n",
+    "        n_bytes = n.to_bytes(4, endian)\n",
+    "        p.append(n_bytes)\n",
+    "        assert n == int.from_bytes(n_bytes, endian)\n",
+    "        #print(n_bytes)\n",
+    "\n",
+    "    encoded_bytes = struct.pack(packed_fmt, *p)\n",
+    "    encoded_num = int.from_bytes(encoded_bytes, endian)\n",
+    "    return encoded_num\n",
+    "\n",
+    "def unpack(encoded):\n",
+    "    enc_bytes = encoded.to_bytes(struct.calcsize(packed_fmt), endian)\n",
+    "    return [int.from_bytes(b, endian) for b in struct.unpack(packed_fmt, enc_bytes)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "numbers_a = [random.randint(0, 2**20) for _ in range(x)]\n",
+    "\n",
+    "encoded_a = pack_numbers(numbers_a)\n",
+    "assert unpack(encoded_a) == numbers_a\n",
+    "\n",
+    "numbers_b = [random.randint(0, 2**20) for i in range(x)]\n",
+    "numbers_b[0] = 0\n",
+    "numbers_b[1] = 1\n",
+    "numbers_b[2] = 2**20\n",
+    "\n",
+    "encoded_b = pack_numbers(numbers_b)\n",
+    "assert unpack(encoded_b) == numbers_b"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "ciphertext_a = publickey.raw_encrypt(encoded_a)\n",
+    "\n",
+    "ciphertext_b = publickey.raw_encrypt(encoded_b)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now I assume that we can add them together.\n",
+    "\n",
+    "I'm not so confident about multiplying by a scalar:\n",
+    "\n",
+    "- E(a) * b"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "def compare(operation, res):\n",
+    "    for a, b, r in zip(numbers_a, numbers_b, res):\n",
+    "        opres = operation(a, b)\n",
+    "        assert opres == r\n",
+    "        print(opres, r)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Case 1\n",
+    "\n",
+    "$E(a) + E(b)$"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "230434 230434\n",
+      "193515 193515\n",
+      "1438910 1438910\n",
+      "1359267 1359267\n",
+      "1515364 1515364\n",
+      "1319562 1319562\n",
+      "1125473 1125473\n",
+      "1641695 1641695\n",
+      "582305 582305\n",
+      "956673 956673\n",
+      "462229 462229\n",
+      "243885 243885\n",
+      "622645 622645\n",
+      "928660 928660\n",
+      "1251032 1251032\n",
+      "1593311 1593311\n",
+      "1466206 1466206\n",
+      "1041035 1041035\n",
+      "1734044 1734044\n",
+      "1588096 1588096\n"
+     ]
+    }
+   ],
+   "source": [
+    "ea = phe.EncryptedNumber(publickey, ciphertext_a)\n",
+    "cipher_both = ea._raw_add(ciphertext_a, ciphertext_b)\n",
+    "result = unpack(privatekey.raw_decrypt(cipher_both))\n",
+    "compare(lambda a,b: a+b, result)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "OverflowError",
+     "evalue": "int too big to convert",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mOverflowError\u001b[0m                             Traceback (most recent call last)",
+      "\u001b[0;32m<ipython-input-10-3f2a4f3dd82e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0mea\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mphe\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mEncryptedNumber\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpublickey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mciphertext_a\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0mcipher_both\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mea\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_raw_mul\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mencoded_b\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0munpack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprivatekey\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mraw_decrypt\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcipher_both\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      5\u001b[0m \u001b[0mcompare\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m<ipython-input-5-2a2dbaadc88c>\u001b[0m in \u001b[0;36munpack\u001b[0;34m(encoded)\u001b[0m\n\u001b[1;32m     12\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     13\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0munpack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mencoded\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 14\u001b[0;31m     \u001b[0menc_bytes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mencoded\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_bytes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstruct\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcalcsize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpacked_fmt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mendian\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     15\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfrom_bytes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mendian\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mb\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mstruct\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munpack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpacked_fmt\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0menc_bytes\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;31mOverflowError\u001b[0m: int too big to convert"
+     ]
+    }
+   ],
+   "source": [
+    "# Case 2 multiplying be a vector encoded number\n",
+    "ea = phe.EncryptedNumber(publickey, ciphertext_a)\n",
+    "cipher_both = ea._raw_mul(encoded_b)\n",
+    "result = unpack(privatekey.raw_decrypt(cipher_both))\n",
+    "compare(lambda a,b: a*b, result)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/Syft - BaseTensor and PaillierTensor Playground.ipynb b/notebooks/Syft - BaseTensor and PaillierTensor Playground.ipynb
new file mode 100644
index 0000000000..39d48f8e58
--- /dev/null
+++ b/notebooks/Syft - BaseTensor and PaillierTensor Playground.ipynb
@@ -0,0 +1,300 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from syft.he.paillier import KeyPair,PaillierTensor\n",
+    "from syft import TensorBase\n",
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "p,s = KeyPair().generate()\n",
+    "\n",
+    "x = PaillierTensor(p,np.array([1,2,3,4,5.]))\n",
+    "x2 = TensorBase(np.array([3,4,5,6,7.]))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([e, e, e, e, e], dtype=object)"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "x + x2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "x2.encrypted"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([e, e, e, e, e], dtype=object)"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "x2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "out = s.decrypt(x2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array(array([  3.,   8.,  15.,  24.,  35.]), dtype=object)"
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "out.data"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array(array([  3.,   8.,  15.,  24.,  35.]), dtype=object)"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "x2.decrypt(s)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 29,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 24,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "215.0"
+      ]
+     },
+     "execution_count": 17,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "y.sum().decrypt(s)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "1"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "x.dim()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "y= x2*x"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 28,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "y.encrypted"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "out = y.sum()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "55.0"
+      ]
+     },
+     "execution_count": 30,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "out.decrypt(s)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/Syft - Paillier Homomorphic Encryption Example.ipynb b/notebooks/Syft - Paillier Homomorphic Encryption Example.ipynb
index ef1b000d83..6a095c903f 100644
--- a/notebooks/Syft - Paillier Homomorphic Encryption Example.ipynb
+++ b/notebooks/Syft - Paillier Homomorphic Encryption Example.ipynb
@@ -13,7 +13,9 @@
   {
    "cell_type": "code",
    "execution_count": 1,
-   "metadata": {},
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": [
     "from syft.he.Paillier import KeyPair\n",
@@ -309,7 +311,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.5.3"
+   "version": "3.6.1"
   }
  },
  "nbformat": 4,
diff --git a/requirements.txt b/requirements.txt
old mode 100755
new mode 100644
index 8d165f76a7..2d56f035d8
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,5 +2,6 @@ pyRserve
 numpy
 phe
 line_profiler
-jupyter
 clint
+pytest-flake8
+pytest
diff --git a/setup.cfg b/setup.cfg
index 9af7e6f11b..989acd41a6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,5 @@
 [aliases]
-test=pytest
\ No newline at end of file
+test=pytest
+
+[tool:pytest]
+flake8-ignore = E501
diff --git a/setup.py b/setup.py
index 0a3aff4fae..a2b5266cdd 100755
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,6 @@
 import os
-from setuptools import setup,find_packages
+from setuptools import setup, find_packages
+
 
 # Utility function to read the README file.
 # Used for the long_description.  It's nice, because now 1) we have a top level
@@ -8,17 +9,18 @@ from setuptools import setup,find_packages
 def read(fname):
     return open(os.path.join(os.path.dirname(__file__), fname)).read()
 
+
 requirements = read('requirements.txt').split()
 
 setup(
-    name = "syft",
-    version = "0.1.0",
-    author = "Amber Trask",
-    author_email = "contact@openmined.org",
-    description = ("A library for Homomorphically Encrypted Deep Learning Algorithms"),
-    license = "Apache-2.0",
-    keywords = "deep learning machine artificial intelligence homomorphic encryption",
-    packages=find_packages(exclude=['notebooks', 'test*','dist']),
+    name="syft",
+    version="0.1.0",
+    author="Amber Trask",
+    author_email="contact@openmined.org",
+    description=("A library for Encrypted Deep Learning Algorithms"),
+    license="Apache-2.0",
+    keywords="deep learning artificial intelligence homomorphic encryption",
+    packages=find_packages(exclude=['notebooks', 'test*', 'dist']),
     include_package_data=True,
     long_description=read('README.md'),
     url='github.com/OpenMined/Syft',
@@ -28,5 +30,5 @@ setup(
     scripts=['bin/syft_cmd'],
     install_requires=requirements,
     setup_requires=['pytest-runner'],
-    tests_require=['pytest']
+    tests_require=['pytest', 'pytest-flake8']
 )
diff --git a/syft/__init__.py b/syft/__init__.py
index 67a265d83b..13a92d338a 100644
--- a/syft/__init__.py
+++ b/syft/__init__.py
@@ -1,1 +1,15 @@
-from .tensor import TensorBase
+from syft import he
+from syft import nn
+
+from syft.tensor import equal, TensorBase
+from syft.math import cumprod, cumsum, ceil, dot, matmul, addmm, addcmul
+from syft.math import addcdiv, addmv, addbmm, baddbmm
+from syft.math import unsqueeze
+
+s = str(he)
+s += str(nn)
+
+s += str(equal) + str(TensorBase) + str(cumprod) + str(cumsum) + str(ceil)
+s += str(dot) + str(matmul) + str(addmm) + str(addcmul) + str(addcdiv)
+s += str(addmv) + str(addbmm) + str(baddbmm)
+s += str(unsqueeze)
diff --git a/syft/he/FV.py b/syft/he/FV.py
deleted file mode 100644
index e2b4936743..0000000000
--- a/syft/he/FV.py
+++ /dev/null
@@ -1,230 +0,0 @@
-# import pyRserve,os
-# import random
-# import numpy as np
-# import json
-# import tempfile
-#
-# class SecretKey():
-#     def __init__(self,conn,sk_str,params,r1k_data,sk_data):
-#         self.conn = conn
-#         self.params = params
-#         self.sk_data = sk_data
-#         self.r1k_data = r1k_data
-#         self.sk_str = sk_str
-#
-#     def decrypt(self,x):
-#         """Decrypts x. X can be either an encrypted int or a numpy vector/matrix/tensor."""
-#
-#         if(type(x) == FVInteger):
-#             return self.conn.eval('dec('+str(self.sk_str)+', '+str(x.vector_name)+')')
-#         elif(type(x) == np.ndarray):
-#             sh = x.shape
-#             x_ = x.reshape(-1)
-#             out = list()
-#             for v in x_:
-#                 out.append(self.conn.eval('dec('+str(self.sk_str)+', '+str(v.vector_name)+')'))
-#             return np.array(out).reshape(sh)
-#         else:
-#             return None
-#
-#
-#     def serialize(self):
-#         """Store all the unique information about this key to a json object."""
-#
-#         ser_seckey = {}
-#         ser_seckey['params'] = self.params
-#         ser_seckey['sk_data'] = self.sk_data
-#         ser_seckey['sk_str'] = self.sk_str
-#         ser_seckey['r1k_data'] = self.r1k_data
-#         return json.dumps(ser_seckey)
-#
-# class PublicKey():
-#     def __init__(self,conn,pk_str,params,r1k_data,pk_data):
-#         self.conn = conn
-#         self.params = params
-#         self.pk_data = pk_data
-#         self.r1k_data = r1k_data
-#         self.pk_str = pk_str
-#
-#     def encrypt(self,x=4):
-#         """Encrypts x. X can be either an int or a numpy vector/matrix/tensor."""
-#
-#         if(type(x) == int):
-#             return FVInteger(self.conn,self,x)
-#         elif(type(x) == np.ndarray):
-#             sh = x.shape
-#             x_ = x.reshape(-1)
-#             out = list()
-#             for v in x_:
-#                 out.append(FVInteger(self.conn,self,int(v)))
-#             return np.array(out).reshape(sh)
-#
-#         else:
-#             print("format not recognized")
-#
-#     def serialize(self):
-#         """Store all the unique information about this key to a json object."""
-#
-#         ser_pubkey = {}
-#         ser_pubkey['params'] = self.params
-#         ser_pubkey['pk_data'] = self.pk_data
-#         ser_pubkey['pk_str'] = self.pk_str
-#         ser_pubkey['r1k_data'] = self.r1k_data
-#         return json.dumps(ser_pubkey)
-#
-#
-# class KeyPair():
-#     def __init__(self,conn=None):
-#         """Creates connection to R server and loads HE library containing FV."""
-#
-#         if(conn is None):
-#             self.conn = pyRserve.connect()
-#         else:
-#             self.conn = conn
-#         self.conn.r('library("HomomorphicEncryption")',void=True)
-#
-#     def deserialize(self,pubkey_json,seckey_json=None):
-#         """Converts json objects into public and private keys. If no secret key
-#         json is provided, it only initializes a public one."""
-#
-#         k_str = 'k'+str(random.randint(0,2**32))
-#
-#         pubkey_obj = json.loads(pubkey_json)
-#         self.public_key = PublicKey(self.conn,k_str+'$pk',pubkey_obj['params'],pubkey_obj['r1k_data'],pubkey_obj['pk_data'])
-#
-#         if(seckey_json is not None):
-#             seckey_obj = json.loads(seckey_json)
-#             self.secret_key = SecretKey(self.conn,k_str+'$sk',seckey_obj['params'],seckey_obj['r1k_data'],seckey_obj['sk_data'])
-#             filepath = self.create_file(self.public_key.params,self.public_key.r1k_data,self.public_key.pk_data,self.secret_key.sk_data)
-#         else:
-#             self.secret_key = None
-#             filepath = self.create_file(self.public_key.params,self.public_key.r1k_data,self.public_key.pk_data)
-#
-#         self.conn.eval(self.public_key.pk_str[:-3]+' <- loadFHE(file="'+filepath+'")')
-#         os.remove(filepath)
-#         return (self.public_key,self.secret_key)
-#
-#
-#
-#     def create_file(self,params,r1k,pk,sk=None):
-#         """ Saves keypair to file. If there is no secret key, it saves a random one.
-#
-#         Because we're only wrapping FV instead of re-implementing it (long term
-#         plan), we need to use the filesystem to save/load or serialize/deserialize
-#         keys objects."""
-#
-#
-#         boiler = ['=> FHE pkg obj <=\n',
-#          'FandV_keys\n',
-#          '=> FHE pkg obj <=\n',
-#          'Rcpp_FandV_par\n']
-#         boiler2 = ['=> FHE pkg obj <=\n', 'Rcpp_FandV_rlk\n']
-#         boiler3 = ['=> FHE package object <=\n', 'Rcpp_FandV_pk\n']
-#         boiler4 = ['=> FHE package object <=\n', 'Rcpp_FandV_sk\n']
-#
-#         # if no secret key is deserialized, make a fake one
-#         if(sk is None):
-#             out = "4097  "
-#             for x in (np.random.rand(4097)>0.5).astype('int'):
-#                 out += str(x) + " "
-#             out[:-1]+"\n"
-#             sk = [out]
-#
-#         raw = boiler + params + boiler2 + r1k + boiler3 + pk + boiler4 + sk
-#
-#         tmp_file = tempfile.mktemp()
-#         f = open(tmp_file,'w')
-#         f.writelines(raw)
-#         f.close()
-#
-#         return tmp_file
-#
-#
-#     def generate(self):
-#         """Using connection created in __init__, it initializes a new keypair."""
-#
-#         lambd=80
-#         L=4
-#
-#         k_str = 'k'+str(random.randint(0,2**32))
-#
-#         tmp_file = tempfile.mktemp()
-#
-#         p = self.conn.eval('p <- pars("FandV", lambda='+str(lambd)+', L='+str(L)+')')
-#         k = self.conn.eval(k_str+' <- keygen(p)')
-#         self.conn.eval('saveFHE('+str(k_str)+',file="'+tmp_file+'")')
-#
-#         f = open(tmp_file,'r')
-#         raw = f.readlines()
-#         f.close()
-#
-#         # boilerplate code is all the "boiler" variables
-#         boiler = raw[0:4]
-#         params = raw[4:10]
-#         boiler2 = raw[10:12]
-#         r1k = raw[12:16]
-#         boiler3 = raw[16:18]
-#         pk = raw[18:20]
-#         boiler4 = raw[20:22]
-#         sk = raw[22:23]
-#
-#         self.secret_key = SecretKey(self.conn,k_str+'$sk',params,r1k,sk)
-#         self.public_key = PublicKey(self.conn,k_str+'$pk',params,r1k,pk)
-#
-#         os.remove(tmp_file)
-#         return (self.public_key,self.secret_key)
-#
-# class FVInteger():
-#
-#     def __init__(self,conn,public_key,data=None):
-#         """Wraps pointer to encrypted integer with an interface that numpy can use."""
-#
-#         self.conn = conn
-#         self.vector_name ='c'+str(random.randint(0,2**32))
-#         self.public_key = public_key
-#         self.length = 1
-#         if(data is not None):
-#             self.conn.eval(self.vector_name+' <- enc('+str(self.public_key.pk_str)+', c('+str(data)+'))')
-#
-#     def __add__(self,y):
-#         """Adds two encrypted integers together."""
-#
-#         vector_name='c'+str(random.randint(0,2**32))
-#         out = FVInteger(self.conn,self.public_key)
-#         self.conn.eval(out.vector_name+' <- '+self.vector_name+' + '+y.vector_name)
-#         return out
-#
-#     def __sub__(self,y):
-#         """Subtracts two encrypted integers."""
-#
-#         vector_name='c'+str(random.randint(0,2**32))
-#         out = FVInteger(self.conn,self.public_key)
-#         self.conn.eval(out.vector_name+' <- '+self.vector_name+' - '+y.vector_name)
-#         return out
-#
-#     def __mul__(self,y):
-#         """Multiplies two integers. y may be encrypted or a simple integer."""
-#
-#         vector_name='c'+str(random.randint(0,2**32))
-#         out = FVInteger(self.conn,self.public_key)
-#
-#         # if y is encrypted
-#         if(type(y) == type(self)):
-#             self.conn.eval(out.vector_name+' <- '+self.vector_name+' * '+y.vector_name)
-#
-#         # if y is an integer.
-#         elif(type(y) == int):
-#             adds = '+'+self.vector_name
-#
-#             self.conn.eval(out.vector_name+' <- ('+self.vector_name+(adds*(y-1)) + ")")
-#         return out
-#
-#     def __repr__(self):
-#         """This is kindof a boring/uninformative __repr__"""
-#
-#         return 'e'
-#
-#     def __str__(self):
-#         """This is kindof a boring/uninformative __str__"""
-#
-#         return 'e'
diff --git a/syft/he/Paillier.py b/syft/he/Paillier.py
deleted file mode 100644
index 7d97ae3e97..0000000000
--- a/syft/he/Paillier.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import phe as paillier
-import numpy as np
-import pickle
-
-class SecretKey():
-
-    def __init__(self,sk):
-        self.sk = sk
-
-    def decrypt(self,x):
-        """Decrypts x. X can be either an encrypted int or a numpy vector/matrix/tensor."""
-        if(type(x) == PaillierFloat):
-            return self.sk.decrypt(x)
-        elif(type(x) == np.ndarray):
-            sh = x.shape
-            x_ = x.reshape(-1)
-            out = list()
-            for v in x_:
-                out.append(self.sk.decrypt(v.data))
-            return np.array(out).reshape(sh)
-
-    def serialize(self):
-        return pickle.dumps(self.sk)
-
-class PublicKey():
-
-    def __init__(self,pk):
-        self.pk = pk
-
-    def encrypt(self,x):
-        """Encrypts x. X can be either an encrypted int or a numpy vector/matrix/tensor."""
-        if(type(x) == int):
-            return PaillierFloat(self,x)
-        elif(type(x) == np.ndarray):
-            sh = x.shape
-            x_ = x.reshape(-1)
-            out = list()
-            for v in x_:
-                out.append(PaillierFloat(self,v))
-            return np.array(out).reshape(sh)
-
-        else:
-            print("format not recognized")
-
-        return self.pk.encrypt(x)
-
-    def serialize(self):
-        return pickle.dumps(self.pk)
-
-class KeyPair():
-
-    def __init__(self):
-        ""
-
-    def deserialize(self,pubkey,seckey):
-        self.public_key = PublicKey(pickle.loads(pubkey))
-        self.secret_key = SecretKey(pickle.loads(seckey))
-        return (self.public_key, self.secret_key)
-
-    def generate(self,n_length=1024):
-        pubkey, prikey = paillier.generate_paillier_keypair(n_length=n_length)
-        self.public_key = PublicKey(pubkey)
-        self.secret_key = SecretKey(prikey)
-
-        return (self.public_key,self.secret_key)
-
-class PaillierFloat():
-
-    def __init__(self,public_key,data=None):
-        """Wraps pointer to encrypted integer with an interface that numpy can use."""
-
-        self.public_key = public_key
-        if(data is not None):
-            self.data = self.public_key.pk.encrypt(data)
-        else:
-            self.data = None
-
-    def __add__(self,y):
-        """Adds two encrypted integers together."""
-
-        out = PaillierFloat(self.public_key,None)
-        out.data = self.data + y.data
-        return out
-
-    def __sub__(self,y):
-        """Subtracts two encrypted integers."""
-
-        out = PaillierFloat(self.public_key, None)
-        out.data = self.data - y.data
-        return out
-
-    def __mul__(self,y):
-        """Multiplies two integers. y may be encrypted or a simple integer."""
-
-        if(type(y) == type(self)):
-            out = PaillierFloat(self.public_key, None)
-            out.data = self.data * y.data
-            return out
-        elif(type(y) == int or type(y) == float):
-            out = PaillierFloat(self.public_key, None)
-            out.data = self.data * y
-            return out
-        else:
-            return None
-
-    def __truediv__(self,y):
-        """Divides two integers. y may be encrypted or a simple integer."""
-
-        if(type(y) == type(self)):
-            out = PaillierFloat(self.public_key, None)
-            out.data = self.data / y.data
-            return out
-        elif(type(y) == int):
-            out = PaillierFloat(self.public_key, None)
-            out.data = self.data / y
-            return out
-        else:
-            return None
-
-    def __repr__(self):
-        """This is kindof a boring/uninformative __repr__"""
-
-        return 'e'
-
-    def __str__(self):
-        """This is kindof a boring/uninformative __str__"""
-
-        return 'e'
diff --git a/syft/he/__init__.py b/syft/he/__init__.py
index e69de29bb2..f626644aa1 100644
--- a/syft/he/__init__.py
+++ b/syft/he/__init__.py
@@ -0,0 +1,5 @@
+from . import paillier
+from . import abstract
+
+s = str(paillier)
+s += str(abstract)
diff --git a/syft/he/abstract/__init__.py b/syft/he/abstract/__init__.py
new file mode 100644
index 0000000000..92cf078721
--- /dev/null
+++ b/syft/he/abstract/__init__.py
@@ -0,0 +1,4 @@
+from . import keys
+
+s = str(keys)
+s += "asdf"
diff --git a/syft/he/abstract/keys.py b/syft/he/abstract/keys.py
new file mode 100644
index 0000000000..3afadfab7c
--- /dev/null
+++ b/syft/he/abstract/keys.py
@@ -0,0 +1,34 @@
+class AbstractSecretKey():
+
+    def __init__(self, sk):
+        return NotImplemented
+
+    def decrypt(self, x):
+        return NotImplemented
+
+    def serialize(self):
+        return NotImplemented
+
+
+class AbstractPublicKey():
+
+    def __init__(self, pk):
+        return NotImplemented
+
+    def encrypt(self, x):
+        return NotImplemented
+
+    def serialize(self):
+        return NotImplemented
+
+
+class AbstractKeyPair():
+
+    def __init__(self):
+        ""
+
+    def deserialize(self, pubkey, seckey):
+        return NotImplemented
+
+    def generate(self, n_length=1024):
+        return NotImplemented
diff --git a/syft/he/paillier/__init__.py b/syft/he/paillier/__init__.py
new file mode 100644
index 0000000000..8a1f99603f
--- /dev/null
+++ b/syft/he/paillier/__init__.py
@@ -0,0 +1,5 @@
+from .basic import PaillierTensor, Float
+from .keys import SecretKey, PublicKey, KeyPair
+
+s = str(PaillierTensor) + str(Float) + str(SecretKey) + str(PublicKey)
+s += str(KeyPair)
diff --git a/syft/he/paillier/basic.py b/syft/he/paillier/basic.py
new file mode 100644
index 0000000000..2720d24919
--- /dev/null
+++ b/syft/he/paillier/basic.py
@@ -0,0 +1,118 @@
+import numpy as np
+from ...tensor import TensorBase
+
+
+class PaillierTensor(TensorBase):
+
+    def __init__(self, public_key, data=None, input_is_decrypted=True):
+        self.encrypted = True
+
+        self.public_key = public_key
+        if(type(data) == np.ndarray and input_is_decrypted):
+            self.data = public_key.encrypt(data, True)
+        else:
+            self.data = data
+
+    def __add__(self, tensor):
+        """Performs element-wise addition between two tensors"""
+
+        if(not isinstance(tensor, TensorBase)):
+            # try encrypting it
+            tensor = self.public_key.encrypt(tensor)
+            return PaillierTensor(self.public_key, self.data + tensor, False)
+
+        if(type(tensor) == TensorBase):
+            tensor = PaillierTensor(self.public_key, tensor.data)
+
+        return PaillierTensor(self.public_key, self.data + tensor.data, False)
+
+    def __mul__(self, tensor):
+        """Performs element-wise addition between two tensors"""
+
+        if(isinstance(tensor, TensorBase)):
+            if(not tensor.encrypted):
+                result = self.data * tensor.data
+                o = PaillierTensor(self.public_key, result, False)
+                return o
+            else:
+                return NotImplemented
+        else:
+            return PaillierTensor(self.public_key, self.data * tensor, False)
+
+    def sum(self, dim=None):
+        """Returns the sum of all elements in the input array."""
+        if not self.encrypted:
+            return NotImplemented
+
+        if dim is None:
+            return self.data.sum()
+        else:
+            return self.data.sum(axis=dim)
+
+
+class Float():
+
+    def __init__(self, public_key, data=None):
+        """Wraps pointer to encrypted Float with an interface that numpy
+        can use."""
+
+        self.public_key = public_key
+        if(data is not None):
+            self.data = self.public_key.pk.encrypt(data)
+        else:
+            self.data = None
+
+    def decrypt(self, secret_key):
+        return secret_key.decrypt(self)
+
+    def __add__(self, y):
+        """Adds two encrypted Floats together."""
+
+        out = Float(self.public_key, None)
+        out.data = self.data + y.data
+        return out
+
+    def __sub__(self, y):
+        """Subtracts two encrypted Floats."""
+
+        out = Float(self.public_key, None)
+        out.data = self.data - y.data
+        return out
+
+    def __mul__(self, y):
+        """Multiplies two Floats. y may be encrypted or a simple Float."""
+
+        if(type(y) == type(self)):
+            out = Float(self.public_key, None)
+            out.data = self.data * y.data
+            return out
+        elif(type(y) == int or type(y) == float):
+            out = Float(self.public_key, None)
+            out.data = self.data * y
+            return out
+        else:
+            return None
+
+    def __truediv__(self, y):
+        """Divides two Floats. y may be encrypted or a simple Float."""
+
+        if(type(y) == type(self)):
+            out = Float(self.public_key, None)
+            out.data = self.data / y.data
+            return out
+        elif(type(y) == int):
+            out = Float(self.public_key, None)
+            out.data = self.data / y
+            return out
+        else:
+            return None
+
+    def __repr__(self):
+        """This is kindof a boring/uninformative __repr__"""
+
+        return 'e'
+
+    def __str__(self):
+        """This is kindof a boring/uninformative __str__"""
+
+        return 'e'
diff --git a/syft/he/paillier/keys.py b/syft/he/paillier/keys.py
new file mode 100644
index 0000000000..20eeb20e8c
--- /dev/null
+++ b/syft/he/paillier/keys.py
@@ -0,0 +1,91 @@
+import phe as paillier
+import numpy as np
+import pickle
+from .basic import Float, PaillierTensor
+from ...tensor import TensorBase
+from ..abstract.keys import AbstractSecretKey, AbstractPublicKey
+from ..abstract.keys import AbstractKeyPair
+
+
+class SecretKey(AbstractSecretKey):
+
+    def __init__(self, sk):
+        self.sk = sk
+
+    def decrypt(self, x):
+        """Decrypts x. X can be either an encrypted int or a numpy
+        vector/matrix/tensor."""
+
+        if(type(x) == Float):
+            return self.sk.decrypt(x.data)
+        elif(type(x) == TensorBase or type(x) == PaillierTensor):
+            if(x.encrypted):
+                return TensorBase(self.decrypt(x.data), encrypted=False)
+            else:
+                return NotImplemented
+        elif(type(x) == np.ndarray):
+            sh = x.shape
+            x_ = x.reshape(-1)
+            out = list()
+            for v in x_:
+                out.append(self.sk.decrypt(v.data))
+            return np.array(out).reshape(sh)
+        else:
+            return NotImplemented
+
+    def serialize(self):
+        return pickle.dumps(self.sk)
+
+
+class PublicKey(AbstractPublicKey):
+
+    def __init__(self, pk):
+        self.pk = pk
+
+    def encrypt(self, x, same_type=False):
+        """Encrypts x. X can be either an encrypted int or a numpy
+        vector/matrix/tensor."""
+        if(type(x) == int):
+            if(same_type):
+                return NotImplemented
+            return Float(self, x)
+        elif(type(x) == TensorBase):
+            if(x.encrypted or same_type):
+                return NotImplemented
+            return PaillierTensor(self, x.data)
+        elif(type(x) == np.ndarray):
+            sh = x.shape
+            x_ = x.reshape(-1)
+            out = list()
+            for v in x_:
+                out.append(Float(self, v))
+            if(same_type):
+                return np.array(out).reshape(sh)
+            else:
+                return PaillierTensor(self, np.array(out).reshape(sh))
+        else:
+            print("format not recognized")
+            return NotImplemented
+
+        return self.pk.encrypt(x)
+
+    def serialize(self):
+        return pickle.dumps(self.pk)
+
+
+class KeyPair(AbstractKeyPair):
+
+    def __init__(self):
+        ""
+
+    def deserialize(self, pubkey, seckey):
+        self.public_key = PublicKey(pickle.loads(pubkey))
+        self.secret_key = SecretKey(pickle.loads(seckey))
+        return (self.public_key, self.secret_key)
+
+    def generate(self, n_length=1024):
+        pubkey, prikey = paillier.generate_paillier_keypair(n_length=n_length)
+        self.public_key = PublicKey(pubkey)
+        self.secret_key = SecretKey(prikey)
+
+        return (self.public_key, self.secret_key)
diff --git a/syft/math.py b/syft/math.py
new file mode 100644
index 0000000000..0f8cf7c93c
--- /dev/null
+++ b/syft/math.py
@@ -0,0 +1,280 @@
+"""
+    Module math implements mathematical primitives for tensor objects
+"""
+import numpy as np
+
+from .tensor import TensorBase
+from .tensor import _ensure_tensorbase
+
+__all__ = [
+    'cumprod', 'cumsum', 'ceil', 'dot', 'floor', 'matmul', 'addmm', 'addcmul',
+    'addcdiv', 'addmv', 'addbmm', 'baddbmm', 'sigmoid', 'unsqueeze'
+]
+
+
+def dot(tensor1, tensor2):
+    """Returns inner product of two tensors.
+
+    N-dimensional tensors are flattened into 1-D vectors, therefore this
+    method should only be used on vectors.
+    """
+
+    tensor1 = _ensure_tensorbase(tensor1)
+    tensor2 = _ensure_tensorbase(tensor2)
+
+    if tensor1.encrypted is True or tensor2.encrypted is True:
+        return NotImplemented
+    return np.vdot(tensor1.data, tensor2.data)
+
+
+def matmul(tensor1, tensor2):
+    """Performs matrix multiplication between two tensors.
+
+    Exact behavior depends on the input tensors' dimensionality like so:
+    * If both tensors are 1-dimensional, their dot product is returned.
+    * If both tensors are 2-dimensional, their matrix-matrix product is
+    returned.
+
+    * If either tensor has dimensionality > 2, the last 2 dimensions are
+    treated as matrices and multiplied.
+
+    * If tensor1 is 1-dimensional, it is converted to a matrix by prepending
+    a 1 to its dimensions. This prepended dimension is removed after the
+    matrix multiplication.
+
+    * If tensor2 is 1-dimensional, it is converted to a matrix by prepending
+    a 1 to its dimensions. This prepended dimension is removed after the
+    matrix multiplication.
+    """
+
+    tensor1 = _ensure_tensorbase(tensor1)
+    tensor2 = _ensure_tensorbase(tensor2)
+
+    if tensor1.encrypted is True or tensor2.encrypted is True:
+        return NotImplemented
+
+    if tensor1.dim() == 1 and tensor2.dim() == 1:
+        return dot(tensor1, tensor2)
+    else:
+        return TensorBase(np.matmul(tensor1.data, tensor2.data))
+
+
+def ceil(tensor):
+    """
+    Returns the ceilling input tensor,element wise .
+
+    Ceilling of an input scalar is the smallest integer such as :
+    for each floating point number x : a >= x
+
+    Behavior is independent of a tensor's shape.
+
+    :input: TensorBase tensor\n
+    :return: TensorBase tensor
+    """
+
+    tensor = _ensure_tensorbase(tensor)
+    if tensor.encrypted is True:
+        return NotImplemented
+    return TensorBase(np.ceil(tensor.data))
+
+
+def floor(tensor):
+    """
+    Returns the floored input tensor,element wise.
+    Floor of an input scalar is the largest integer such as:
+    for each floating point number x : a <= x
+
+    Behavior is independent of a tensor's shape
+    :input: TensorBase tensor\n
+    :return: TensorBase tensor of floored elements .
+    """
+
+    tensor = _ensure_tensorbase(tensor)
+    if tensor.encrypted is True:
+        return NotImplemented
+    return TensorBase(np.floor(tensor.data))
+
+
+def cumsum(tensor, dim=0):
+    """
+    Returns the cumulative sum of the elements along a given dimension
+
+    **Parameters**:
+    * TensorBase tensor
+    * Dimension on which the operation is done
+
+    **returns**  A new 1D Tensor holding the result
+    """
+    tensor = _ensure_tensorbase(tensor)
+    if tensor.encrypted is True:
+        return NotImplemented
+    return TensorBase(np.cumsum(tensor.data, dim))
+
+
+def cumprod(tensor, dim=0):
+    """
+    Returns the cumulative product of the elements along a given axis
+
+    **Parameters**:
+    * TensorBase tensor
+    * Dimension on which the operation is done
+
+    **returns** A new 1D Tensor holding the result
+    """
+
+    tensor = _ensure_tensorbase(tensor)
+    if tensor.encrypted is True:
+        return NotImplemented
+    return TensorBase(np.cumprod(tensor.data, dim))
+
+
+def sigmoid(tensor):
+    """ Returns a new tensor holding element wise values of Sigmoid function
+        Sigmoid(x) = 1 / 1+exp(-x)
+    """
+    tensor = _ensure_tensorbase(tensor)
+    if tensor.encrypted is True:
+        return NotImplemented
+    return TensorBase(1 / (1 + np.exp(np.array(-tensor.data))))
+
+
+def addmm(tensor1, 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.
+        """
+    _ensure_tensorbase(tensor1)
+    _ensure_tensorbase(tensor2)
+    _ensure_tensorbase(mat)
+    if tensor1.encrypted or tensor2.encrypted or mat.encrypted:
+        return NotImplemented
+    else:
+        delta = (np.matmul(tensor1.data, tensor2.data))
+        return TensorBase(np.array(((mat.data) * beta) + (delta * alpha)))
+
+
+def addcmul(tensor1, tensor2, mat, value=1):
+    """Performs the element-wise multiplication of tensor1 by tensor2,
+    multiply the result by the scalar value and add it to mat."""
+    _ensure_tensorbase(tensor1)
+    _ensure_tensorbase(tensor2)
+    _ensure_tensorbase(mat)
+    if tensor1.encrypted or tensor2.encrypted or mat.encrypted:
+        return NotImplemented
+    else:
+        out = (mat.data) + ((tensor1.data * tensor2.data) * value)
+        return TensorBase(out)
+
+
+def addcdiv(tensor1, tensor2, mat, value=1):
+    """Performs the element-wise division of tensor1 by tensor2, multiply
+    the result by the scalar value and add it to mat."""
+    _ensure_tensorbase(tensor1)
+    _ensure_tensorbase(tensor2)
+    _ensure_tensorbase(mat)
+    if tensor1.encrypted or tensor2.encrypted or mat.encrypted:
+        return NotImplemented
+    else:
+        out = (mat.data) + ((tensor1.data / tensor2.data) * value)
+        return TensorBase(out)
+
+
+def addmv(tensor1, mat, vec, beta=1, alpha=1):
+    """"Performs a matrix-vector product of the matrix mat and the vector vec.
+    The vector tensor is added to the final result.
+          tensor1 and vec are 1d tensors
+          out=(beta∗tensor)+(alpha∗(mat@vec2))"""
+    _ensure_tensorbase(tensor1)
+    _ensure_tensorbase(vec)
+    _ensure_tensorbase(mat)
+    if vec.data.ndim != 1:
+        print("dimension of vec is not 1")
+    elif tensor1.data.ndim != 1:
+        print("dimension of vec is not 1")
+    elif tensor1.encrypted or vec.encrypted or mat.encrypted:
+        return NotImplemented
+    else:
+        out = (tensor1.data * beta) + (np.matmul(mat.data, vec.data) * alpha)
+        return TensorBase(out)
+
+
+def addbmm(tensor1, tensor2, mat, beta=1, alpha=1):
+    """Performs a batch matrix-matrix product of matrices stored in
+    batch1(tensor1) and batch2(tensor2),
+     with a reduced add step (all matrix multiplications get accumulated along
+     the first dimension).
+     mat is added to the final result.
+     res=(beta∗M)+(alpha∗sum(batch1i@batch2i, i=0, b))
+    * batch1 and batch2 must be 3D Tensors each containing the same number of
+    matrices."""
+    _ensure_tensorbase(tensor1)
+    _ensure_tensorbase(tensor2)
+    _ensure_tensorbase(mat)
+    if tensor2.data.ndim != 3:
+        print("dimension of tensor2 is not 3")
+    elif tensor1.data.ndim != 3:
+        print("dimension of tensor1 is not 3")
+    elif tensor1.encrypted or tensor2.encrypted or mat.encrypted:
+        return NotImplemented
+    else:
+        mmul = np.matmul(tensor1.data, tensor2.data)
+        sum_ = 0  # sum is a built in python function
+        for i, _ in enumerate(mmul):
+            sum_ += mmul[i]
+        out = (mat.data * beta) + (alpha * sum_)
+        return TensorBase(out)
+
+
+def baddbmm(tensor1, tensor2, mat, beta=1, alpha=1):
+    """Performs a batch matrix-matrix product of matrices in batch1(tensor1)
+    and batch2(tensor2). mat is added to the final result.
+      resi=(beta∗Mi)+(alpha∗batch1i×batch2i)
+      *batch1 and batch2 must be 3D Tensors each containing the same number
+      of matrices."""
+    _ensure_tensorbase(tensor1)
+    _ensure_tensorbase(tensor2)
+    _ensure_tensorbase(mat)
+    if tensor2.data.ndim != 3:
+        print("dimension of tensor2 is not 3")
+    elif tensor1.data.ndim != 3:
+        print("dimension of tensor1 is not 3")
+    elif mat.data.ndim != 3:
+        print("dimension of mat is not 3")
+    elif tensor1.encrypted or tensor2.encrypted or mat.encrypted:
+        return NotImplemented
+    else:
+        mmul = np.matmul(tensor1.data, tensor2.data)
+        out = (mat.data * beta) + (mmul * alpha)
+        return TensorBase(out)
+
+
+def unsqueeze(tensor1, dim):
+    """
+    Performs 'unsqueeze' operation, returning a new tensor with a dimension
+    of size one inserted at the specified position.
+    """
+    tensor1 = _ensure_tensorbase(tensor1)
+    num_dims = len(tensor1.data.shape)
+
+    if dim >= num_dims or dim < 0:
+        print("dimension out of range")
+    elif tensor1.encrypted:
+        raise NotImplemented
+    else:
+        return TensorBase(np.expand_dims(tensor1.data, dim))
diff --git a/syft/nn/__init__.py b/syft/nn/__init__.py
index e69de29bb2..79a39671f6 100644
--- a/syft/nn/__init__.py
+++ b/syft/nn/__init__.py
@@ -0,0 +1,3 @@
+from . import linear
+n = linear.LinearClassifier()
+n.weights = list()
diff --git a/syft/nn/convnet.py b/syft/nn/convnet.py
index 0ec140be30..8783cfecc1 100644
--- a/syft/nn/convnet.py
+++ b/syft/nn/convnet.py
@@ -1,1 +1,1 @@
-#TODO: build this
+# TODO: build this
diff --git a/syft/nn/fasttext.py b/syft/nn/fasttext.py
index 0ec140be30..8783cfecc1 100644
--- a/syft/nn/fasttext.py
+++ b/syft/nn/fasttext.py
@@ -1,1 +1,1 @@
-#TODO: build this
+# TODO: build this
diff --git a/syft/nn/linear.py b/syft/nn/linear.py
index 11bdd034f3..85e326914f 100644
--- a/syft/nn/linear.py
+++ b/syft/nn/linear.py
@@ -1,20 +1,20 @@
-from syft import he
 import numpy as np
 
+
 class LinearClassifier():
-    """This class is a basic linear classifier with functionality to encrypt/decrypt
-    weights according to any of the homomorphic encryption schemes in syft.he. It also
-    contains the logic to make predictions when in an encrypted state.
+    """This class is a basic linear classifier with functionality to
+    encrypt/decrypt weights according to any of the homomorphic encryption
+    schemes in syft.he. It also contains the logic to make predictions when
+    in an encrypted state.
 
     TODO: create a generic interface for ML models that this class implements.
 
     TODO: minibatching in the "learn" API. The greater the batch size, the more
-    iterations should be doable before the homomorphic encryption noise grows too
-    much to be decrypted.
+    iterations should be doable before the homomorphic encryption noise grows
+    too much to be decrypted.
     """
 
-
-    def __init__(self,n_inputs=4,n_labels=2,desc=""):
+    def __init__(self, n_inputs=4, n_labels=2, desc=""):
 
         self.desc = desc
 
@@ -28,7 +28,7 @@ class LinearClassifier():
         self.pubkey = None
         self.encrypted = False
 
-    def encrypt(self,pubkey):
+    def encrypt(self, pubkey):
         """iterates through each weight and encrypts it
 
         TODO: check that weights are actually decrypted
@@ -37,42 +37,42 @@ class LinearClassifier():
 
         self.encrypted = True
         self.pubkey = pubkey
-        for i,w in enumerate(self.weights):
+        for i, w in enumerate(self.weights):
             self.weights[i] = self.pubkey.encrypt(w)
         return self
 
-    def decrypt(self,seckey):
+    def decrypt(self, seckey):
         """iterates through each weight and decrypts it
 
         TODO: check that weights are actually encrypted
 
         """
         self.encrypted = False
-        for i,w in enumerate(self.weights):
+        for i, w in enumerate(self.weights):
             self.weights[i] = seckey.decrypt(w)
         return self
 
-    def forward(self,input):
+    def forward(self, input):
 
         """Makes a prediction based on input. If the network is encrypted, the
         prediction is also encrypted and vise versa."""
 
         pred = self.weights[0] * input[0]
-        for j,each_inp in enumerate(input[1:]):
+        for j, each_inp in enumerate(input[1:]):
             if(each_inp == 1):
-                pred = pred + self.weights[j+1]
+                pred = pred + self.weights[j + 1]
             elif(each_inp != 0):
-                pred = pred + (self.weights[j+1] * input[j+1])
+                pred = pred + (self.weights[j + 1] * input[j + 1])
 
         return pred
 
-    def generate_gradient(self,input,target):
+    def generate_gradient(self, input, target):
         target = np.array(target).astype('float64')
         pred = self.forward(input)
 
         target_v = target
 
-        if(self.pubkey is not None and self.encrypted == True):
+        if(self.pubkey is not None and self.encrypted):
             target_v = self.pubkey.encrypt(target_v)
 
         output_grad = (pred - target_v)
@@ -92,11 +92,10 @@ class LinearClassifier():
 
         return weight_grad
 
-
-    def learn(self,input,target,alpha=0.5):
+    def learn(self, input, target, alpha=0.5):
         """Updates weights based on input and target prediction. Note, updating
-        weights increases the noise in the encrypted weights and will eventually
-        require the weights to be re-encrypted.
+        weights increases the noise in the encrypted weights and will
+        eventually require the weights to be re-encrypted.
 
         TODO: minibatching
         TODO: instead of storing weights, store aggregated weight updates (and
@@ -104,31 +103,31 @@ class LinearClassifier():
 
         """
 
-        weight_update = self.generate_gradient(input,target)
+        weight_update = self.generate_gradient(input, target)
         self.weights -= weight_update * alpha
 
         return weight_update
 
-    def evaluate(self,inputs,targets):
-        """accepts a list of inputs and a list of targets - returns the mean squared
-        error scaled by a fixed amount and converted to an integer."""
+    def evaluate(self, inputs, targets):
+        """accepts a list of inputs and a list of targets - returns the mean
+        squared error scaled by a fixed amount and converted to an integer."""
 
         scale = 1000
 
-        if(self.encrypted == True):
+        if(self.encrypted):
             return "not yet supported... but will in the future"
         else:
 
             loss = 0
-            for i,row in enumerate(inputs):
+            for i, row in enumerate(inputs):
                 pred = self.forward(row)
                 true = targets[i]
                 loss += (pred - true)**2
-            return int((loss[0]*scale)/float(len(inputs)))
-
+            return int((loss[0] * scale) / float(len(inputs)))
 
     def __str__(self):
-        return "Linear Model ("+str(self.n_inputs)+","+str(self.n_labels)+"): " + str(self.desc)
+        left = "Linear Model (" + str(self.n_inputs) + ","
+        return left + str(self.n_labels) + "): " + str(self.desc)
 
     def __repr__(self):
         return self.__str__()
diff --git a/syft/nn/rl.py b/syft/nn/rl.py
index 2708f54e00..8783cfecc1 100644
--- a/syft/nn/rl.py
+++ b/syft/nn/rl.py
@@ -1,1 +1,1 @@
-# TODO: build this 
+# TODO: build this
diff --git a/syft/nn/word2vec.py b/syft/nn/word2vec.py
index 82c873265f..b1d67f003e 100644
--- a/syft/nn/word2vec.py
+++ b/syft/nn/word2vec.py
@@ -1,1 +1,1 @@
-#TOOD: build this
+# TOOD: build this
diff --git a/syft/tensor.py b/syft/tensor.py
index 6d1ce8dc30..ee9cf8715d 100644
--- a/syft/tensor.py
+++ b/syft/tensor.py
@@ -1,4 +1,10 @@
 import numpy as np
+import syft
+
+__all__ = [
+    'equal', 'TensorBase',
+]
+
 
 def _ensure_ndarray(arr):
     if not isinstance(arr, np.ndarray):
@@ -6,80 +12,547 @@ def _ensure_ndarray(arr):
 
     return arr
 
+
+def _ensure_tensorbase(tensor):
+    if not isinstance(tensor, TensorBase):
+        tensor = TensorBase(tensor)
+
+    return tensor
+
+
+def equal(tensor1, tensor2):
+    """Checks if two tensors are equal.
+
+    Two tensors are considered equal if they are the same size and contain the
+    same elements.
+
+    Assumption:
+    tensor1 and tensor2 are of type TensorBase.
+    Non-TensorBase objects will be converted to TensorBase objects.
+    """
+
+    tensor1 = _ensure_tensorbase(tensor1)
+    tensor2 = _ensure_tensorbase(tensor2)
+
+    if tensor1.encrypted or tensor2.encrypted:
+        return NotImplemented
+
+    left = tensor1.data.shape == tensor2.data.shape
+    right = np.allclose(tensor1.data, tensor2.data)
+    return left and right
+
+
 class TensorBase(object):
     """
-    A base tensor class that perform basic element-wise operation such as 
-    addition, subtraction, multiplication and division
+    A base tensor class that performs basic element-wise operation such as
+    addition, subtraction, multiplication and division, and also dot and
+    matrix products.
     """
 
     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"""
+    def encrypt(self, pubkey):
+        """Encrypts the Tensor using a Public Key"""
         if self.encrypted:
             return NotImplemented
+        else:
+            return pubkey.encrypt(self)
 
-        arr_like = _ensure_ndarray(arr_like)
-        return self.data + arr_like
+    def decrypt(self, seckey):
+        """Decrypts the tensor using a Secret Key"""
+        if self.encrypted:
+            return seckey.decrypt(self)
+        else:
+            return self
 
-    def __iadd__(self, arr_like):
-        """Performs in place element-wise addition between two array like objects"""
+    def __add__(self, tensor):
+        """Performs element-wise addition between two tensors"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        self.data = self.data + arr_like
-        return self.data
+        tensor = _ensure_tensorbase(tensor)
+        return TensorBase(self.data + tensor.data)
 
-    def __sub__(self, arr_like):
-        """Performs element-wise subtraction between two array like objects"""
+    def __iadd__(self, tensor):
+        """Performs in place element-wise addition between two tensors"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        return self.data - arr_like
+        tensor = _ensure_tensorbase(tensor)
+        self.data += tensor.data
+        return self
 
-    def __isub__(self, arr_like):
-        """Performs in place element-wise subtraction between two array like objects"""
+    def __sub__(self, tensor):
+        """Performs element-wise subtraction between two tensors"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        self.data = self.data - arr_like
-        return self.data
+        tensor = _ensure_tensorbase(tensor)
+        return TensorBase(self.data - tensor.data)
 
-    def __mul__(self, arr_like):
-        """Performs element-wise multiplication between two array like objects"""
+    def __isub__(self, tensor):
+        """Performs in place element-wise subtraction between two tensors"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        return self.data * arr_like
+        tensor = _ensure_tensorbase(tensor)
+        self.data -= tensor.data
+        return self
 
-    def __imul__(self, arr_like):
-        """Performs in place element-wise multiplication between two array like objects"""
+    def __eq__(self, tensor):
+        """Checks if two tensors are equal"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        self.data = self.data * arr_like
-        return self.data
+        return syft.equal(self, tensor)
 
-    def __truediv__(self, arr_like):
-        """Performs element-wise division between two array like objects"""
+    def dot(self, tensor):
+        """Returns inner product of two tensors"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        return self.data / arr_like
+        return syft.dot(self, tensor)
 
-    def __itruediv__(self, arr_like):
-        """Performs in place element-wise subtraction between two array like objects"""
+    def __matmul__(self, tensor):
+        """Performs matrix multiplication between two tensors"""
         if self.encrypted:
             return NotImplemented
 
-        arr_like = _ensure_ndarray(arr_like)
-        self.data = self.data / arr_like
+        return syft.matmul(self, tensor)
+
+    def __mul__(self, tensor):
+        """Performs element-wise multiplication between two tensors"""
+
+        if self.encrypted:
+            return NotImplemented
+
+        # if it's a sub-class of TensorBase, use the multiplication of that
+        # subclass not this one.
+        if(type(tensor) != TensorBase and isinstance(tensor, TensorBase)):
+            return tensor * self
+        else:
+            tensor = _ensure_tensorbase(tensor)
+            return TensorBase(tensor.data * self.data)
+
+    def __imul__(self, tensor):
+        """Performs in place element-wise multiplication between two tensors"""
+        if self.encrypted:
+            return NotImplemented
+
+        if(type(tensor) != TensorBase and isinstance(tensor, TensorBase)):
+            self.data = tensor.data * self.data
+            self.encrypted = tensor.encrypted
+        else:
+            tensor = _ensure_tensorbase(tensor)
+            self.data *= tensor.data
+        return self
+
+    def __truediv__(self, tensor):
+        """Performs element-wise division between two tensors"""
+        if self.encrypted:
+            return NotImplemented
+
+        tensor = _ensure_tensorbase(tensor)
+        return TensorBase(self.data / tensor.data)
+
+    def __itruediv__(self, tensor):
+        """Performs in place element-wise subtraction between two tensors"""
+        if self.encrypted:
+            return NotImplemented
+
+        tensor = _ensure_tensorbase(tensor)
+        self.data /= tensor.data
+        return self
+
+    def __getitem__(self, position):
+        """Get value at a specific index."""
+        if self.encrypted:
+            return NotImplemented
+        else:
+            out = self.data[position]
+            if(len(self.shape()) == 1):
+                return out
+            else:
+                return TensorBase(self.data[position], self.encrypted)
+
+    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:
+            return NotImplemented
+
+        return self.data.shape
+
+    def sqrt(self):
+        """Returns the squared tensor."""
+        if self.encrypted:
+            return NotImplemented
+        return np.sqrt(self.data)
+
+    def sqrt_(self):
+        """Inline squared tensor."""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.sqrt(self.data)
+
+    def dim(self):
+        """Returns an integer of the number of dimensions of this tensor."""
+
+        return self.data.ndim
+
+    def sum(self, dim=None):
+        """Returns the sum of all elements in the input array."""
+        if self.encrypted:
+            return NotImplemented
+
+        if dim is None:
+            return self.data.sum()
+        else:
+            return self.data.sum(axis=dim)
+
+    def ceil_(self):
+        """Returns the ceilling of the input tensor elementwise."""
+        if self.encrypted:
+            return NotImplemented
+        self.data = syft.math.ceil(self.data)
+        return self
+
+    def floor_(self):
+        """Inplace floor method"""
+        if self.encrypted:
+            return NotImplemented
+        self.data = syft.math.floor(self.data)
+        return self
+
+    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.
+            """
+        return syft.addmm(self, tensor2, mat, beta, 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.
+            """
+        _ensure_tensorbase(tensor2)
+        _ensure_tensorbase(mat)
+        if self.encrypted or tensor2.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            self.data = np.array((np.matmul(self.data, tensor2.data)))
+            self.data *= alpha
+            mat.data *= beta
+            self.data = self.data + mat.data
+            return self
+
+    def addcmul(self, tensor2, mat, value=1):
+        """Performs the element-wise multiplication of tensor1 by tensor2,
+        multiply the result by the scalar value and add it to mat."""
+        return syft.addcmul(self, tensor2, mat, value)
+
+    def addcmul_(self, tensor2, mat, value=1):
+        """Performs implace element-wise multiplication of tensor1 by tensor2,
+        multiply the result by the scalar value and add it to mat."""
+        _ensure_tensorbase(tensor2)
+        _ensure_tensorbase(mat)
+        if self.encrypted or tensor2.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            self.data *= tensor2.data
+            self.data *= value
+            self.data += mat.data
+            return self
+
+    def addcdiv(self, tensor2, mat, value=1):
+        """Performs the element-wise division of tensor1 by tensor2,
+        multiply the result by the scalar value and add it to mat."""
+        return syft.addcdiv(self, tensor2, mat, value)
+
+    def addcdiv_(self, tensor2, mat, value=1):
+        """Performs implace element-wise division of tensor1 by tensor2,
+        multiply the result by the scalar value and add it to mat."""
+        _ensure_tensorbase(tensor2)
+        _ensure_tensorbase(mat)
+        if self.encrypted or tensor2.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            self.data = self.data / tensor2.data
+            self.data *= value
+            self.data += mat.data
+            return self
+
+    def addmv(self, mat, vec, beta=1, alpha=1):
+        """"Performs a matrix-vector product of the matrix mat and the vector
+         vec. The vector tensor is added to the final result.
+              tensor1 and vec are 1d tensors
+              out=(beta∗tensor)+(alpha∗(mat@vec2))"""
+        return syft.addmv(self, mat, vec, beta, alpha)
+
+    def addmv_(self, mat, vec, beta=1, alpha=1):
+        """"Performs a inplace matrix-vector product of the matrix mat and the
+         vector vec. The vector tensor is added to the final result.
+              tensor1 and vec are 1d tensors
+              out=(beta∗tensor)+(alpha∗(mat@vec2))"""
+        _ensure_tensorbase(vec)
+        _ensure_tensorbase(mat)
+        if vec.data.ndim != 1:
+            print("dimension of vec is not 1")
+        elif self.data.ndim != 1:
+            print("dimension of tensor is not 1")
+        elif self.encrypted or vec.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            self *= beta
+            temp = np.matmul(mat.data, vec.data) * alpha
+            self += temp
+            return self
+
+    def addbmm(self, tensor2, mat, beta=1, alpha=1):
+        """Performs a batch matrix-matrix product of matrices stored in
+        batch1(tensor1) and batch2(tensor2), with a reduced add step (all
+        matrix multiplications get accumulated along the first dimension).
+         mat is added to the final result.
+         res=(beta∗M)+(alpha∗sum(batch1i@batch2i, i=0, b))
+        * batch1 and batch2 must be 3D Tensors each containing the same
+        number of matrices."""
+        return syft.addbmm(self, tensor2, mat, beta, alpha)
+
+    def addbmm_(self, tensor2, mat, beta=1, alpha=1):
+        """Performs a inplace batch matrix-matrix product of matrices stored
+        in batch1(tensor1) and batch2(tensor2), with a reduced add step
+        (all matrix multiplications get accumulated along the first dimension).
+         mat is added to the final result.
+         res=(beta∗M)+(alpha∗sum(batch1i@batch2i, i=0, b)
+        * batch1 and batch2 must be 3D Tensors each containing the same number
+        of matrices.)"""
+        _ensure_tensorbase(tensor2)
+        _ensure_tensorbase(mat)
+        if tensor2.data.ndim != 3:
+            print("dimension of tensor2 is not 3")
+        elif self.data.ndim != 3:
+            print("dimension of tensor1 is not 3")
+        elif self.encrypted or tensor2.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            self.data = np.matmul(self.data, tensor2.data)
+            sum_ = 0  # sum is a python built in function a keyword !
+            for i in range(len(self.data)):
+                sum_ += self.data[i]
+            self.data = (mat.data * beta) + (alpha * sum_)
+            return self
+
+    def baddbmm(self, tensor2, mat, beta=1, alpha=1):
+        """Performs a batch matrix-matrix product of matrices in
+        batch1(tensor1) and batch2(tensor2). mat is added to the final result.
+          resi=(beta∗Mi)+(alpha∗batch1i×batch2i)
+          *batch1 and batch2 must be 3D Tensors each containing the same number
+          of matrices."""
+        return syft.baddbmm(self, tensor2, mat, beta, alpha)
+
+    def baddbmm_(self, tensor2, mat, beta=1, alpha=1):
+        """Performs a batch matrix-matrix product of matrices in
+        batch1(tensor1) and batch2(tensor2). mat is added to the final result.
+          resi=(beta∗Mi)+(alpha∗batch1i×batch2i)
+          *batch1 and batch2 must be 3D Tensors each containing the same number
+          of matrices."""
+        _ensure_tensorbase(tensor2)
+        _ensure_tensorbase(mat)
+        if tensor2.data.ndim != 3:
+            print("dimension of tensor2 is not 3")
+        elif self.data.ndim != 3:
+            print("dimension of tensor1 is not 3")
+        elif self.encrypted or tensor2.encrypted or mat.encrypted:
+            return NotImplemented
+        else:
+            self.data = np.matmul(self.data, tensor2.data)
+            self.data *= alpha
+            self.data += (mat.data * beta)
+            return self
+
+    def unsqueeze(self, dim):
+        """
+        Returns expanded Tensor. An additional dimension of size one is added
+        to at index 'dim'.
+        """
+        return syft.unsqueeze(self.data, dim)
+
+    def unsqueeze_(self, dim):
+        """
+        Replaces with an expanded Tensor. An additional dimension of size one
+        is added to at index 'dim'.
+        """
+        num_dims = len(self.data.shape)
+
+        if dim >= num_dims or dim < 0:
+            print("dimension out of range")
+        elif self.encrypted:
+            raise NotImplemented
+        else:
+            self.data = np.expand_dims(self.data, dim)
+
+    def exp(self):
+        """Computes the exponential of each element in tensor."""
+        if self.encrypted:
+            return NotImplemented
+        out = np.exp(self.data)
+        return TensorBase(out)
+
+    def exp_(self):
+        """Computes the exponential of each element inplace."""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.exp(self.data)
+        return self
+
+    def frac(self):
+        """"Computes the fractional portion of each element in tensor."""
+        if self.encrypted:
+            return NotImplemented
+        out = np.modf(self.data)[0]
+        return TensorBase(out)
+
+    def frac_(self):
+        """"Computes the fractional portion of each element inplace."""
+        if self.encrypted:
+            return NotImplemented
+        self.data = np.modf(self.data)[0]
+        return self
+
+    def sigmoid_(self):
+        """
+            Performs inline sigmoid function on the Tensor elementwise
+            Implementation details:
+            Because of the way syft.math.sigmoid operates on a Tensor Object
+            calling it on self.data will cause an input error thus we call
+            sigmoid on the tensor object and we take the member 'data' from the returned Tensor
+        """
+        if self.encrypted:
+            return NotImplemented
+        self.data = syft.math.sigmoid(self).data
+        # self.data = np.array((1 / (1 + np.exp(np.array(-self.data)))))
+        return self
+
+    def __str__(self):
+        return str(self.data)
+
+    def __repr__(self):
+        return repr(self.data)
+
+    def rsqrt(self):
+        """Returns reciprocal of square root of Tensor element wise"""
+        if self.encrypted:
+            return NotImplemented
+        out = 1 / np.sqrt(self.data)
+        return TensorBase(out)
+
+    def rsqrt_(self):
+        """Computes reciprocal of square root of Tensor elements inplace"""
+        if self.encrypted:
+            return NotImplemented
+        self.data = 1 / np.sqrt(self.data)
+
+    def to_numpy(self):
+        """Returns the tensor as numpy.ndarray"""
+        if self.encrypted:
+            return NotImplemented
+        return np.array(self.data)
+
+    def reciprocal(self):
+        """Computes element wise reciprocal"""
+        if self.encrypted:
+            return NotImplemented
+        out = 1 / np.array(self.data)
+        return TensorBase(out)
+
+    def reciprocal_(self):
+        """Computes element wise reciprocal"""
+        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/syft/test/benchmark.py b/syft/test/benchmark.py
index 77091539bb..743525dd68 100644
--- a/syft/test/benchmark.py
+++ b/syft/test/benchmark.py
@@ -1,10 +1,12 @@
 from time import time
 from line_profiler import LineProfiler
 
+
 class Benchmark():
     """
     This is a testing class for the benchmarking of functions.
-    Input of the Benchmark class should be the function to test and the params (optional)
+    Input of the Benchmark class should be the function to test
+    and the params (optional)
     """
 
     def __init__(self, function, **params):
@@ -21,7 +23,6 @@ class Benchmark():
             self.function(**self.params)
             t1 = time()
             results.append(t1 - t0)
-        
         return results
 
     def profile_lines(self):
@@ -33,4 +34,3 @@ class Benchmark():
         lp_wrapper = lp(self.function)
         lp_wrapper(**self.params)
         lp.print_stats()
-    
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_math.py b/tests/test_math.py
new file mode 100644
index 0000000000..011bcfeb88
--- /dev/null
+++ b/tests/test_math.py
@@ -0,0 +1,179 @@
+import unittest
+
+import numpy as np
+
+import syft
+from syft import TensorBase
+
+
+class DotTests(unittest.TestCase):
+    def testDotInt(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([4, 5, 6]))
+        self.assertEqual(syft.dot(t1, t2), 32)
+
+    def testDotFloat(self):
+        t1 = TensorBase(np.array([1.3, 2.5, 3.7]))
+        t2 = TensorBase(np.array([4.9, 5.8, 6.5]))
+        self.assertEqual(syft.dot(t1, t2), 44.92)
+
+
+class CeilTests(unittest.TestCase):
+    def testCeil(self):
+        t1 = TensorBase(np.array([[2.3, 4.1], [7.4, 8.3]]))
+        self.assertTrue(syft.equal(syft.ceil(t1), TensorBase([[3., 5.],
+                                                              [8., 9.]])))
+
+
+class FloorTests(unittest.TestCase):
+    def testFloor(self):
+        t1 = TensorBase(np.array([[2.3, 4.1], [7.4, 8.3]]))
+        self.assertTrue(syft.equal(syft.math.floor(t1), TensorBase([[2., 4.],
+                                                                    [7., 8.]])))
+
+
+class CumsumTests(unittest.TestCase):
+    def testCumsum(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.equal(syft.cumsum(t1), TensorBase([1, 3, 6])))
+
+
+class CumprodTests(unittest.TestCase):
+    """Cumultative Product test"""
+
+    def testCumprod(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.equal(syft.cumprod(t1), TensorBase([1, 2, 6])))
+
+
+class SigmoidTests(unittest.TestCase):
+    """Sigmoid Test"""
+
+    def testSigmoid(self):
+        t1 = TensorBase(np.array([1.2, 3.3, 4]))
+        self.assertTrue(syft.equal(syft.math.sigmoid(t1), TensorBase(
+            [0.76852478, 0.96442881, 0.98201379])))
+
+
+class MatmulTests(unittest.TestCase):
+    """Matmul Tests"""
+
+    def testMatmul1DInt(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([4, 5, 6]))
+        self.assertEqual(syft.matmul(t1, t2), syft.dot(t1, t2))
+
+    def testMatmul1DFloat(self):
+        t1 = TensorBase(np.array([1.3, 2.5, 3.7]))
+        t2 = TensorBase(np.array([4.9, 5.8, 6.5]))
+        self.assertEqual(syft.matmul(t1, t2), syft.dot(t1, t2))
+
+    def testMatmul2DIdentity(self):
+        t1 = TensorBase(np.array([[1, 0],
+                                  [0, 1]]))
+        t2 = TensorBase(np.array([[5.8, 6.5],
+                                  [7.8, 8.9]]))
+        self.assertTrue(syft.equal(syft.matmul(t1, t2), [[5.8, 6.5],
+                                                         [7.8, 8.9]]))
+
+    def testMatmul2DInt(self):
+        t1 = TensorBase(np.array([[1, 2],
+                                  [3, 4]]))
+        t2 = TensorBase(np.array([[5, 6],
+                                  [7, 8]]))
+        self.assertTrue(syft.equal(syft.matmul(t1, t2), [[19, 22],
+                                                         [43, 50]]))
+
+    def testMatmul2DFloat(self):
+        t1 = TensorBase(np.array([[1.3, 2.5],
+                                  [3.4, 4.5]]))
+        t2 = TensorBase(np.array([[5.8, 6.5],
+                                  [7.8, 8.9]]))
+        self.assertTrue(syft.equal(syft.matmul(t1, t2), [[27.04, 30.7],
+                                                         [54.82, 62.15]]))
+
+
+class admmTests(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 = syft.addmm(t1, 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 = syft.addmm(t1, t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [[10, 18], [12, 20]]))
+
+
+class addcmulTests(unittest.TestCase):
+    def testaddcmul1d(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([2, 3, 4]))
+        mat = TensorBase(np.array([5]))
+        out = syft.addcmul(t1, t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [9, 17, 29]))
+
+    def testaddcmul2d(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 = syft.addcmul(t1, t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [[4, 11], [5, 12]]))
+
+
+class addcdivTests(unittest.TestCase):
+    def testaddcdiv1d(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([2, 5, 4]))
+        mat = TensorBase(np.array([5]))
+        out = syft.addcdiv(t1, t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [6., 5.8, 6.5]))
+
+    def testaddcdiv2d(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 = syft.addcdiv(t1, t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [[4., 5.], [5., 6.]]))
+
+
+class addmv(unittest.TestCase):
+    def testaddmv(self):
+        t1 = TensorBase(np.array([1, 2]))
+        vec = TensorBase(np.array([1, 2, 3, 4]))
+        mat = TensorBase(np.array([[2, 3, 3, 4], [5, 6, 6, 7]]))
+        out = syft.addmv(t1, mat, vec, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [68, 130]))
+
+
+class addbmmTests(unittest.TestCase):
+    def testaddbmm(self):
+        t1 = TensorBase(np.array([[[3, 4], [5, 6]], [[7, 8], [1, 2]]]))
+        t2 = TensorBase(np.array([[[3, 5], [5, 7]], [[7, 9], [1, 3]]]))
+        mat = TensorBase(np.array([[2, 3], [3, 4]]))
+        out = syft.addbmm(t1, t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [[176, 266], [114, 172]]))
+
+
+class baddbmmTests(unittest.TestCase):
+    def testbaddbmm(self):
+        t1 = TensorBase(np.array([[[3, 4], [5, 6]], [[7, 8], [1, 2]]]))
+        t2 = TensorBase(np.array([[[3, 5], [5, 7]], [[7, 9], [1, 3]]]))
+        mat = TensorBase(np.array([[[2, 3], [3, 4]], [[4, 5], [5, 6]]]))
+        out = syft.baddbmm(t1, t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [[[62, 92], [96, 142]],
+                                                  [[122, 184], [28, 42]]]))
+
+
+class unsqueezeTests(unittest.TestCase):
+    def testUnsqueeze(self):
+        t1 = TensorBase(np.arange(3 * 4 * 5).reshape((3, 4, 5)))
+        for i in range(len(t1.data.shape)):
+            out = syft.unsqueeze(t1, i)
+            expected_shape = list(t1.data.shape)
+            expected_shape.insert(i, 1)
+            self.assertTrue(np.array_equal(out.data.shape, expected_shape))
diff --git a/tests/test_paillier.py b/tests/test_paillier.py
new file mode 100644
index 0000000000..8f52aca3df
--- /dev/null
+++ b/tests/test_paillier.py
@@ -0,0 +1,124 @@
+from syft.he.paillier import KeyPair, PaillierTensor
+from syft import TensorBase
+import unittest
+import numpy as np
+
+
+# Here's our "unit tests".
+class DimTests(unittest.TestCase):
+    def testDimOne(self):
+        p, s = KeyPair().generate()
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        self.assertTrue(x.dim() == 1)
+
+
+class AddTests(unittest.TestCase):
+
+    def testSimple(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = PaillierTensor(p, np.array([3, 4, 5, 6, 7.]))
+
+        y = (x + x2).decrypt(s)
+        self.assertTrue(y == np.array([4., 6., 8., 10., 12.]))
+
+    def testSimpleReversed(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = PaillierTensor(p, np.array([3, 4, 5, 6, 7.]))
+
+        y = (x2 + x).decrypt(s)
+        self.assertTrue(y == np.array([4., 6., 8., 10., 12.]))
+
+    def testScalarInplace(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+
+        x += 1
+        self.assertTrue(s.decrypt(x) == np.array([2., 3., 4., 5., 6.]))
+
+    def testInplace(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = PaillierTensor(p, np.array([3, 4, 5, 6, 7.]))
+
+        x += x2
+        self.assertTrue(s.decrypt(x) == np.array([4., 6., 8., 10., 12.]))
+
+    def testInplaceReversed(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = PaillierTensor(p, np.array([3, 4, 5, 6, 7.]))
+
+        x2 += x
+        self.assertTrue(s.decrypt(x2) == np.array([4., 6., 8., 10., 12.]))
+
+    def testScalar(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+
+        y = x + 40
+
+        self.assertTrue(s.decrypt(y) == np.array([41., 42., 43., 44., 45.]))
+
+    def testInplacePlaintext(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = TensorBase(np.array([3, 4, 5, 6, 7.]))
+
+        x += x2
+        self.assertTrue(s.decrypt(x) == np.array([4., 6., 8., 10., 12.]))
+
+
+class MulTests(unittest.TestCase):
+
+    def testBasic(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = TensorBase(np.array([3, 4, 5, 6, 7.]))
+
+        y = x * x2
+        self.assertTrue(y.decrypt(s) == np.array([3., 8., 15., 24., 35.]))
+
+    def testBasicReversed(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = TensorBase(np.array([3, 4, 5, 6, 7.]))
+
+        y = x2 * x
+        self.assertTrue(y.decrypt(s) == np.array([3., 8., 15., 24., 35.]))
+
+    def testInline(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = TensorBase(np.array([3, 4, 5, 6, 7.]))
+
+        x *= x2
+        self.assertTrue(x.decrypt(s) == np.array([3., 8., 15., 24., 35.]))
+
+    def testInlineReversed(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+        x2 = TensorBase(np.array([3, 4, 5, 6, 7.]))
+
+        x2 *= x
+        self.assertTrue(x2.decrypt(s) == np.array([3., 8., 15., 24., 35.]))
+
+    def testScalar(self):
+        p, s = KeyPair().generate()
+
+        x = PaillierTensor(p, np.array([1, 2, 3, 4, 5.]))
+
+        x *= 2
+        self.assertTrue(s.decrypt(x) == np.array([2., 4., 6., 8., 10.]))
diff --git a/tests/test_tensor.py b/tests/test_tensor.py
index ae9cb22b63..d73c443827 100644
--- a/tests/test_tensor.py
+++ b/tests/test_tensor.py
@@ -1,66 +1,395 @@
 from syft import TensorBase
+import syft
 import unittest
 import numpy as np
+import math
+
 
 # Here's our "unit tests".
+class DimTests(unittest.TestCase):
+    def testDimOne(self):
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(t.dim() == 1)
+
+
 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]))
-    
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        t += np.array([1, 2, 3])
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.equal(t + 2, [3, 4, 5]))
+
+
+class CeilTests(unittest.TestCase):
+    def testCeil_(self):
+        t = TensorBase(np.array([1.4, 2.7, 6.2]))
+        t.ceil_()
+        self.assertTrue(syft.equal(t.data, TensorBase([2, 3, 7])))
+
+
+class FloorTests(unittest.TestCase):
+    def testFloor_(self):
+        t = TensorBase(np.array([1.4, 2.7, 6.2]))
+        t.floor_()
+        self.assertTrue(syft.equal(t.data, TensorBase([1., 2., 6.])))
+
 
 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]))
-    
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        t -= np.array([1, 2, 3])
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        t *= np.array([1, 2, 3])
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([2, 4, 8]))
+        self.assertTrue(syft.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]))
+        t = TensorBase(np.array([1, 2, 3]))
+        t *= np.array([1, 2, 3])
+        self.assertTrue(syft.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
+        t = TensorBase(np.array([2, 4, 6]))
+        self.assertTrue(syft.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]]))
+        self.assertTrue(syft.equal(t.shape(), (2, 2)))
+
+
+class SqrtTests(unittest.TestCase):
+    def testSqrt(self):
+        t = TensorBase(np.array([[0, 4], [9, 16]]))
+
+        self.assertTrue(syft.equal(t.sqrt(), ([[0, 2], [3, 4]])))
+
+    def testSqrt_(self):
+        t = TensorBase(np.array([[0, 4], [9, 16]]))
+        t.sqrt_()
+        self.assertTrue(syft.equal(t, ([[0, 2], [3, 4]])))
+
+
+class SumTests(unittest.TestCase):
+    def testDimNoneInt(self):
+        t = TensorBase(np.array([1, 2, 3]))
+        self.assertTrue(syft.equal(t.sum(), 6))
+
+    def testDimIsNotNoneInt(self):
+        t = TensorBase(np.array([[0, 1], [0, 5]]))
+        self.assertTrue(syft.equal(t.sum(dim=1), [1, 5]))
+
+
+class EqualTests(unittest.TestCase):
+    def testEqual(self):
+        t1 = TensorBase(np.array([1.2, 2, 3]))
+        t2 = TensorBase(np.array([1.2, 2, 3]))
+        self.assertTrue(syft.equal(t1, t2))
+
+    def testEqOp(self):
+        t1 = TensorBase(np.array([1, 2.4, 3]))
+        t2 = TensorBase(np.array([1, 2.4, 3]))
+        self.assertTrue(t1 == t2)
+
+    def testNotEqual(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([1, 4, 3]))
+        self.assertFalse(syft.equal(t1, t2))
+
+    def testIneqOp(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([1, 4, 5]))
+        self.assertTrue(t1 != t2)
+
+
+class IndexTests(unittest.TestCase):
+    def testIndexing(self):
+        t1 = TensorBase(np.array([1.2, 2, 3]))
+        self.assertEqual(1.2, t1[0])
+        self.assertEqual(3, t1[-1])
+
+
+class sigmoidTests(unittest.TestCase):
+    def testSigmoid(self):
+        t1 = TensorBase(np.array([1.2, 3.3, 4]))
+        self.assertTrue(syft.equal(t1.sigmoid_(), TensorBase(
+            [0.76852478, 0.96442881, 0.98201379])))
+
+
+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]]))
+
+
+class addcmulTests(unittest.TestCase):
+    def testaddcmul1d(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([2, 3, 4]))
+        mat = TensorBase(np.array([5]))
+        out = t1.addcmul(t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [9, 17, 29]))
+
+    def testaddcmul2d(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.addcmul(t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [[4, 11], [5, 12]]))
+
+    def testaddcmul_1d(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([2, 3, 4]))
+        mat = TensorBase(np.array([5]))
+        t1.addcmul_(t2, mat, value=2)
+        self.assertTrue(np.array_equal(t1.data, [9, 17, 29]))
+
+    def testaddcmul_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.addcmul_(t2, mat, value=2)
+        self.assertTrue(np.array_equal(t1.data, [[4, 11], [5, 12]]))
+
+
+class addcdivTests(unittest.TestCase):
+    def testaddcdiv1d(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([2, 5, 4]))
+        mat = TensorBase(np.array([5]))
+        out = t1.addcdiv(t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [6., 5.8, 6.5]))
+
+    def testaddcdiv2d(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.addcdiv(t2, mat, value=2)
+        self.assertTrue(np.array_equal(out.data, [[4., 5.], [5., 6.]]))
+
+    def testaddcdiv_1d(self):
+        t1 = TensorBase(np.array([1, 2, 3]))
+        t2 = TensorBase(np.array([2, 5, 4]))
+        mat = TensorBase(np.array([5]))
+        t1.addcdiv_(t2, mat, value=2)
+        self.assertTrue(np.array_equal(t1.data, [6., 5.8, 6.5]))
+
+    def testaddcdiv_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.addcdiv_(t2, mat, value=2)
+        self.assertTrue(np.array_equal(t1.data, [[4., 5.], [5., 6.]]))
+
+
+class addmvTests(unittest.TestCase):
+    def testaddmv(self):
+        t1 = TensorBase(np.array([1, 2]))
+        vec = TensorBase(np.array([1, 2, 3, 4]))
+        mat = TensorBase(np.array([[2, 3, 3, 4], [5, 6, 6, 7]]))
+        out = t1.addmv(mat, vec, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [68, 130]))
+
+    def testaddmv_(self):
+        t1 = TensorBase(np.array([1, 2]))
+        vec = TensorBase(np.array([1, 2, 3, 4]))
+        mat = TensorBase(np.array([[2, 3, 3, 4], [5, 6, 6, 7]]))
+        t1.addmv_(mat, vec, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(t1.data, [68, 130]))
+
+
+class addbmmTests(unittest.TestCase):
+    def testaddbmm(self):
+        t1 = TensorBase(np.array([[[3, 4], [5, 6]], [[7, 8], [1, 2]]]))
+        t2 = TensorBase(np.array([[[3, 5], [5, 7]], [[7, 9], [1, 3]]]))
+        mat = TensorBase(np.array([[2, 3], [3, 4]]))
+        out = t1.addbmm(t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [[176, 266], [114, 172]]))
+
+    def testaddbmm_(self):
+        t1 = TensorBase(np.array([[[3, 4], [5, 6]], [[7, 8], [1, 2]]]))
+        t2 = TensorBase(np.array([[[3, 5], [5, 7]], [[7, 9], [1, 3]]]))
+        mat = TensorBase(np.array([[2, 3], [3, 4]]))
+        t1.addbmm_(t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(t1.data, [[176, 266], [114, 172]]))
+
+
+class baddbmmTests(unittest.TestCase):
+    def testbaddbmm(self):
+        t1 = TensorBase(np.array([[[3, 4], [5, 6]], [[7, 8], [1, 2]]]))
+        t2 = TensorBase(np.array([[[3, 5], [5, 7]], [[7, 9], [1, 3]]]))
+        mat = TensorBase(np.array([[[2, 3], [3, 4]], [[4, 5], [5, 6]]]))
+        out = t1.baddbmm(t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(out.data, [[[62, 92], [96, 142]],
+                                                  [[122, 184], [28, 42]]]))
+
+    def testbaddbmm_(self):
+        t1 = TensorBase(np.array([[[3, 4], [5, 6]], [[7, 8], [1, 2]]]))
+        t2 = TensorBase(np.array([[[3, 5], [5, 7]], [[7, 9], [1, 3]]]))
+        mat = TensorBase(np.array([[[2, 3], [3, 4]], [[4, 5], [5, 6]]]))
+        t1.baddbmm_(t2, mat, beta=2, alpha=2)
+        self.assertTrue(np.array_equal(t1.data, [[[62, 92], [96, 142]],
+                                                 [[122, 184], [28, 42]]]))
+
+
+class unsqueezeTests(unittest.TestCase):
+    def testUnsqueeze(self):
+        t1 = TensorBase(np.arange(3 * 4 * 5).reshape((3, 4, 5)))
+        for i in range(len(t1.data.shape)):
+            out = t1.unsqueeze(i)
+            expected_shape = list(t1.data.shape)
+            expected_shape.insert(i, 1)
+            self.assertTrue(np.array_equal(out.data.shape, expected_shape))
+
+    def testUnsqueeze_(self):
+        test_shape = (3, 4, 5)
+        for i in range(len(test_shape)):
+            t1 = TensorBase(np.arange(3 * 4 * 5).reshape(test_shape))
+            expected_shape = list(t1.data.shape)
+            expected_shape.insert(i, 1)
+            t1.unsqueeze_(i)
+            self.assertTrue(np.array_equal(t1.data.shape, expected_shape))
+
+
+class expTests(unittest.TestCase):
+    def testexp(self):
+        t3 = TensorBase(np.array([[[1, 3], [3, 5]],
+                                  [[5, 7], [9, 1]]]))
+        out = t3.exp()
+        self.assertTrue(np.allclose(out.data, [[[2.71828183e+00, 2.00855369e+01], [2.00855369e+01, 1.48413159e+02]],
+                                               [[1.48413159e+02, 1.09663316e+03], [8.10308393e+03, 2.71828183e+00]]]))
+
+    def testexp_(self):
+        t3 = TensorBase(np.array([[[1, 3], [3, 5]],
+                                  [[5, 7], [9, 1]]]))
+        t3.exp_()
+        self.assertTrue(np.allclose(t3.data, [[[2.71828183e+00, 2.00855369e+01], [2.00855369e+01, 1.48413159e+02]],
+                                              [[1.48413159e+02, 1.09663316e+03], [8.10308393e+03, 2.71828183e+00]]]))
+
+
+class fracTests(unittest.TestCase):
+    def testfrac(self):
+        t3 = TensorBase(np.array([1.23, 4.56, 7.89]))
+        out = t3.frac()
+        self.assertTrue(np.allclose(out.data, [0.23, 0.56, 0.89]))
+
+    def testfrac_(self):
+        t3 = TensorBase(np.array([1.23, 4.56, 7.89]))
+        t3.frac_()
+        self.assertTrue(np.allclose(t3.data, [0.23, 0.56, 0.89]))
+
+
+class rsqrtTests(unittest.TestCase):
+    def testrsqrt(self):
+        t1 = TensorBase(np.array([2, 3, 4]))
+        out = t1.rsqrt()
+        self.assertTrue(np.allclose(out.data, [0.70710678, 0.57735027, 0.5]))
+
+    def testrsqrt_(self):
+        t1 = TensorBase(np.array([2, 3, 4]))
+        t1.rsqrt_()
+        self.assertTrue(np.allclose(t1.data, [0.70710678, 0.57735027, 0.5]))
+
+
+class numpyTests(unittest.TestCase):
+    def testnumpy(self):
+        t1 = TensorBase(np.array([[1, 2], [3, 4]]))
+        self.assertTrue(np.array_equal(t1.to_numpy(), np.array([[1, 2], [3, 4]])))
+
+
+class reciprocalTests(unittest.TestCase):
+    def testreciprocal(self):
+        t1 = TensorBase(np.array([2, 3, 4]))
+        out = t1.reciprocal()
+        self.assertTrue(np.allclose(out.data, [0.5, 0.33333333, 0.25]))
+
+    def testrsqrt_(self):
+        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]))