Repository: Casyfill/pyCombo (language: python)

Issue:
Pybind11
I've updated c++ code with new version and added binding to it. Also, I've changed pyCombo to work with this new version and changed code style to  better comply with PEP8.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml
index bd80fb1..4acf06a 100644
--- a/.github/workflows/blank.yml
+++ b/.github/workflows/blank.yml
@@ -37,7 +37,6 @@ jobs:
       - name: Install dependencies
         run: |
             poetry install
-            poetry build
       - name: Lint with flake8
         run: |
             # stop the build if there are Python syntax errors or undefined names
diff --git a/.gitignore b/.gitignore
index 0fe617d..0986a63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,4 +102,5 @@ target/
 #Ipython Notebook
 .ipynb_checkpoints
 .DS_Store
-.vscode/
\ No newline at end of file
+.vscode/
+setup.py
diff --git a/README.md b/README.md
index 1d3eb4e..99e0ae6 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,28 @@
 # pyCOMBO
-[![Build Status](https://travis-ci.org/Casyfill/pyCOMBO.svg?branch=master)](https://travis-ci.org/Casyfill/pyCOMBO)
-[![Coverage Status](https://coveralls.io/repos/github/Casyfill/pyCombo/badge.svg?branch=master)](https://coveralls.io/github/Casyfill/pyCombo?branch=master)
+![CI](https://github.com/Casyfill/pyCombo/workflows/CI/badge.svg)
 
-**THis is the initial commit, not release version. any code shared AS IS**
+Python wrapper around C++ implementation of the [network] community detection algorithm called "Combo".
 
-This is a python wrapper around C++ implementation (for Modularity maximization) of the community detection algorithm called "Combo" described in the paper "General optimization technique for high-quality community detection in complex networks" by Stanislav Sobolevsky, Riccardo Campari, Alexander Belyi and Carlo Ratti.
+Details of the algorythm are described in the paper "General optimization technique for high-quality community detection in the paper:
 
-![Portugal cellular talks partition](http://senseable.mit.edu/community_detection/img/portugal_img.png)
-
-"Combo" algorithm for community detection  described in the paper:
 
 	Sobolevsky S., Campari R., Belyi A., and Ratti C. "General optimization technique for high-quality community detection in complex networks" Phys. Rev. E 90, 012811
 
+## Installation
+for now, use:
+```bash
+python -m pip install https://github.com/Casyfill/pyCombo/archive/pybind11.tar.gz
+```
+## Dependencies
+
+*pyCombo* does not have any dependency, yet it was created having [NetworkX](https://networkx.github.io/) module in mind.
+
 ## Why Combo?
 
 ![algorithm comparison](http://senseable.mit.edu/community_detection/img/plot_yoon_01.png)
 
 Combo achieves hight quality of partitioning, while being less greedy in terms of computation, than other algorithms.
 
-## Installation
-
-`pip install -e git+git@github.com:Casyfill/pyCombo.git@master#egg=pyCombo`
-
-## Dependency
-
-*pyCombo* does not have any dependency, yet it was created having [NetworkX](https://networkx.github.io/) module in mind.
 
 
 ## More information
@@ -35,26 +33,37 @@ Combo achieves hight quality of partitioning, while being less greedy in terms o
 ## Licensing
 All copiryghts and licensing is same as covered in the initial package
 
-## Roadmap
+##E Roadmap
 
 - [x] Initial release ASIS
 - [x] Tests, Travis Ci, Coveralls
 - [x] Package delivery, setup.py
 - [x] Installation via pip
 - [x] unweighted graph
-- [ ] switched to Github Actions
-- [ ] switch to poetry
-- [ ] check & test modularity
-- [ ] assert reproductibility
+- [x] switched to Github Actions
+- [x] switch to poetry
+- [x] assert reproductibility (random seed)
+- [x] setup pybind11 binding
+- [ ] performance (speed) benchmarks
+- [ ] full testing
 - [ ] understand 2 partitions issue/reason
 - [ ] directed graph
 - [ ] Exceptions
-
-
+- [ ] Logging
+- [ ] Two Communities minumum
 
 
 # Notes and issues
 
 - pyCombo does not work on graph of N=1
 - pyCombo for some reason always split the network into 2, even for the complete graph
-- binaries are now pulled from https://github.com/Express50/Combo/blob/master/Main.cpp, which fixes a few bugs and passes 
\ No newline at end of file
+- binaries are now pulled from https://github.com/Express50/Combo/blob/master/Main.cpp, which fixes a few bugs and passes
+
+# Development
+this section is for contributors and
+## Build
+run `poetry install` in order to build local (dev) code. It will
+be installed in editable mode within poetry environment
+
+## Links and resources
+- [How to build C extension in poetry](https://stackoverflow.com/questions/60073711/how-to-build-c-extensions-via-poetry)
diff --git a/build.py b/build.py
new file mode 100644
index 0000000..e11c28b
--- /dev/null
+++ b/build.py
@@ -0,0 +1,43 @@
+# from distutils.command.build_ext import build_ext
+# from distutils.core import Extension
+from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError
+from pybind11.setup_helpers import Pybind11Extension, build_ext
+
+
+ext_modules = [
+    Pybind11Extension(
+        "_combo", sources=["src/Graph.cpp", "src/Combo.cpp", "src/Binder.cpp"]
+    )
+]
+
+
+class BuildFailed(Exception):
+    pass
+
+
+class ExtBuilder(build_ext):
+    def run(self):
+        try:
+            build_ext.run(self)
+        except (DistutilsPlatformError, FileNotFoundError):
+            raise BuildFailed("File not found. Could not compile C extension.")
+
+    def build_extension(self, ext):
+        try:
+            build_ext.build_extension(self, ext)
+        except (
+            CCompilerError,
+            DistutilsExecError,
+            DistutilsPlatformError,
+            ValueError,
+        ) as e:
+            raise BuildFailed(f"Could not compile C extension: {e}")
+
+
+def build(setup_kwargs):
+    """
+    This function is mandatory in order to build the extensions.
+    """
+    setup_kwargs.update(
+        {"ext_modules": ext_modules, "cmdclass": {"build_ext": ExtBuilder}}
+    )
diff --git a/docs/source/conf.py b/docs/source/conf.py
index d93fc7f..01064f2 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -231,7 +231,7 @@
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-    (master_doc, "pyCombo.tex", u"pyCombo Documentation", u"Philipp Kats", "manual"),
+    (master_doc, "pyCombo.tex", u"pyCombo Documentation", u"Philipp Kats", "manual")
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -279,7 +279,7 @@
         "pyCombo",
         "One line description of project.",
         "Miscellaneous",
-    ),
+    )
 ]
 
 # Documents to append as an appendix to all manuals.
diff --git a/example/.ipynb_checkpoints/example-checkpoint.ipynb b/example/.ipynb_checkpoints/example-checkpoint.ipynb
deleted file mode 100644
index 8183b92..0000000
--- a/example/.ipynb_checkpoints/example-checkpoint.ipynb
+++ /dev/null
@@ -1,209 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Populating the interactive namespace from numpy and matplotlib\n"
-     ]
-    }
-   ],
-   "source": [
-    "%pylab inline\n",
-    "import matplotlib.pyplot as plt"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 2,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": [
-    "import networkx as nx"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 67,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [],
-   "source": [
-    "import community\n",
-    "from pyCombo import combo, modularity"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 61,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [],
-   "source": [
-    "G = nx.dorogovtsev_goltsev_mendes_graph(3)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 62,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": [
-    "pos=nx.spring_layout(G)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 63,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [
-    {
-     "data": {
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYEAAAEACAYAAABVtcpZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlcVNX7wPHPRVFk/SqM+165KyjmTm6llv40d3MpM7e0\nXCsst1wqtazQNJcsLQW1RMu1Ur+YZmXuqKUmuaYGKKPIzjy/Pwb8Ig4wzMKwnPfrNS+Ze8/ce2aE\nee4595znaCKCoiiKUjQ5OboCiqIoiuOoIKAoilKEqSCgKIpShKkgoCiKUoSpIKAoilKEqSCgKIpS\nhNkkCGiatkrTtJuapp3MYn9bTdNiNE07mvaYZovzKoqiKNYpbqPjfAEsBr7MpsxPItLdRudTFEVR\nbMAmLQEROQDczqGYZotzKYqiKLaTl/cEWmqadlzTtO2aptXLw/MqiqIoWbBVd1BOjgBVRSRO07Sn\ngS1ArTw6t6IoipKFPAkCIhKb4eedmqYt1TStjIjcylxW0zSVzEhRFCWXRMSiLndbdgdpZNHvr2la\nuQw/NwM0UwEgnYgUysfMmTMdXgf1/tT7U++v8D2sYZOWgKZpwUA7wFvTtMvATKAEICKyAuijadrL\nQDIQD/S3xXkVRVEU69gkCIjIwBz2LwGW2OJciqIoiu2oGcN5qF27do6ugl2p91ewqfdXNGnW9ifZ\nmqZpkt/qpCiKkp9pmobkgxvDiqIoSgGjgoCiKEoRpoKAoihKEaaCgKIoShGmgoCiKEoRpoKAoihK\nEaaCgKIoShGmgoCiKEoRpoKAoihKEaaCgKIoShGmgoCiKEoRpoKAoihKEaaCgKIoShGmgoCiKEoR\npoKAoihKEaaCgKIoShGmgoCiKEoRpoKAoihKEaaCgKIoShGmgoCiKEoRpoKAoihKEVbc0RVQFEfR\n6/VER0cD4O3tjZeXl4NrpCh5T7UElCIlMTGRkJAQAvz8qKTT0dHXl46+vlTS6Qjw8yMkJISkpCRH\nV1NR8owmIo6uwwM0TZP8VielcNiwfj3jR42ioQhj7t7l//hfUzgZ2AosdXfnlJMTQcuX03/AAMdV\nVlFyQdM0RESz6LX57QtXBQHFHhZ9+CEfTJvG5vh4/HMoewTo6erKa3PmMG7SpLyonqJYRQUBRcnG\nhvXreX3YMA7Ex1PVzNdcBtq4uvL+qlWqRaDkew4PApqmrQK6ATdFpFEWZRYBTwP3gKEicjyLcioI\nKDaTmJhItbJl2XHnDk0y7bsEjAF+AVyA3kAQ/7tRdgTo6unJ5chISpQokWd1VpTcsiYI2OrG8BdA\n56x2apr2NPCIiDwGjAKW2ei8ipKt0NBQGhgMDwUAMAaAssBN4DiwD1iaYb8/UN9gIDQ01P4VVRQH\nsUkQEJEDwO1sivQAvkwr+xvgpWlaOVucW1Gys3T+fMbExprcdxHoDzhjDAZdgNOZyoyJjWXp/Pn2\nrKKiOFReDRGtBFzJ8Pxa2jZFsRu9Xs+xM2fonsX+CcB6IB7jL+ROjP2VGXUHjp4+jV6vt19FFcWB\n1DwBpdCKjo5GV7JkljMiA4BTgCdQFXgcHgoYzoBPiRLcunXLfhVVFAfKqxnD14AqGZ5XTttm0ttv\nv33/53bt2tGuXTt71UspogRj989ojDeGY4EXgUBAdf4o+V1YWBhhYWE2OZbNhohqmlYd2CoiDU3s\newYYKyJdNU1rAXwsIi2yOI4aHaTYhF6vp5JOx+3kZJwz7YvGeB8gBvBI2/YtMB04maFcMlDa2Zlr\nkZEqrYSSbzl8dJCmacHAQaCWpmmXNU17UdO0UZqmjQQQkR3A35qm/QUsxzgwQ1HsysvLi8b16rHV\nxD5voAbGYWqpGIPBGsA3U7nvgCb166sAoBRaarKYUqiFhISwauRIdpsYIXQSGA+cwNgv2gFYDOgy\nlOno4cGIFSsYoCaMKfmYwyeL2ZIKAootZTdZLCdHgPbFi3Pi3Dlq1Khhj+opik04vDtIUfKrkiVL\nErR8Oc+WKsXlXLzuMtCzVCm6PPsszZs3Z+3ataiLE6UwUkFAKfT6DxjAa3Pn0qZUKY6YUf4IxrxB\nr82dy8avv2bXrl3MmzeP3r178++//9q7uoqSp1QQUIqEcZMm8f7nn/NkyZK0KV6cUCAlw/5kYBPG\newBdPT15f9Wq+xlEmzRpwpEjR3jsscdo1KiRSiOhFCrqnoBSZERHR1OrVi1mzJjBptWrOXr6ND5p\nieGikpJoUr8+YwID6dWrV5YJ4w4ePMgLL7xAixYtWLRoEaVLl87Lt6AoJqkbw4pihjfffJPo6GhW\nrFgBGOcRpM8ELlOmjNnDQO/du8eUKVPYsmULK1eupEuXLnars6KYQwUBRcnBv//+S926dTl27BhV\nq5q7qkD29uzZw7Bhw+jSpQsffPABHh4eOb9IUexAjQ5SlBzMmzePgQMH2iwAAHTs2JGTJ0+SkpKC\nr68v+/bts9mxFSWvqJaAUuhdu3aNhg0bcvr0aSpUqGCXc2zbto1Ro0bRr18/3n33XUqVKmWX8yiK\nKaoloCjZePfddxk2bJjdAgBAt27dOHnyJNevX6dx48YcOnTIbudSFFtSLQGlULt06RJNmjThzz//\nRKfT5fwCG9iwYQPjxo1jxIgRzJgxQy1NqdidagkoShbmzJnD6NGj8ywAAPTv358TJ05w4sQJmjVr\nxokTJ/Ls3IqSW6olkIf0ej3R0dEAeHt7q8yUdvbXX3/RokULzp8/75Dx/CLCmjVreP3115k4cSJv\nvPEGxYvn1RIeSlGiWgL5WGJiIiEhIQT4+VFJp6Ojry8dfX2ppNMR4OdHSEgISUlJjq5moTRr1izG\njRvnsAldmqYxdOhQjhw5wt69e2nTpg1nz551SF0UJUsikq8exioVDutDQqScp6c86eEhoSDJIJL2\nSALZBNLR3V3KeXrK+pAQR1e3UDl9+rTodDrR6/WOroqIiKSmpsonn3wi3t7e8vHHH0tqaqqjq6QU\nImnfmxZ956ruIDtZ9OGHfDBtGpvj4/HPoewRoKerK6/NmXM/X41inX79+uHv709gYKCjq/KA8+fP\nM3ToUJydnfniiy9ylaJadScqWbGmO8jhV/6ZHxSClsD6kBCpUqqUXMpw5Z/T4xJIFVdX1SKwgePH\nj0v58uUlNjbW0VUxKSUlRRYsWCA+Pj6yYsUKMRgMWZZNSEiQ4OBgaePrK27OzlLd3V2qu7uLm7Oz\ntPH1leDgYElMTMzD2iv5EVa0BBz+pf9QhQp4EEhISJBynp5yxMQX/R8gHUC8QB4D2Zxp/2GQcp6e\n6o/aSt27d5ePPvrI0dXI0alTp6RJkyby9NNPy9WrVx/ar7oTFXOpIJCPBAcHS0d394cCQApILZCP\nQQwge0HcQM5nKtfB3V1C1B+0xQ4dOiSVKlWS+Ph4R1fFLElJSTJz5kzR6XSydu3a+62CoIULpUqp\nUnLYjFbk4bRWZNDChQ5+N4qjqCCQj7Tx9ZVNJv5QT4F4ZNrWCWRGpm3fgAT4+Tn6bRRYnTt3lqVL\nlzq6Grl2+PBhqVevnvTu3VtWLF+uuhOVXLEmCKgbwzak1+uppNMRk5xM5tHgp4GWwJ0M2zoBHhgX\nM0mXDJR2duZaZKS68ZdLBw4cYMiQIZw9e7ZAztJNSEjgrbfeYvnHH7NfxKI1kbt6enI5MrJAvn/F\ncmqeQD4RHR2NrmTJhwIAQG2gLPABxhWtfgD2AXGZyjkDPiVK3M9zr5hHRJg2bRrTp08vsF+ALi4u\nPP744zQrVcpkABgCVAD+A9QBVmXa7w/UNxjUymdKrqggkEeKA1uAbRj/kD8C+gOVHVmpQmTv3r1c\nu3aN559/3tFVscrS+fN5NS7zpYHRm8DfQAzwHTANOJapzJjYWJbOn2/XOiqFiwoCNuTt7U1kYiLJ\nWexvAIQBkcBO4ALQLFOZZIxLHZYpU8Zu9SxsRITp06cza9asAp2WQa/Xc+zMGbpnsb8e4JL2swAa\nxt+hjLoDR0+fRq/X26mWSmGjgoANeXl50bhePbZmsT8cSMTYBfQBcAMYmqnMd0D50qW5evWq3epZ\n2OzcuRO9Xk///v0dXRWrZNedmG4s4AbUBSoCz2Tar7oTldxSQcDGxgQGstTd3eS+rzB2BZUH/gv8\niPGPNqPFrq7Ua96czp0707hxYxYuXMg///xj1zoXZOmtgNmzZ1OsWDFHV8fulgCxwAGgF1DSsdVR\nCgEVBGysV69enHJy4qiJfQuAWxhHCG0HambafwQINxj4+uuvuXTpEh9++CFnzpyhfv36PPnkk6xe\nvZo7d+5kPmyRtmXLFgwGAz179nR0VayWU3diOg1oBVwBPs20Lxn4Nz6e33//ncuXL1MQR9rp9Xoi\nIiKIiIhQ3Vp5wdKxpfZ6UMDnCYhYnjaisouLVK1SRfr06SO3bt26f7y4uDjZuHGj9OjRQzw9PaV/\n//6ydetWSUpKcuC7dLzU1FRp0KCBbN261dFVsYnLly9LnYoVTc4zMfUYDjLBxDyTR3Q66dSpk5Qv\nX168vLykdevW8vLLL8vSpUtl//79EhMT4+i3+hCVHsM6qMli+Y+lMz7j4+Nl/PjxUrVqVfnpp58e\nOm5UVJR8+umn0rp1a/Hx8ZGxY8fKwYMHs80/U1iFhIRI8+bNC/R7P3/+vMybN0+aNWsmZcqUkYCA\nAGlbqtRDvyP/gqwHiQVJBdkF4g6yLfOMcw+PB2acR0ZGyt69eyUoKEiGDx8uzZo1Ezc3N6latap0\n7dpVpkyZIuvWrZOTJ0867EtWpcewnsODANAF+BM4BwSa2N8W48i2o2mPadkcy16fU55L/+Xu6O4u\nm0z8cn+T9kdr6pd7+/btUr58eZk+fbokJyebPP6FCxdkzpw5Urt2bXnkkUdkxowZcvbs2bx4aw6X\nnJwstWvXlh9++MHRVckVg8EgJ0+elLffflsaNmwo5cqVk9GjR8uPP/4oSUlJWeaeigRpC1IaY+6p\nRiCrTFxMmJN7KjU1Vf766y/ZvHmzzJo1S/r06SO1a9cWFxcXadiwoQwcOFDee+892bZtm1y+fNmu\nQValx7ANhwYBjPcV/gKqYbzPeRyok6lMW+A7M49np4/JMRITEyUkJEQC/PzEzdlZqrm5STU3N3Fz\ndpYAPz8JCQnJ8o/2+vXr0qlTJ2nZsqX8/fffWZ7DYDDI4cOHZcKECVKuXDlp1qyZBAUFyc2bN+30\nrhxv9erVEhAQUCBaAQaDQX777TcJDAyUxx57TKpVqyYTJ06U/fv3S0pKykPlHZWFNi4uTo4cOSJf\nfPGFTJo0SZ566ikpX768/Oc//5E2bdrc71I6cOCATbqUVLZd23F0EGgB7MzwfErm1kBaENhq5vHs\n8iHlBzExMRIRESERERFm/xGlpqbKwoULRafTmZVYLjk5WXbt2iWDBw8WLy8vefrpp2XdunX5Nq2y\nJZKSkqRGjRqyb98+R1clSykpKbJv3z4ZN26cVKlSRWrXri1vvfWWHD582KzAlZ+ukP/991/Zs2eP\nfPzxx/LSSy891KX05ptvSnBwsISHh5t9nyq9xRMI0hSkJMiLmVrKfUCqg2gg+yxo8RQljg4CvYEV\nGZ4PBhZlKtMWiEprJWwH6mVzPHt9TgXakSNHpFatWjJ06FC5c+eOWa+JjY2VdevWydNPPy1eXl4y\nZMgQ2bVrV5bdSwXF8uXL5cknn3R0NR6SmJgou3btkpEjR0rZsmXFz89PZs+eLadPn7aoxWJNd6K9\npaamyvnz5yU0NPR+l1KtWrXudykNGjRI5s2bJ9u3bzfZpZSebXczyLcgY0wEgSCQn0EqZgoCgsq2\nm5k1QcDqBHKapvUGOovIyLTng4FmIjIuQxl3wCAicZqmPQ0EiUitLI4nM2fOvP+8Xbt2tGvXzqo6\nFhaxsbFMmDCBffv2ERwczOOPP272a2/evMmGDRtYu3YtV65cYcCAAQwePJgmTZqgaZYtSOQICQkJ\n1KpVi40bN9KiRQtHV4f4+Hi+//57QkND2bZtG3Xq1KFXr1706tWLmjUzDwLOvaSkJEJDQ1k6fz5H\nT5+mTPHiJCQkEFe8OE3q12dMYCC9evXKN/mS4uPjOXPmDOHh4Zw8eZLw8HDCw8NJTEykYcOG9x8r\nFi5k2l9/0SvtddOBa8DnJo5ZBVgHPJFh2yYgyM+Pn45lTpxRNISFhREWFnb/+axZsxBHrSyGsTto\nV4bnD3UHmXjN30CZLPbZOEYWPhs2bBCdTifz58+3aK3as2fPyowZM6RmzZpSp04dmTt3rkRERNih\npra3aNEi6dq1q0ProNfrJTg4WPr06SOenp7SoUMH+eSTT0wuDGNLMTExcvToUXF3d8+Xwzyzk96l\n9NFHH8ngwYOlpKY90LKZlqklkPFR2URLIAnEzdm5wH0O9oKDu4OK8b8bwyUwdvnUzVSmXIafmwEX\nszmevT6nQuXixYvSunVr6dixo1y7ds2iYxgMBjl48KCMGTNGfHx8pHXr1vLpp59KVFSUjWtrG/fu\n3ZMKFSrIkSNH8vzcUVFR8vnnn0vXrl3Fw8NDnnnmGVm1apVERkbmaT0MBoM4OzsXmEVzTLlw4YJU\nz7TwUm6DgIBUc3MrMBcv9mZNELB6xrCIpAKvYMyOfBpYLyJ/aJo2StO0kWnF+miadkrTtGPAxxgT\naCpWqFatGmFhYQQEBNCkSRO2bs0qY1HWNE2jZcuWLFmyhGvXrhEYGEhYWBg1a9bk2Wef5ZtvviEh\nIcEOtbfM0qVLadmyJU2a5DbTvmX++ecfli5dypNPPknNmjXZvn07AwcO5MqVK2zfvp1hw4bh4+OT\nJ3VJp2kaOp2OyMjIPD2vUohZGj3s9UC1BHLtwIEDUq1aNRk7dqzExcVZfTy9Xi9ffPGFdOzYUUqX\nLi3Dhg2TvXv3WtT1ZCt37tyRsmXLSnh4uMXHiImJkQsXLsiFCxey7EaIiIiQDz74QFq1aiWlS5eW\nwYMHS2hoqNy7d8/i89pa48aN5ffff3d0NSwWExMjbs7OkqS6g2wGR7YEFMdr3bo1x48fJzIykmbN\nmnHq1Cmrjufp6cnQoUPZvXs34eHh1K1bl4kTJ1KtWjUCAwMJDw+3Uc0fllXemEWLFtGxY0caNGiQ\nq+MlJiYSEhJCgJ8flXQ6Ovr60tHXl0o6HQF+foSEhHDixAneeecdmjRpQvPmzTl79izTp0/nxo0b\nfPXVV/Ts2RNXV1dbv1WLlS1bln///dfR1bBYxmy7qUBC2r8pGLPspqaVS0rbR9r2xAzH+A5oUr++\nWn3PFiyNHvZ6oFoCFjMYDPL555+Lj4+PLFmyxKxhieZcHacLDw+XKVOmSJUqVaRRo0ayYMECuXLl\n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\nyoL0A7mezf9xQaOCQBGXmJiYbWbJkJCQAv9Lbg17XiVevnxZgoKC5IknnhAvLy8ZMGCAfP3117nK\nqfPXX3+Jt7e3nD171tq3apaePXvK3Llz8+RctpaQkCCbN2+WPn36iKenp/To0UPGjRt3v7W3GeRb\njKlHMgaBnWkXRHdB4kGGgXRJb+05OJeWLaggoNxny9E1hUluu858NE3enT07V+e4ceOGLFu2TJ56\n6inx9PSUnj17ytq1a7P9f0hNTZW2bdvKBx98YO1bNNuFCxekTJkycvXq1Tw7pz3o9Xr54osvpGJa\nKzjj/+G0TEEg8+MoxnQh6fd9HJlLyxZUEFAUM5jTddbOzU1cNU2e7NhRnn76aYtH6URHR8sXX3wh\n3bp1Ew8PD3nmmWdk1apVD6VhXrJkibRo0cLu3UCZvfXWWzJo0KA8Pac9ZJVLK6cg8BHGQQHp//eO\nzKVlC9YEATVZTClSspyYlpREk/r1GRMYyO+//87t27c5f/48Xbp0YerUqVad886dO2zfvp1Nmzbx\n448/0rRpU3r37o2/vz9du3Zl//791K1b1xZvz2yxsbHUqVOHjRs30qpVqzw9ty1llUZlOnAN+NzE\na04C7YGtQPo7z+sJgbamJospigWy6jq7ffu2lC1bVr7//nspX7687N6922bnvHfvnmzatEkGDhwo\nxYsXl2rVqsmHH34oFy9etNk5zPXVV19J06ZNC/SawllNCMyqJXAepBLIukzb83JCoD1gRUvA8qQk\nxuhTWtO0HzRNO6tp2veappmck61p2kVN005omnZM07RD1pxTUWzFy8uLGjVqUKNGjQfSCfznP/9h\nxowZLFiwgK+++orBgwfzzz//2OScrq6u9OrViyeeeAJfX18WLVrEqVOn8Pf35/HHH2fevHmcP3/e\nJufKyaBBg3B2dmbNmjV5cj578Pb2JjIxkWQzyl4CngJmAgMzbE/G2BIssrmMLI0exuDDfOCNtJ8D\ngXlZlIsASpt5TLtESkXJjaSkJKlTp45s27ZN5syZI23atLHZnIBLly6Jj4+PnDp16v625ORk2b17\nt7z88stSvnx5adiwobz99tsSHh5u18ldhw4dkgoVKoher7fbOewhMjJSNm7cKKNHjxafEiXu3xhO\nSRv98ybIEJCEtG3XMC6ytNBE66Co3xi26p6Apml/Am1F5KamaeWBMBGpY6Lc30BTEYk245hiTZ0U\n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\nYII=\n",
-      "text/plain": [
-       "<matplotlib.figure.Figure at 0x106d70fd0>"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "nx.draw_networkx(G,pos)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 64,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [],
-   "source": [
-    "nx.set_edge_attributes(G, 'weight', [1]*G.number_of_edges())"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 65,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [],
-   "source": [
-    "partition = combo(G, weight='weight')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 68,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [
-    {
-     "ename": "TypeError",
-     "evalue": "unsupported operand type(s) for +: 'int' and 'list'",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-68-99b1bd4f7912>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mpartition\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcommunity\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbest_partition\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mG\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;32m/Users/casy/anaconda/lib/python2.7/site-packages/python_louvain-0.4-py2.7.egg/community/__init__.pyc\u001b[0m in \u001b[0;36mbest_partition\u001b[0;34m(graph, partition)\u001b[0m\n\u001b[1;32m    193\u001b[0m     \u001b[0;34m>>\u001b[0m\u001b[0;34m>\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    194\u001b[0m     \"\"\"\n\u001b[0;32m--> 195\u001b[0;31m     \u001b[0mdendo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgenerate_dendrogram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgraph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpartition\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    196\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0mpartition_at_level\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdendo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdendo\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    197\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/Users/casy/anaconda/lib/python2.7/site-packages/python_louvain-0.4-py2.7.egg/community/__init__.pyc\u001b[0m in \u001b[0;36mgenerate_dendrogram\u001b[0;34m(graph, part_init)\u001b[0m\n\u001b[1;32m    264\u001b[0m     \u001b[0mcurrent_graph\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgraph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    265\u001b[0m     \u001b[0mstatus\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mStatus\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 266\u001b[0;31m     \u001b[0mstatus\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcurrent_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpart_init\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    267\u001b[0m     \u001b[0mstatus_list\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    268\u001b[0m     \u001b[0m__one_level\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcurrent_graph\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstatus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/Users/casy/anaconda/lib/python2.7/site-packages/python_louvain-0.4-py2.7.egg/community/__init__.pyc\u001b[0m in \u001b[0;36minit\u001b[0;34m(self, graph, part)\u001b[0m\n\u001b[1;32m    456\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgdegrees\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    457\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minternals\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 458\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtotal_weight\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgraph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mweight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'weight'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    459\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mpart\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    460\u001b[0m             \u001b[0;32mfor\u001b[0m \u001b[0mnode\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgraph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnodes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/Users/casy/anaconda/lib/python2.7/site-packages/networkx/classes/graph.pyc\u001b[0m in \u001b[0;36msize\u001b[0;34m(self, weight)\u001b[0m\n\u001b[1;32m   1726\u001b[0m         \u001b[0;36m6.0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1727\u001b[0m         \"\"\"\n\u001b[0;32m-> 1728\u001b[0;31m         \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdegree\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mweight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mweight\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1729\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mweight\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1730\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/Users/casy/anaconda/lib/python2.7/site-packages/networkx/classes/graph.pyc\u001b[0m in \u001b[0;36mdegree\u001b[0;34m(self, nbunch, weight)\u001b[0m\n\u001b[1;32m   1344\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdegree_iter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnbunch\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mweight\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1345\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m           \u001b[0;31m# return a dict\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1346\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdegree_iter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnbunch\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mweight\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   1347\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1348\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mdegree_iter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnbunch\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mweight\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/Users/casy/anaconda/lib/python2.7/site-packages/networkx/classes/graph.pyc\u001b[0m in \u001b[0;36mdegree_iter\u001b[0;34m(self, nbunch, weight)\u001b[0m\n\u001b[1;32m   1392\u001b[0m             \u001b[0;31m# edge weighted graph - degree is sum of nbr edge weights\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1393\u001b[0m             \u001b[0;32mfor\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnbrs\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnodes_nbrs\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1394\u001b[0;31m                 yield (n, sum((nbrs[nbr].get(weight, 1) for nbr in nbrs)) +\n\u001b[0m\u001b[1;32m   1395\u001b[0m                     (n in nbrs and nbrs[n].get(weight, 1)))\n\u001b[1;32m   1396\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'int' and 'list'"
-     ]
-    }
-   ],
-   "source": [
-    "partition = community.best_partition(G)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 57,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [
-    {
-     "data": {
-      "image/png": "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\n",
-      "text/plain": [
-       "<matplotlib.figure.Figure at 0x1072f61d0>"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "size = float(len(set(partition.values())))\n",
-    "\n",
-    "count = 0.\n",
-    "for com in set(partition.values()) :\n",
-    "    count+= 1.\n",
-    "    c = count / size\n",
-    "    \n",
-    "    list_nodes = [nodes for nodes in partition.keys()\n",
-    "                                if partition[nodes] == com]\n",
-    "    nx.draw_networkx_nodes(G, pos, list_nodes, node_size = 300,\n",
-    "                                node_color = str(c))\n",
-    "\n",
-    "\n",
-    "nx.draw_networkx_edges(G,pos, alpha=1)\n",
-    "plt.axis('off');\n",
-    "plt.show()\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 2",
-   "language": "python",
-   "name": "python2"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 2
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.11"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/example/deconstruct_graph.ipynb b/example/deconstruct_graph.ipynb
new file mode 100644
index 0000000..2ccbb75
--- /dev/null
+++ b/example/deconstruct_graph.ipynb
@@ -0,0 +1,143 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import networkx as nx\n",
+    "\n",
+    "G = nx.karate_club_graph()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "NodeView((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33))"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "G.nodes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "EdgeView([(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 10), (0, 11), (0, 12), (0, 13), (0, 17), (0, 19), (0, 21), (0, 31), (1, 2), (1, 3), (1, 7), (1, 13), (1, 17), (1, 19), (1, 21), (1, 30), (2, 3), (2, 7), (2, 8), (2, 9), (2, 13), (2, 27), (2, 28), (2, 32), (3, 7), (3, 12), (3, 13), (4, 6), (4, 10), (5, 6), (5, 10), (5, 16), (6, 16), (8, 30), (8, 32), (8, 33), (9, 33), (13, 33), (14, 32), (14, 33), (15, 32), (15, 33), (18, 32), (18, 33), (19, 33), (20, 32), (20, 33), (22, 32), (22, 33), (23, 25), (23, 27), (23, 29), (23, 32), (23, 33), (24, 25), (24, 27), (24, 31), (25, 31), (26, 29), (26, 33), (27, 33), (28, 31), (28, 33), (29, 32), (29, 33), (30, 32), (30, 33), (31, 32), (31, 33), (32, 33)])"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "G.edges()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "EdgeDataView([(0, 1, {}), (0, 2, {}), (0, 3, {}), (0, 4, {}), (0, 5, {}), (0, 6, {}), (0, 7, {}), (0, 8, {}), (0, 10, {}), (0, 11, {}), (0, 12, {}), (0, 13, {}), (0, 17, {}), (0, 19, {}), (0, 21, {}), (0, 31, {}), (1, 2, {}), (1, 3, {}), (1, 7, {}), (1, 13, {}), (1, 17, {}), (1, 19, {}), (1, 21, {}), (1, 30, {}), (2, 3, {}), (2, 7, {}), (2, 8, {}), (2, 9, {}), (2, 13, {}), (2, 27, {}), (2, 28, {}), (2, 32, {}), (3, 7, {}), (3, 12, {}), (3, 13, {}), (4, 6, {}), (4, 10, {}), (5, 6, {}), (5, 10, {}), (5, 16, {}), (6, 16, {}), (8, 30, {}), (8, 32, {}), (8, 33, {}), (9, 33, {}), (13, 33, {}), (14, 32, {}), (14, 33, {}), (15, 32, {}), (15, 33, {}), (18, 32, {}), (18, 33, {}), (19, 33, {}), (20, 32, {}), (20, 33, {}), (22, 32, {}), (22, 33, {}), (23, 25, {}), (23, 27, {}), (23, 29, {}), (23, 32, {}), (23, 33, {}), (24, 25, {}), (24, 27, {}), (24, 31, {}), (25, 31, {}), (26, 29, {}), (26, 33, {}), (27, 33, {}), (28, 31, {}), (28, 33, {}), (29, 32, {}), (29, 33, {}), (30, 32, {}), (30, 33, {}), (31, 32, {}), (31, 33, {}), (32, 33, {})])"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "G.edges(data=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "False"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "nx.is_weighted(G)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "False"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "nx.is_directed(G)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "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.7.8"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/makefile b/makefile
index e91c864..a5a5350 100644
--- a/makefile
+++ b/makefile
@@ -1,2 +1,10 @@
 test:
-	python -m pytest tests/
\ No newline at end of file
+	poetry install
+	poetry run pytest tests/  --log-level DEBUG
+
+
+install_git:
+	pip install git+https://github.com/Casyfill/pyCombo.git@pybind11
+
+install_tarball:
+	python -m pip install https://github.com/Casyfill/pyCombo/archive/c09dbe078d93a6abc3fa3027916092ccac17c4ab.tar.gz
diff --git a/poetry.lock b/poetry.lock
index 3cc0411..67964b9 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,3 +1,11 @@
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "atomicwrites"
 version = "1.4.0"
@@ -20,6 +28,44 @@ docs = ["furo", "sphinx", "zope.interface"]
 tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
 tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
 
+[[package]]
+name = "black"
+version = "20.8b1"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+appdirs = "*"
+click = ">=7.1.2"
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.6,<1"
+regex = ">=2020.1.8"
+toml = ">=0.10.1"
+typed-ast = ">=1.4.0"
+typing-extensions = ">=3.7.4"
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+
+[[package]]
+name = "cfgv"
+version = "3.2.0"
+description = "Validate configuration and produce human readable error messages."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[[package]]
+name = "click"
+version = "7.1.2"
+description = "Composable command line interface toolkit"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
 [[package]]
 name = "colorama"
 version = "0.4.4"
@@ -30,7 +76,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "coverage"
-version = "5.3"
+version = "5.4"
 description = "Code coverage measurement for Python"
 category = "dev"
 optional = false
@@ -47,6 +93,22 @@ category = "dev"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*"
 
+[[package]]
+name = "distlib"
+version = "0.3.1"
+description = "Distribution utilities"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "filelock"
+version = "3.0.12"
+description = "A platform independent file lock."
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "flake8"
 version = "3.8.3"
@@ -61,20 +123,32 @@ mccabe = ">=0.6.0,<0.7.0"
 pycodestyle = ">=2.6.0a1,<2.7.0"
 pyflakes = ">=2.2.0,<2.3.0"
 
+[[package]]
+name = "identify"
+version = "1.5.13"
+description = "File identification library for Python"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+
+[package.extras]
+license = ["editdistance"]
+
 [[package]]
 name = "importlib-metadata"
-version = "3.1.0"
+version = "3.4.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["sphinx", "rst.linker"]
-testing = ["packaging", "pep517", "unittest2", "importlib-resources (>=1.3)"]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
 name = "iniconfig"
@@ -92,6 +166,14 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "networkx"
 version = "2.5"
@@ -116,17 +198,25 @@ pytest = ["pytest"]
 pyyaml = ["pyyaml"]
 scipy = ["scipy"]
 
+[[package]]
+name = "nodeenv"
+version = "1.5.0"
+description = "Node.js virtual environment builder"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "numpy"
-version = "1.19.4"
+version = "1.20.0"
 description = "NumPy is the fundamental package for array computing with Python."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "packaging"
-version = "20.7"
+version = "20.9"
 description = "Core utilities for Python packages"
 category = "dev"
 optional = false
@@ -135,6 +225,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 [package.dependencies]
 pyparsing = ">=2.0.2"
 
+[[package]]
+name = "pathspec"
+version = "0.8.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
 [[package]]
 name = "pluggy"
 version = "0.13.1"
@@ -149,14 +247,50 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 [package.extras]
 dev = ["pre-commit", "tox"]
 
+[[package]]
+name = "pre-commit"
+version = "2.10.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+toml = "*"
+virtualenv = ">=20.0.8"
+
 [[package]]
 name = "py"
-version = "1.9.0"
+version = "1.10.0"
 description = "library with cross-python path, ini-parsing, io, code, log facilities"
 category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
+[[package]]
+name = "py-cpuinfo"
+version = "7.0.0"
+description = "Get CPU info with pure Python 2 & 3"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pybind11"
+version = "2.6.2"
+description = "Seamless operability between C++11 and Python"
+category = "main"
+optional = false
+python-versions = "!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,>=2.7"
+
+[package.extras]
+global = ["pybind11-global (==2.6.2)"]
+
 [[package]]
 name = "pycodestyle"
 version = "2.6.0"
@@ -183,37 +317,53 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "pytest"
-version = "6.1.2"
+version = "6.2.2"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.6"
 
 [package.dependencies]
 atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
-attrs = ">=17.4.0"
+attrs = ">=19.2.0"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 iniconfig = "*"
 packaging = "*"
-pluggy = ">=0.12,<1.0"
+pluggy = ">=0.12,<1.0.0a1"
 py = ">=1.8.2"
 toml = "*"
 
 [package.extras]
-checkqa_mypy = ["mypy (==0.780)"]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
 
+[[package]]
+name = "pytest-benchmark"
+version = "3.2.3"
+description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. See calibration_ and FAQ_."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+py-cpuinfo = "*"
+pytest = ">=3.8"
+
+[package.extras]
+aspect = ["aspectlib"]
+elasticsearch = ["elasticsearch"]
+histogram = ["pygal", "pygaljs"]
+
 [[package]]
 name = "pytest-cov"
-version = "2.10.1"
+version = "2.11.1"
 description = "Pytest plugin for measuring coverage."
 category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [package.dependencies]
-coverage = ">=4.4"
+coverage = ">=5.2.1"
 pytest = ">=4.6"
 
 [package.extras]
@@ -231,6 +381,30 @@ python-versions = "*"
 networkx = "*"
 numpy = "*"
 
+[[package]]
+name = "pyyaml"
+version = "5.4.1"
+description = "YAML parser and emitter for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[[package]]
+name = "regex"
+version = "2020.11.13"
+description = "Alternative regular expression module, to replace re."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "six"
+version = "1.15.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
 [[package]]
 name = "toml"
 version = "0.10.2"
@@ -239,6 +413,41 @@ category = "dev"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
+[[package]]
+name = "typed-ast"
+version = "1.4.2"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "typing-extensions"
+version = "3.7.4.3"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "virtualenv"
+version = "20.4.1"
+description = "Virtual Python Environment builder"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+
+[package.dependencies]
+appdirs = ">=1.4.3,<2"
+distlib = ">=0.3.1,<1"
+filelock = ">=3.0.0,<4"
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+six = ">=1.9.0,<2"
+
+[package.extras]
+docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
+testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
+
 [[package]]
 name = "zipp"
 version = "3.4.0"
@@ -254,9 +463,13 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "3c29150f829ce4e63933316668a6a6a35a5e06c24883dd2469fff1fecef2f7d8"
+content-hash = "641583837df4b5563273092c1c9ed6c755dd0d6142748c01f6bbda9396fd9f5a"
 
 [metadata.files]
+appdirs = [
+    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
 atomicwrites = [
     {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
@@ -265,57 +478,95 @@ attrs = [
     {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
     {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
 ]
+black = [
+    {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
+]
+cfgv = [
+    {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"},
+    {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"},
+]
+click = [
+    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
+    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
     {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
 coverage = [
-    {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"},
-    {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"},
-    {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"},
-    {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"},
-    {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"},
-    {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"},
-    {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"},
-    {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"},
-    {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"},
-    {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"},
-    {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"},
-    {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"},
-    {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"},
-    {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"},
-    {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"},
-    {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"},
-    {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"},
-    {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"},
-    {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"},
-    {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"},
-    {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"},
-    {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"},
-    {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"},
-    {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"},
-    {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"},
-    {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"},
-    {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"},
-    {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"},
-    {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"},
-    {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"},
-    {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"},
-    {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"},
-    {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"},
-    {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"},
+    {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"},
+    {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"},
+    {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"},
+    {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"},
+    {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"},
+    {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"},
+    {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"},
+    {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"},
+    {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"},
+    {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"},
+    {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"},
+    {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"},
+    {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"},
+    {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"},
+    {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"},
+    {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"},
+    {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"},
+    {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"},
+    {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"},
+    {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"},
+    {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"},
+    {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"},
+    {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"},
+    {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"},
+    {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"},
+    {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"},
+    {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"},
+    {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"},
+    {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"},
+    {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"},
+    {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"},
+    {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"},
+    {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"},
+    {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"},
 ]
 decorator = [
     {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
     {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
 ]
+distlib = [
+    {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
+    {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
+]
+filelock = [
+    {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
+    {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
+]
 flake8 = [
     {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
     {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
 ]
+identify = [
+    {file = "identify-1.5.13-py2.py3-none-any.whl", hash = "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4"},
+    {file = "identify-1.5.13.tar.gz", hash = "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66"},
+]
 importlib-metadata = [
-    {file = "importlib_metadata-3.1.0-py2.py3-none-any.whl", hash = "sha256:590690d61efdd716ff82c39ca9a9d4209252adfe288a4b5721181050acbd4175"},
-    {file = "importlib_metadata-3.1.0.tar.gz", hash = "sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099"},
+    {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"},
+    {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"},
 ]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -325,57 +576,70 @@ mccabe = [
     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
+mypy-extensions = [
+    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
 networkx = [
     {file = "networkx-2.5-py3-none-any.whl", hash = "sha256:8c5812e9f798d37c50570d15c4a69d5710a18d77bafc903ee9c5fba7454c616c"},
     {file = "networkx-2.5.tar.gz", hash = "sha256:7978955423fbc9639c10498878be59caf99b44dc304c2286162fd24b458c1602"},
 ]
+nodeenv = [
+    {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
+    {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"},
+]
 numpy = [
-    {file = "numpy-1.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949"},
-    {file = "numpy-1.19.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"},
-    {file = "numpy-1.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad"},
-    {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83"},
-    {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764"},
-    {file = "numpy-1.19.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6"},
-    {file = "numpy-1.19.4-cp36-cp36m-win32.whl", hash = "sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1"},
-    {file = "numpy-1.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb"},
-    {file = "numpy-1.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2"},
-    {file = "numpy-1.19.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2"},
-    {file = "numpy-1.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9"},
-    {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757"},
-    {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15"},
-    {file = "numpy-1.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387"},
-    {file = "numpy-1.19.4-cp37-cp37m-win32.whl", hash = "sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36"},
-    {file = "numpy-1.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c"},
-    {file = "numpy-1.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909"},
-    {file = "numpy-1.19.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c"},
-    {file = "numpy-1.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893"},
-    {file = "numpy-1.19.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab"},
-    {file = "numpy-1.19.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9"},
-    {file = "numpy-1.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db"},
-    {file = "numpy-1.19.4-cp38-cp38-win32.whl", hash = "sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac"},
-    {file = "numpy-1.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce"},
-    {file = "numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63"},
-    {file = "numpy-1.19.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37"},
-    {file = "numpy-1.19.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414"},
-    {file = "numpy-1.19.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc"},
-    {file = "numpy-1.19.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3"},
-    {file = "numpy-1.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753"},
-    {file = "numpy-1.19.4-cp39-cp39-win32.whl", hash = "sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f"},
-    {file = "numpy-1.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b"},
-    {file = "numpy-1.19.4-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08"},
-    {file = "numpy-1.19.4.zip", hash = "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512"},
+    {file = "numpy-1.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bd70c9ad540febe6c28451ba225eb4e49d27f64728357f512c808002325dfa"},
+    {file = "numpy-1.20.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1264c66129f5ef63187649dd43f1ca59532e8c098723643336a85131c0dcce3f"},
+    {file = "numpy-1.20.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e9c5fd330d2fedf06051bafb996252de9b032fcb2ec03eefc9a543e56efa66d4"},
+    {file = "numpy-1.20.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:db5e69d08756a2fa75a42b4e433880b6187768fe1bc73d21819def893e5128c6"},
+    {file = "numpy-1.20.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:1abc02e30e3efd81a4571e00f8e62bf42e343c76698e0a3e11d9c2b3ee0d77a7"},
+    {file = "numpy-1.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5ae765dd29c71a555f8102281f6fb15a3f4dbd35f6e7daf36af9df6d9dd716a5"},
+    {file = "numpy-1.20.0-cp37-cp37m-win32.whl", hash = "sha256:b51b9ef0624f4b01b846c981034c10d2e30db33f9f8be71e992f3900741f6f77"},
+    {file = "numpy-1.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:afeee581b50df20ef07b736e62ca612858f1fcdba96651d26ab44e3d567a4e6e"},
+    {file = "numpy-1.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2bf0e68c92ef077fe766e53f8937d8ac341bdbca68ec128ae049b7d5c34e3206"},
+    {file = "numpy-1.20.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2445a96fbae23a4109c61be0f0af0f3bc273905dc5687a710850c1dfde0fc994"},
+    {file = "numpy-1.20.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:33edfc0eb229f86f539493917b34035054313a11afbed48404aaf9f86bf4b0f6"},
+    {file = "numpy-1.20.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:894aaee60043a98b03f0ad992c810f62e3a15f98a701e1c0f58a4f4a0df13429"},
+    {file = "numpy-1.20.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b66a6c15d793eda7cdad986e737775aa31b9306d588c14dd0277d2dda5546150"},
+    {file = "numpy-1.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:eee454d3aa3955d0c0069a0f265fea47f1e1384c35a110a95efed358eb6e1562"},
+    {file = "numpy-1.20.0-cp38-cp38-win32.whl", hash = "sha256:abdfa075e293d73638ece434708aa60b510dc6e70d805f57f481a0f550b25a9e"},
+    {file = "numpy-1.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:f1e9424e9aa3834ea27cc12f9c6ea8ace5da18ee60a720bb3a85b2f733f41782"},
+    {file = "numpy-1.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cb257bb0c0a3176c32782a63cfab2eace7eabfa2a3b2dfd85a13700617ccaf28"},
+    {file = "numpy-1.20.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:cf5d9dcbdbe523fa665c5309cce5f144648d94a7fddbf5a40f8e0d5c9f5b596d"},
+    {file = "numpy-1.20.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:93c2abea7bb69f47029b84ceac30ab46dfcfdb99b671ad850a333ff794a765e4"},
+    {file = "numpy-1.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d28a54afcf46f1f9ebd163e49ad6b49087f22986fefd01a23ca0c1cdda25ca6"},
+    {file = "numpy-1.20.0-cp39-cp39-win32.whl", hash = "sha256:d1bc331e1706fd1809a1bc8a31205329e5b30cf5ba50461c624da267e99f6ae6"},
+    {file = "numpy-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:e3db646af9f6a145f0c57202f4b55d4a33f975e395e78fb7b394644c17c1a3a6"},
+    {file = "numpy-1.20.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:4d592264d2a4f368afbb4288b5ceb646d4cbaf559c0249c096fbb0a149806b90"},
+    {file = "numpy-1.20.0.zip", hash = "sha256:3d8233c03f116d068d5365fed4477f2947c7229582dad81e5953088989294cec"},
 ]
 packaging = [
-    {file = "packaging-20.7-py2.py3-none-any.whl", hash = "sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"},
-    {file = "packaging-20.7.tar.gz", hash = "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236"},
+    {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
+    {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
+]
+pathspec = [
+    {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
+    {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
 ]
 pluggy = [
     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
 ]
+pre-commit = [
+    {file = "pre_commit-2.10.0-py2.py3-none-any.whl", hash = "sha256:391ed331fdd0a21d0be48c1b9919921e9d372dfd60f6dc77b8f01dd6b13161c1"},
+    {file = "pre_commit-2.10.0.tar.gz", hash = "sha256:f413348d3a8464b77987e36ef6e02c3372dadb823edf0dfe6fb0c3dc2f378ef9"},
+]
 py = [
-    {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
-    {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
+    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
+    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
+]
+py-cpuinfo = [
+    {file = "py-cpuinfo-7.0.0.tar.gz", hash = "sha256:9aa2e49675114959697d25cf57fec41c29b55887bff3bc4809b44ac6f5730097"},
+]
+pybind11 = [
+    {file = "pybind11-2.6.2-py2.py3-none-any.whl", hash = "sha256:2d8aebe1709bc367e34e3b23d8eccbf3f387ee9d5640548c6260d33b59f02405"},
+    {file = "pybind11-2.6.2.tar.gz", hash = "sha256:d0e0aed9279656f21501243b707eb6e3b951e89e10c3271dedf3ae41c365e5ed"},
 ]
 pycodestyle = [
     {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
@@ -390,20 +654,135 @@ pyparsing = [
     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
 ]
 pytest = [
-    {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"},
-    {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"},
+    {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"},
+    {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"},
+]
+pytest-benchmark = [
+    {file = "pytest-benchmark-3.2.3.tar.gz", hash = "sha256:ad4314d093a3089701b24c80a05121994c7765ce373478c8f4ba8d23c9ba9528"},
+    {file = "pytest_benchmark-3.2.3-py2.py3-none-any.whl", hash = "sha256:01f79d38d506f5a3a0a9ada22ded714537bbdfc8147a881a35c1655db07289d9"},
 ]
 pytest-cov = [
-    {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
-    {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"},
+    {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"},
+    {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"},
 ]
 python-louvain = [
     {file = "python-louvain-0.14.tar.gz", hash = "sha256:e0a50c7a1e87b45410cd2a1d6298268862e55edaa46f90ae6b834bbb62df0951"},
 ]
+pyyaml = [
+    {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
+    {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
+    {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
+    {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
+    {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
+    {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
+    {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
+    {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
+    {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
+    {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
+    {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
+]
+regex = [
+    {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"},
+    {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"},
+    {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"},
+    {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"},
+    {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"},
+    {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"},
+    {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"},
+    {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"},
+    {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"},
+    {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"},
+    {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"},
+    {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
+    {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
+]
+six = [
+    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
+    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+]
 toml = [
     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
+typed-ast = [
+    {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"},
+    {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"},
+    {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"},
+    {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"},
+    {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"},
+    {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"},
+    {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"},
+    {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"},
+    {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"},
+    {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"},
+    {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"},
+    {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"},
+    {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"},
+    {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"},
+    {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"},
+    {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"},
+    {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"},
+    {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"},
+    {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"},
+    {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"},
+    {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"},
+    {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"},
+    {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"},
+    {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"},
+    {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"},
+    {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"},
+    {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"},
+    {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"},
+    {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"},
+    {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"},
+]
+typing-extensions = [
+    {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
+    {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
+    {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
+]
+virtualenv = [
+    {file = "virtualenv-20.4.1-py2.py3-none-any.whl", hash = "sha256:3e445890d7f58a066080885c48144268f85b456b788ad849a60f239eb04932af"},
+    {file = "virtualenv-20.4.1.tar.gz", hash = "sha256:f61234eee61c817a7d33e3a21642bd642f0825d3a56107b2a8deb067b03bf1e6"},
+]
 zipp = [
     {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
     {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
diff --git a/pyCombo/Makefile b/pyCombo/Makefile
deleted file mode 100755
index a632547..0000000
--- a/pyCombo/Makefile
+++ /dev/null
@@ -1,31 +0,0 @@
-# Various flags
-CXX  = g++
-LINK = $(CXX)
-#CXXFLAGS = -I -Wall -g 
-CXXFLAGS = -I -Wall -O3 -funroll-loops -pipe
-LFLAGS = -lm
-
-TARGET  = comboCPP
-
-HEADER  = Graph.h
-FILES = Graph.cc Main.cc
-
-OBJECTS = $(FILES:.cc=.o)
-
-$(TARGET): ${OBJECTS}
-	$(LINK) $^ $(LFLAGS) -o $@
-
-all: $(TARGET)
-
-clean:
-	rm -f $(OBJECTS)
-
-distclean:
-	rm -f $(OBJECTS) $(TARGET)
-
-# Compile and dependency
-$(OBJECTS): $(HEADER) Makefile
-
-
-
-
diff --git a/pyCombo/Readme.txt b/pyCombo/Readme.txt
deleted file mode 100755
index ac1f17d..0000000
--- a/pyCombo/Readme.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-
-This is an implementation (for Modularity maximization) of the community detection algorithm called "Combo" described in the paper "General optimization technique for high-quality community detection in complex networks" by Stanislav Sobolevsky, Riccardo Campari, Alexander Belyi and Carlo Ratti.
-Please, send your feedback, bug reports and questions to:
-
-alexander.belyi@gmail.com
-
-If you use this code, please, cite:
-Sobolevsky S., Campari R., Belyi A., and Ratti C. "General optimization technique for high-quality community detection in complex networks" Phys. Rev. E 90, 012811
-
---------------------------
-In order to compile, type:
-
-make
-
--------------------------
-To run the program, type:
-
-./comboCPP path_to_network_file.net [max_number_of_communities]
-
-
-path_to_network_file - path to the file in Pajek .net format
-
-max_number_of_communities - maximal number of communities to be found (INF by defaul)
-
-Example:
-./comboCPP karate.net
-
--------------------- Output ---------------------------
-
-Program outputs one file named path_to_network_file_comm_comboC++.txt containing numbers of communities for each vertex on separete line.
-And it writes achieved modularity score to standart output.
\ No newline at end of file
diff --git a/pyCombo/__init__.py b/pyCombo/__init__.py
deleted file mode 100644
index 9057e93..0000000
--- a/pyCombo/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-try:
-    from pyCombo.version import version as __version__
-except ImportError:
-    __version__ = "0.1.0.dev-unknown"
-
-from .pyCombo import getComboPartition as combo, modularity
-
-__all__ = ["combo", "modularity"]
diff --git a/pyCombo/comboCPP b/pyCombo/comboCPP
deleted file mode 100755
index bb218f6..0000000
Binary files a/pyCombo/comboCPP and /dev/null differ
diff --git a/pyCombo/pyCombo.py b/pyCombo/pyCombo.py
deleted file mode 100644
index e46b4fe..0000000
--- a/pyCombo/pyCombo.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-from __future__ import division, absolute_import
-import subprocess
-# import os
-import logging
-import tempfile
-from pathlib import Path
-from typing import Optional
-
-__author__ = "Philipp Kats"
-__copyright__ = "Philipp Kats"
-__license__ = "fmit"
-
-__all__ = ["getComboPartition", "modularity"]
-logger = logging.getLogger(__name__)
-
-
-def _check_repr(G):
-    if type(G).__name__ not in {"Graph", "MultiDiGraph"}:
-        raise ValueError(
-            f"require networkx graph as first parameter, got `{type(G).__name__}`"
-        )
-
-    if len(G) == 0:
-        raise ValueError("Graph is empty")
-
-
-def _fileojb_write_graph(f, G, weight=None) -> dict:
-
-    nodenum, nodes = {}, {}
-
-    f.write(f"*Vertices {len(G.nodes())}\n")
-    for i, n in enumerate(G.nodes()):
-        f.write(f'{i} "{n}"\n')
-        nodenum[n] = i
-        nodes[i] = n
-
-    f.write("*Arcs\n")
-
-    for e in G.edges(data=True):
-        if weight is not None:
-            f.write(f"{nodenum[e[0]]} {nodenum[e[1]]} {nodenum[e[2][weight]]}\n")
-        else:
-            f.write(f"{nodenum[e[0]]} {nodenum[e[1]]} 1\n")
-    f.flush()
-
-    cnt = 1 + len(G.edges())
-    logger.debug(f"Wrote Graph to `{f.name}` ({cnt} lines)")
-    return nodes
-
-
-def getComboPartition(
-    G, max_number_of_communities="INF", mod_resolution: int = 1, weight_prop: str = None
-):
-    """
-    calculates Combo Partition using Combo C++ script
-    all details here: https://github.com/Casyfill/pyCOMBO
-
-    G - NetworkX graph
-    max_number_of_communities - maximum number of partitions, by defeult infinite
-    weight - graph edges weight property. If None, graph assumed to be unweighted
-
-
-    #### NOTE: code generates temporary partitioning file
-    """
-
-    _check_repr(G)
-    directory = Path(__name__).parent.absolute()
-
-    with tempfile.TemporaryDirectory(dir=directory) as tmpdir:
-        with open(f"{tmpdir}/temp_graph.net", "w") as f:
-            nodes = _fileojb_write_graph(f, G, weight=weight_prop)
-
-        if max_number_of_communities is None:
-            max_number_of_communities = "INF"
-
-        # RUN COMBO
-        commands = [
-            str(directory / "pyCombo/comboCPP"),
-            f.name,
-            max_number_of_communities,
-            str(mod_resolution),
-            "temp_partition",  # file_suffix
-        ]
-
-        logger.info(f'Executing command: `{" ".join(commands)}`')
-        out = subprocess.Popen(
-            commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
-        )
-
-        stdout, stderr = out.communicate()
-        logger.debug(f"STDOUT: {stdout}")
-        if stderr is not None:
-            raise Exception(f"STDERR: {stderr}")  # TODO: setup c++ to throw stderr
-
-        try:
-            modularity_ = float(stdout)
-        except Exception as e:
-            raise Exception(stdout, e)
-
-        # READ RESULTING PARTITON
-        with open(f"{tmpdir}/temp_graph_temp_partition.txt", "r") as f:
-            partition = {nodes[i]: int(line) for i, line in enumerate(f)}
-            return partition, modularity_
-
-
-def modularity(G, partition, key: Optional[str] = None):
-    """
-    compute network modularity
-    for the given partitioning
-
-    G:              networkx graph
-    partition:      any partition
-    key:            weight attribute
-
-    """
-    _check_repr(G)
-    assert len(G.nodes()) == len(
-        partition.keys()
-    ), f"Graph got {len(G.nodes())} nodes, partition got {len(partition.keys())}"
-    weighted = key is not None
-    nodes = G.nodes()
-    # compute node weights
-    if G.is_directed():
-        w1 = G.out_degree(weight=key)
-        w2 = G.in_degree(weight=key)
-    else:
-        w1 = w2 = G.degree(weight=key)
-
-    # compute total network weight
-    if weighted:
-        T = 2.0 * sum([edge[2][key] for edge in G.edges(data=True)])
-    else:
-        T = 2.0 * len(G.edges())
-
-    M = 0  # start accumulating modularity score
-    for a in nodes:
-        for b in nodes:
-
-            try:
-                if partition[a] == partition[b]:  # if belong to the same community
-                    # get edge weight
-                    if G.has_edge(a, b):
-                        if weighted:
-                            e = G[a][b][key]
-                        else:
-                            e = 1
-                    else:
-                        e = 0
-                    # add modularity score for the considered edge
-                    M += e / T - w1[a] * w2[b] / (T ** 2)
-            except KeyError:
-                raise KeyError(a, b, partition, nodes)
-    return M
diff --git a/pycombo/__init__.py b/pycombo/__init__.py
new file mode 100644
index 0000000..8e2357c
--- /dev/null
+++ b/pycombo/__init__.py
@@ -0,0 +1,5 @@
+__version__ = "0.0.1"
+
+from .pyCombo import get_combo_partition
+
+__all__ = ["get_combo_partition"]
diff --git a/pycombo/pyCombo.py b/pycombo/pyCombo.py
new file mode 100644
index 0000000..869be2f
--- /dev/null
+++ b/pycombo/pyCombo.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from __future__ import annotations
+
+import logging
+from typing import Optional, Tuple
+import _combo as comboCPP
+
+__author__ = "Philipp Kats"
+__copyright__ = "Philipp Kats"
+__license__ = "fmit"
+__all__ = ["get_combo_partition"]
+
+logger = logging.getLogger(__name__)
+
+
+def _check_repr(graph):
+    if type(graph).__name__ not in {"Graph", "DiGraph", "MultiGraph", "MultiDiGraph"}:
+        raise ValueError(
+            f"require networkx graph as first parameter, got `{type(graph).__name__}`"
+        )
+
+    if len(graph) == 0:
+        raise ValueError("Graph is empty")
+
+
+def get_combo_partition(
+    graph,
+    weight_prop: Optional[str] = None,
+    max_communities: int = -1,
+    modularity_resolution: int = 1,
+    num_split_attempts: int = 0,
+    fixed_split_step: int = 0,
+    random_seed: int = -1,
+) -> Tuple[dict, float]:
+    """
+    Partition graph into communities using Combo algorithm.
+    All details are here: https://github.com/Casyfill/pyCOMBO
+
+    Parameters
+    ----------
+    graph : NetworkX graph or str
+        String treated as path to Pajek .net file with graph.
+    weight_prop : str, default None
+        Graph edges property to use as weights. If None, graph assumed to be unweighted.
+        Unused if graph is string.
+    max_communities : int, default -1
+        Maximum number of communities. If -1, assume to be infinite.
+    modularity_resolution : float, default 1.0
+        Modularity resolution parameter.
+    num_split_attempts : int, default 0
+        Number of split attempts. If 0, autoadjust this number automatically.
+    fixed_split_step : int, default 0
+        Step number to apply predifined split. If 0, use only random splits,
+        if >0 sets up the usage of 6 fixed type splits on every fixed_split_step.
+    random_seedd : int, default 0
+        Random seed to use.
+
+    Returns
+    -------
+    partition : dict{int : int}
+        Nodes to community labels correspondance.
+    modularity : float
+        Achieved modularity value.
+    """
+    if type(graph) is str:
+        community_labels, modularity = comboCPP.execute_from_file(
+            graph_path=graph,
+            max_communities=max_communities,
+            modularity_resolution=modularity_resolution,
+            num_split_attempts=num_split_attempts,
+            fixed_split_step=fixed_split_step,
+            random_seed=random_seed,
+        )
+        partition = {}
+        for i, community in enumerate(community_labels):
+            partition[i] = community
+    else:
+        _check_repr(graph)
+        nodenum, nodes = {}, {}
+        edges = []
+        for i, n in enumerate(graph.nodes()):
+            nodenum[n] = i
+            nodes[i] = n
+        for edge in graph.edges(data=True):
+            if weight_prop is not None:
+                edges.append(
+                    (nodenum[edge[0]], nodenum[edge[1]], edge[2].get(weight_prop, 1))
+                )
+            else:
+                edges.append((nodenum[edge[0]], nodenum[edge[1]], 1.0))
+        community_labels, modularity = comboCPP.execute(
+            size=graph.number_of_nodes(),
+            edges=edges,
+            directed=graph.is_directed(),
+            max_communities=max_communities,
+            modularity_resolution=modularity_resolution,
+            num_split_attempts=num_split_attempts,
+            fixed_split_step=fixed_split_step,
+            random_seed=random_seed,
+        )
+        partition = {}
+        for i, community in enumerate(community_labels):
+            partition[nodes[i]] = community
+    logger.debug(f"Result: {partition}, {modularity}")
+    # TODO: setup c++ to throw stderr
+
+    return partition, modularity
diff --git a/pyproject.toml b/pyproject.toml
index fca35e1..5ac5791 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,20 +1,30 @@
 [tool.poetry]
 name = "pycombo"
-version = "0.0.1"
+version = "0.0.2"
 description = "Python wrapper around Combo network partitioning algorythm (C++)"
 authors = ["Philipp <casyfill@gmail.com>"]
 license = "MIT"
+build = 'build.py'
 
 [tool.poetry.dependencies]
 python = "^3.7"
+pybind11 = "^2.6.1"
 
 [tool.poetry.dev-dependencies]
 pytest = "^6.1.2"
 networkx = "^2.5"
+pytest-benchmark = "^3.2.3"
 pytest-cov = "^2.10.1"
 python-louvain = "^0.14"
 flake8 = "3.8.3"
+black = "^20.8b1"
+pre-commit = "^2.9.3"
 
 [build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
+requires = ["poetry>=0.12", "wheel", "setuptools","pybind11==2.6.0"]
+build-backend = "poetry.masonry.api"
+# requires = ["setuptools", "wheel", "pybind11==2.6.0"]
+# build-backend = "setuptools.build_meta"
+
+[tool.pytest]
+testpaths = "tests"
diff --git a/requirements.txt b/requirements.txt
index 71a2ac4..110413c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,2 @@
 # Add your requirements here like:
-# networkx
-
+pybind11 = "^2.6.1"
\ No newline at end of file
diff --git a/src/Binder.cpp b/src/Binder.cpp
new file mode 100644
index 0000000..cf8a284
--- /dev/null
+++ b/src/Binder.cpp
@@ -0,0 +1,67 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#include "Graph.h"
+#include "Combo.h"
+
+namespace py = pybind11;
+
+
+std::tuple<std::vector<int>, double> execute_from_file(
+	std::string file_name,
+	double modularity_resolution=1.0,
+	int max_communities=-1,
+	int num_split_attempts=0,
+	int fixed_split_step=0,
+	int random_seed=-1)
+{
+	Graph graph = ReadGraphFromFile(file_name, modularity_resolution);
+	if(graph.Size() <= 0) {
+		std::cerr << "Error: graph is empty" << std::endl;
+		return {std::vector<int>(), -1.0};
+	}
+	ComboAlgorithm combo(random_seed, num_split_attempts, fixed_split_step);
+	combo.Run(graph, max_communities);
+	return {graph.Communities(), graph.Modularity()};
+}
+
+std::tuple< std::vector<int>, double> execute(
+	int size,
+	const std::vector<std::tuple<int, int, double>>& edges,
+	bool directed=false,
+	double modularity_resolution=1.0,
+	int max_communities=-1,
+	int num_split_attempts=0,
+	int fixed_split_step=0,
+	int random_seed=-1)
+{
+	Graph graph(size, edges, directed, modularity_resolution);
+	ComboAlgorithm combo(random_seed, num_split_attempts, fixed_split_step);
+	combo.Run(graph, max_communities);
+	return {graph.Communities(), graph.Modularity()};
+}
+
+
+PYBIND11_MODULE(_combo, m) {
+    m.doc() = "Python binding for Combo community detection algorithm"; // optional module docstring
+
+	m.def("execute_from_file", &execute_from_file, "execute combo algorithm on a graph read from specified file",
+		py::arg("graph_path"),
+		py::arg("modularity_resolution") = 1.0,
+		py::arg("max_communities") = -1,
+		py::arg("num_split_attempts") = 0,
+		py::arg("fixed_split_step") = 0,
+		py::arg("random_seed") = -1
+		);
+
+	m.def("execute", &execute, "execute combo algorithm on a graph passed as list of edges",
+		py::arg("size"),
+		py::arg("edges"),
+		py::arg("directed") = false,
+		py::arg("modularity_resolution") = 1.0,
+		py::arg("max_communities") = -1,
+		py::arg("num_split_attempts") = 0,
+		py::arg("fixed_split_step") = 0,
+		py::arg("random_seed") = -1
+	);
+}
diff --git a/pyCombo/COPYING b/src/COPYING
similarity index 100%
rename from pyCombo/COPYING
rename to src/COPYING
diff --git a/src/Combo.cpp b/src/Combo.cpp
new file mode 100755
index 0000000..4139248
--- /dev/null
+++ b/src/Combo.cpp
@@ -0,0 +1,364 @@
+/*                                                                            
+    Copyright 2021
+    Alexander Belyi <alexander.belyi@gmail.com>,
+    Stanislav Sobolevsky <stanly@mit.edu>                                               
+                                                                            
+    This file is part of Combo algorithm.
+
+    Combo is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Combo is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Combo.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "Combo.h"
+#include "Graph.h"
+
+#include <algorithm>
+#include <chrono>
+#include <cmath>
+#include <iostream>
+#include <random>
+using namespace std;
+
+#define THRESHOLD 1e-6
+
+
+vector<double> Sum(const vector< vector<double> >& matrix)
+{
+	int n = matrix.size();
+	vector<double> res(n, 0.0);
+	for (int i = 0; i < n; ++i)
+		for (int j = 0; j < n; ++j)
+			res[i] += matrix[i][j];
+	return res;
+}
+
+template<typename T> bool Positive(T x) {return x > 0.0;}
+template<typename T> bool Negative(T x) {return x < 0.0;}
+template<typename T> bool NotNegative(T x) {return x >= 0.0;}
+template<typename T> bool NotPositive(T x) {return x <= 0.0;}
+vector<double> SumPos(const vector< vector<double> >& matrix, bool (*Pred)(double) = NULL)
+{
+	int n = matrix.size();
+	vector<double> res(n, 0.0);
+	for (int i = 0; i < n; ++i)
+		for (int j = 0; j < n; ++j)
+			if (Pred && Pred(matrix[i][j]))
+				res[i] += matrix[i][j];
+	return res;
+}
+
+template<typename T>
+bool TestAll(const vector<T>& vec, bool (*Pred)(T))
+{
+	int n = vec.size();
+	for (int i = 0; i < n; ++i)
+		if (!Pred(vec[i]))
+			return false;
+	return true;
+}
+
+double ModularityGain(const vector< vector<double> >& Q, const vector<double>& correction_vector, const vector<int>& community)
+{
+	int n = community.size();
+	double mod_gain = 0.0;
+	for (int i = 0; i < n; ++i) {
+		for (int j = 0; j < n; ++j)
+			if (community[i] == community[j])
+				mod_gain += Q[i][j];
+			else
+				mod_gain -= Q[i][j];
+	}
+	mod_gain *= 0.5;
+	for (int i = 0; i < n; ++i) {
+		if (community[i])
+			mod_gain += correction_vector[i];
+		else
+			mod_gain -= correction_vector[i];
+	}
+	return mod_gain;
+}
+
+//perform a split improvement using a Kernighan-Lin-style iterative shifts series
+double PerformKernighansShift(const vector< vector<double> >& Q, const vector<double>& correction_vector,
+	const vector<int>& communities_old, vector<int>& communities_new)
+{
+ 	int n = Q.size();
+	vector<double> gains(n, 0.0);
+	for (int i = 0; i < n; ++i) {
+		for (int j = 0; j < n; ++j)
+			if (i != j) {
+				if (communities_old[i] == communities_old[j])
+					gains[i] -= Q[i][j];
+				else
+					gains[i] += Q[i][j];
+			}
+		if (communities_old[i])
+			gains[i] -= correction_vector[i];
+		else
+			gains[i] += correction_vector[i];
+		gains[i] *= 2;
+	}
+	vector<double> gains_got(n, 0.0);
+	vector<int> gains_indexes(n, 0);
+	communities_new = communities_old;
+	for (int i = 0; i < n; ++i) {
+		vector<double>::iterator it = max_element(gains.begin(), gains.end());
+		gains_got[i] = *it;
+		int gains_ind = it - gains.begin();
+		gains_indexes[i] = gains_ind;
+		if (i > 0)
+			gains_got[i] = gains_got[i] + gains_got[i-1];
+		for (int j = 0; j < n; ++j)
+			if (communities_new[gains_ind] == communities_new[j])
+				gains[j] += 4 * Q[gains_ind][j];
+			else
+				gains[j] -= 4 * Q[gains_ind][j];
+		communities_new[gains_ind] = !communities_new[gains_ind];
+		gains[gains_ind] = gains[gains_ind] - 2*n;
+	}
+	vector<double>::iterator it = max_element(gains_got.begin(), gains_got.end());
+	double mod_gain = *it;
+	int steps_to_get_max_gain = it - gains_got.begin() + 1;
+	if (mod_gain > 0) {
+		communities_new = communities_old;
+		for (int i = 0; i < steps_to_get_max_gain; ++i)
+			communities_new[gains_indexes[i]] = !communities_new[gains_indexes[i]];
+	} else {
+		communities_new = communities_old;
+		mod_gain = 0;
+	}
+	return mod_gain;
+}
+
+//try to split the subnetwork with respect to the correction vector
+double ComboAlgorithm::Split(vector< vector<double> >& Q,
+	const vector<double>& correction_vector, vector<int>& to_be_moved)
+{
+	double mod_gain = 0.0;
+	vector<double> sumQ = Sum(Q);
+	int n = Q.size();
+	for (int i = 0; i < n; ++i)
+		Q[i][i] += 2 * correction_vector[i] - sumQ[i]; //adjust the submatrix
+	int tries;
+	if (m_num_split_attempts > 0)
+		tries = m_num_split_attempts;
+	else
+		tries = pow(abs(log(m_current_best_gain)), m_autoC2) / m_autoC1 + 3;
+	for (int tryI = 1; tryI <= tries; ++tryI)
+	{
+		vector<int> communities(n); // 0 - stay in origin, 1 - move to destination
+		//perform an initial simple split
+		if (m_fixed_split_step > 0 && tryI <= 6 * m_fixed_split_step && tryI % m_fixed_split_step == 0) {
+			//perform one of predifined split types
+			int fixed_split_type = tryI / m_fixed_split_step;
+			if (fixed_split_type == 1 || fixed_split_type == 2)
+				communities.assign(n, 2 - fixed_split_type);
+			else {
+				vector<double> sum_pos = SumPos(Q, Positive);
+				int node_ind;
+				if (fixed_split_type == 3 || fixed_split_type == 4)
+					node_ind = max_element(sum_pos.begin(), sum_pos.end()) - sum_pos.begin();
+				else
+					node_ind = min_element(sum_pos.begin(), sum_pos.end()) - sum_pos.begin();  
+				communities.assign(n, -1);
+				int community = 1;
+				communities[node_ind] = community;
+				while (true) {
+					int next_node_ind = -1;
+					double cur_min = 1e300;
+					double cur_max = -1e300;
+					for (int i = 0; i < n; ++i) {
+						if (communities[i] == -1) {
+							if ((fixed_split_type == 3 || fixed_split_type == 5) && Q[node_ind][i] < cur_min) {
+								next_node_ind = i;
+								cur_min = Q[node_ind][i];
+							} else if ((fixed_split_type == 4 || fixed_split_type == 6) && Q[node_ind][i] > cur_max) {
+								next_node_ind = i;
+								cur_max = Q[node_ind][i];
+							}
+						}
+					}
+					if (next_node_ind == -1)
+						break;
+					node_ind = next_node_ind;
+					community ^= 1;
+					communities[node_ind] = community;
+				}
+			}
+		} else {
+			for (int i = 0; i < n; ++i)
+				communities[i] = int(m_bernoulli_distribution(m_random_number_generator));
+		}
+
+		double mod_gain_total = ModularityGain(Q, correction_vector, communities);
+		double mod_gain_from_shift = 1;
+		while (mod_gain_from_shift > THRESHOLD) {
+			vector<int> communities_shifted(n);
+			mod_gain_from_shift = PerformKernighansShift(Q, correction_vector, communities, communities_shifted);
+			if (mod_gain_from_shift > THRESHOLD) {
+				mod_gain_total += mod_gain_from_shift;
+				communities = communities_shifted;
+				if (m_debug_verify) {
+					double mod_gain_verify = ModularityGain(Q, correction_vector, communities);
+					if (fabs(mod_gain_verify - mod_gain_total) > THRESHOLD)
+						cerr << "ERROR" << endl;
+				}
+
+			}
+		}
+		if (mod_gain < mod_gain_total) {
+			to_be_moved = communities;
+			mod_gain = mod_gain_total;
+		}
+		if (mod_gain <= 1e-6)
+			tries = int(tries / 2);
+	}
+
+	if (fabs(mod_gain) < THRESHOLD)
+		to_be_moved.assign(n, 1);
+
+	return mod_gain;
+}
+
+void ComboAlgorithm::ReCalc(Graph& graph, vector< vector<double> >& moves, vector< vector<int> >& splits_communities, int origin, int destination)
+{
+	moves[origin][destination] = 0;
+	if (origin != destination) {
+		vector<int> orig_comm_ind = graph.CommunityIndices(origin);
+		if (!orig_comm_ind.empty()) {
+			vector<double> correction_vector = graph.GetCorrectionVector(orig_comm_ind, graph.CommunityIndices(destination));
+			vector<int> to_be_moved(orig_comm_ind.size());
+			vector< vector<double> > Q = graph.GetModularitySubmatrix(orig_comm_ind);
+			moves[origin][destination] = Split(Q, correction_vector, to_be_moved);
+			for (size_t i = 0; i < to_be_moved.size(); ++i)
+				splits_communities[destination][orig_comm_ind[i]] = to_be_moved[i];
+		}
+	}
+}
+
+double BestGain(const vector< vector<double> >& moves, int& origin, int& destination)
+{
+	double best_gain = -1;
+	for (size_t i = 0; i < moves.size(); ++i)
+		for (size_t j = 0; j < moves.size(); ++ j)
+			if (best_gain < moves[i][j]) {
+				best_gain = moves[i][j];
+				origin = i;
+				destination = j;
+			}
+	return best_gain;
+}
+
+void DeleteEmptyCommunities(Graph& graph, vector< vector<double> >& moves, vector< vector<int> >& splits_communities, int origin)
+{
+	if (graph.DeleteCommunityIfEmpty(origin)) {
+		int comm_number = graph.NumberOfCommunities();
+		for (int i = origin; i < comm_number; ++i)
+			moves[i] = moves[i+1];
+		moves[comm_number].assign(comm_number+2, 0);
+		for (size_t i = 0; i < moves.size(); ++i) {
+			for (int j = origin; j < comm_number+1; ++j)
+				moves[i][j] = moves[i][j+1];
+			moves[i][comm_number+1] = 0;
+		}
+		for (int i = origin; i < comm_number+1; ++i)
+			splits_communities[i] = splits_communities[i+1];
+	}
+}
+
+void ComboAlgorithm::Run(Graph& graph, int max_comunities)
+{
+	if (max_comunities <= 0)
+		max_comunities = graph.Size();
+	graph.CalcModMatrix();
+	graph.SetCommunities(vector<int>(graph.Size(), 0));
+	double currentMod = graph.Modularity();
+	vector< vector<double> > moves(2, vector<double>(2, 0)); //results of splitting communities
+	//vectors of boolean meaning that corresponding vertex should be moved to dest
+	vector< vector<int> > splits_communities(2, vector<int>(graph.Size(), 0)); //best split vectors
+	m_current_best_gain = 1;
+	int origin = 0, destination = 0;
+	for (origin = 0; origin < graph.NumberOfCommunities(); ++ origin)
+		for (destination = 0; destination < graph.NumberOfCommunities() + (graph.NumberOfCommunities() < max_comunities); ++destination)
+			ReCalc(graph, moves, splits_communities, origin, destination);
+	m_current_best_gain = BestGain(moves, origin, destination);
+	while (m_current_best_gain > THRESHOLD) {
+		bool comunity_added = destination >= graph.NumberOfCommunities();
+		graph.PerformSplit(origin, destination, splits_communities[destination]);
+		if (m_debug_verify) {
+			double oldMod = currentMod;
+			currentMod = graph.Modularity();
+			if (fabs(currentMod - oldMod - m_current_best_gain) > THRESHOLD)
+				cerr << "ERROR" << endl;
+		}
+		if (comunity_added && destination < max_comunities - 1) {
+			if (destination + 1 >= int(moves.size())) {
+				for (size_t i = 0; i < moves.size(); ++i)
+					moves[i].push_back(0);
+				moves.push_back(vector<double>(moves.size() + 1, 0));
+				splits_communities.push_back(vector<int>(graph.Size(), 0));
+			}
+			for (int i = 0; i < destination; ++i) {
+				moves[i][destination+1] = moves[i][destination];
+				splits_communities[destination+1] = splits_communities[destination];
+			}
+		}
+		for (int i = 0; i < graph.NumberOfCommunities() + (graph.NumberOfCommunities() < max_comunities); ++i) {
+			ReCalc(graph, moves, splits_communities, origin, i);
+			ReCalc(graph, moves, splits_communities, destination, i);
+			if (i != destination && i < graph.NumberOfCommunities())
+				ReCalc(graph, moves, splits_communities, i, origin);
+			if (i != origin && i < graph.NumberOfCommunities())
+				ReCalc(graph, moves, splits_communities, i, destination);
+		}
+		DeleteEmptyCommunities(graph, moves, splits_communities, origin); //remove origin community if empty
+		m_current_best_gain = BestGain(moves, origin, destination);
+	}
+}
+
+void ComboAlgorithm::SetNumberOfSplitAttempts(int split_tries)
+{
+	if (split_tries <= 0) {
+		if (split_tries == -1) {
+			m_autoC1 = 1.5*log(10);
+			m_autoC2 = 1;
+		} else if (split_tries == -2) {
+			m_autoC1 = log(10);
+			m_autoC2 = 1;
+		} else {
+            m_autoC1 = 2;
+            m_autoC2 = 1.5;
+		}  
+	}
+	m_num_split_attempts = split_tries;
+}
+
+ComboAlgorithm::ComboAlgorithm(long long random_seed, int num_split_attempts, int fixed_split_step) :
+	m_fixed_split_step(fixed_split_step),
+	m_random_number_generator(random_seed < 0 ? std::chrono::duration_cast<std::chrono::milliseconds>(
+		std::chrono::steady_clock::now().time_since_epoch()).count() : random_seed),
+	m_bernoulli_distribution(0.5)
+{
+	SetNumberOfSplitAttempts(num_split_attempts);
+}
+
+ComboAlgorithm::ComboAlgorithm(): 
+	ComboAlgorithm(std::chrono::duration_cast<std::chrono::milliseconds>(
+		std::chrono::steady_clock::now().time_since_epoch()).count(), 0, 0)
+{}
+
+ComboAlgorithm::ComboAlgorithm(int num_split_attempts, int fixed_split_step) :
+	ComboAlgorithm(std::chrono::duration_cast<std::chrono::milliseconds>(
+		std::chrono::steady_clock::now().time_since_epoch()).count(), num_split_attempts, fixed_split_step)
+{}
diff --git a/src/Combo.h b/src/Combo.h
new file mode 100755
index 0000000..a8fd462
--- /dev/null
+++ b/src/Combo.h
@@ -0,0 +1,56 @@
+/*                                                                            
+    Copyright 2021
+    Alexander Belyi <alexander.belyi@gmail.com>,
+    Stanislav Sobolevsky <stanly@mit.edu>                                               
+                                                                            
+    This file is part of Combo algorithm.
+
+    Combo is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Combo is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Combo.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef COMBO_H
+#define COMBO_H
+
+#include "Graph.h"
+#include <random>
+#include <vector>
+
+class ComboAlgorithm {
+public:
+    ComboAlgorithm();
+    explicit ComboAlgorithm(long long random_seed, int num_split_attempts, int fixed_split_step);
+    ComboAlgorithm(int num_split_attempts, int fixed_split_step);
+    void Run(Graph& graph, int max_comunities = -1);
+    void SetFixedSplitStep(int fixed_split_step) {m_fixed_split_step = fixed_split_step;}
+    void SetNumberOfSplitAttempts(int split_tries);
+private:
+    //settings
+    const bool m_debug_verify = false;
+    // number of split attempts; 0 - autoadjust this number based on m_current_best_gain
+    int m_num_split_attempts;
+    // step number to apply predifined split; 0 - use only random splits
+    // if >0 sets up the usage of 6 fixed type splits on every m_fixed_split_step
+    int m_fixed_split_step;
+    double m_autoC1;
+    double m_autoC2;
+    //implementation
+    std::mt19937 m_random_number_generator;
+    std::bernoulli_distribution m_bernoulli_distribution;
+    double m_current_best_gain;
+    void ReCalc(Graph& graph, std::vector< std::vector<double> >& moves,
+        std::vector< std::vector<int> >& splits_communities, int origin, int destination);
+    double Split(std::vector< std::vector<double> >& Q, const std::vector<double>& correction_vector, std::vector<int>& to_be_moved);
+};
+
+#endif //COMBO_H
\ No newline at end of file
diff --git a/src/Graph.cpp b/src/Graph.cpp
new file mode 100644
index 0000000..adcb020
--- /dev/null
+++ b/src/Graph.cpp
@@ -0,0 +1,487 @@
+/*                                                                            
+    Copyright 2021
+    Alexander Belyi <alexander.belyi@gmail.com>,
+    Stanislav Sobolevsky <stanly@mit.edu>                                               
+                                                                            
+    This file is part of Combo algorithm.
+
+    Combo is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Combo is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Combo.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "Graph.h"
+
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <locale>
+#include <set>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <vector>
+#include <utility>
+using std::get;
+using std::ifstream;
+using std::ofstream;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::tuple;
+using std::vector;
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::max;
+using std::min;
+using std::move;
+
+Graph ReadFromEdgelist(const string& file_name, double mod_resolution)
+{
+	ifstream file(file_name.c_str());
+	if (!file.is_open()) {
+        cerr << "File " << file_name << " can not be opened." << endl;
+		return Graph();
+    }
+	vector<int> sources, destinations;
+	vector<double> weights;
+	int min_vertex_number = 2e9;
+	int max_vertex_number = 0;
+	while (file.good()) {
+		string line;
+		getline(file, line);
+		int src = -1, dst = -1;
+		double weight = 1.0;
+		stringstream str_stream(line);
+		str_stream >> src >> dst;
+		if (!str_stream.eof())
+			str_stream >> weight;
+		if (!str_stream.fail() && src != -1 && dst != -1)
+		{
+			min_vertex_number = min(min_vertex_number, min(src, dst));
+			max_vertex_number = max(max_vertex_number, max(src, dst));
+			sources.push_back(src);
+			destinations.push_back(dst);
+			weights.push_back(weight);
+		}
+	}
+	file.close();
+	bool is_directed = true;
+	int size = 1 + max_vertex_number - min_vertex_number;
+	for (size_t i = 0; i < sources.size(); ++i) {
+		sources[i] -= min_vertex_number;
+		destinations[i] -= min_vertex_number;
+	}
+	return Graph(size, sources, destinations, weights, is_directed, mod_resolution);
+}
+
+Graph ReadFromPajek(const string& file_name, double mod_resolution)
+{
+	ifstream file(file_name.c_str());
+	if (!file.is_open()) {
+		cerr << "File " << file_name << " can not be opened." << endl;
+		return Graph();
+    }
+    std::locale locale;
+	vector<int> sources, destinations;
+	vector<double> weights;
+	int min_vertex_number = 2e9;
+	int max_vertex_number = 0;
+	bool is_directed = false;
+	bool skip = true;
+	while (file.good()) {
+		string line;
+		getline(file, line);
+		stringstream str_stream(line);
+        string trimmed_string;
+        str_stream >> trimmed_string; // Strips carriage return on Windows
+        transform(trimmed_string.begin(), trimmed_string.end(), trimmed_string.begin(), [&locale](char elem){return std::tolower(elem, locale);});
+		if (trimmed_string == "*edges") {
+			skip = false;
+			is_directed = false;
+		} else if (trimmed_string == "*arcs") {
+			skip = false;
+			is_directed = true;
+		} else if (skip) {
+			str_stream.str(line);
+			int v = -1;
+			str_stream >> v;
+			if (!str_stream.fail()) {
+				min_vertex_number = min(min_vertex_number, v);
+				max_vertex_number = max(max_vertex_number, v);
+			}
+		} else if (!skip) {
+			int src = -1, dst = -1;
+			double weight = 1.0;
+			str_stream.str(line);
+			str_stream >> src >> dst;
+			if (!str_stream.eof())
+				str_stream >> weight;
+			if (!str_stream.fail() && src != -1 && dst != -1) {
+				min_vertex_number = min(min_vertex_number, min(src, dst));
+				max_vertex_number = max(max_vertex_number, max(src, dst));
+				sources.push_back(src);
+				destinations.push_back(dst);
+				weights.push_back(weight);
+			}
+		}
+	}
+	file.close();
+	int size = 1 + max_vertex_number - min_vertex_number;
+	for (size_t i = 0; i < sources.size(); ++i) {
+		sources[i] -= min_vertex_number;
+		destinations[i] -= min_vertex_number;
+	}
+	return Graph(size, sources, destinations, weights, is_directed, mod_resolution);
+}
+
+Graph ReadGraphFromFile(const string& file_name, double mod_resolution)
+{
+	size_t dot_position = file_name.rfind('.');
+	if (dot_position != string::npos) {
+		string ext = file_name.substr(file_name.rfind('.'), file_name.length() - file_name.rfind('.'));
+		if (ext == ".edgelist")
+			return ReadFromEdgelist(file_name, mod_resolution);
+		else if (ext == ".net")
+			return ReadFromPajek(file_name, mod_resolution);
+	}
+	cerr << "Error in ReadGraphFromFile: unsupported file format. Must be either Pajek .net or .edgelist" << endl;
+	return Graph();
+}
+
+void swap(Graph& left, Graph& right)
+{
+	using std::swap;
+	swap(left.m_size, right.m_size);
+	swap(left.m_total_weight, right.m_total_weight);
+	swap(left.m_number_of_communities, right.m_number_of_communities);
+	swap(left.m_is_directed, right.m_is_directed);
+	swap(left.m_modularity_resolution, right.m_modularity_resolution);
+	swap(left.m_matrix, right.m_matrix);
+	swap(left.m_modularity_matrix, right.m_modularity_matrix);
+	swap(left.m_communities, right.m_communities);
+}
+
+Graph::Graph(bool is_directed, double modularity_resolution)
+{
+	m_size = 0;
+	m_total_weight = 0.0;
+	m_is_directed = is_directed;
+	m_number_of_communities = 0;
+	m_modularity_resolution = modularity_resolution;
+}
+
+Graph::Graph(int size, const std::vector<int>& sources, const std::vector<int>& destinations, const std::vector<double>& weights,
+	bool is_directed, double modularity_resolution) :
+	Graph(is_directed, modularity_resolution)
+{
+	FillModMatrix(size, sources, destinations, weights);
+}
+
+Graph::Graph(int size, const std::vector<std::tuple<int, int, double>>& edges, bool is_directed, double modularity_resolution) :
+	Graph(is_directed, modularity_resolution)
+{
+	FillModMatrix(size, edges);
+}
+
+Graph::Graph(const Graph& graph) :
+	m_matrix(graph.m_matrix),
+	m_modularity_matrix(graph.m_modularity_matrix),
+	m_communities(graph.m_communities)
+{
+	m_size = graph.Size();
+	m_total_weight = graph.m_total_weight;
+	m_is_directed = graph.IsDirected();
+	m_modularity_resolution = graph.ModularityResolution();
+	m_number_of_communities = graph.m_number_of_communities;
+}
+
+Graph::Graph(Graph&& graph) noexcept :
+	Graph()
+{
+	swap(*this, graph);
+}
+
+Graph& Graph::operator=(Graph graph)
+{
+	swap(*this, graph);
+	return *this;
+}
+
+void Graph::FillMatrix(int size, const vector<int>& sources, const vector<int>& destinations, const vector<double>& weights)
+{
+	if (sources.size() != destinations.size() || sources.size() != weights.size()) {
+		cerr << "Error in FillMatrix: sources, destinations and weights must be of the same size." << endl;
+		return;
+	}
+	m_total_weight = 0;
+	for (double weight : weights)
+		m_total_weight += weight;
+	if (!m_is_directed)
+		m_total_weight *= 2;
+	m_size = 1 + max(*max_element(sources.begin(), sources.end()), *max_element(destinations.begin(), destinations.end()));
+	if (m_size > size) {
+		cerr << "Error in FillMatrix: vertices' index cannot be greater than size-1" << endl;
+		return;
+	}
+	m_size = size;
+	m_matrix.assign(m_size, vector<double>(m_size, 0));
+	for (size_t i = 0; i < sources.size(); ++i) {
+		m_matrix[sources[i]][destinations[i]] += weights[i];
+		if (!m_is_directed)
+			m_matrix[destinations[i]][sources[i]] += weights[i];
+	}
+}
+
+void Graph::FillMatrix(int size, const vector<tuple<int, int, double>>& edges)
+{
+	m_total_weight = 0;
+	m_size = 0;
+	for (const tuple<int, int, double>& edge : edges) {
+		m_total_weight += get<2>(edge);
+		m_size = max(m_size, max(get<0>(edge), get<1>(edge)));
+	}
+	m_size++;
+	if (m_size > size) {
+		cerr << "Error in FillMatrix: vertices' index cannot be greater than size-1" << endl;
+		return;
+	}
+	m_size = size;
+	if (!m_is_directed)
+		m_total_weight *= 2;
+	m_matrix.assign(m_size, vector<double>(m_size, 0));
+	for (const tuple<int, int, double>& edge : edges) {
+		int source = get<0>(edge);
+		int destination = get<1>(edge);
+		double weight = get<2>(edge);
+		m_matrix[source][destination] += weight;
+		if (!m_is_directed)
+			m_matrix[destination][source] += weight;
+	}
+}
+
+void Graph::FillModMatrix(int size, const vector<int>& sources, const vector<int>& destinations, const vector<double>& weights)
+{
+	m_total_weight = 0;
+	for (double weight : weights)
+		m_total_weight += weight;
+	if (!m_is_directed)
+		m_total_weight *= 2;
+	m_size = 1 + max(*max_element(sources.begin(), sources.end()), *max_element(destinations.begin(), destinations.end()));
+	if (m_size > size) {
+		cerr << "Error in FillModMatrix: vertices' index cannot be greater than size-1" << endl;
+		return;
+	}
+	m_size = size;
+	m_modularity_matrix.assign(m_size, vector<double>(m_size, 0));
+	vector<double> sumQ2(m_size, 0.0);
+	vector<double> sumQ1(m_size, 0.0);
+	for (size_t i = 0; i < sources.size(); ++i) {
+		m_modularity_matrix[sources[i]][destinations[i]] += weights[i] / m_total_weight;
+		if (!m_is_directed)
+			m_modularity_matrix[destinations[i]][sources[i]] += weights[i] / m_total_weight;
+		sumQ1[sources[i]] += weights[i] / m_total_weight;
+		sumQ2[destinations[i]] += weights[i] / m_total_weight;
+		if (!m_is_directed) {
+			sumQ1[destinations[i]] += weights[i] / m_total_weight;
+			sumQ2[sources[i]] += weights[i] / m_total_weight;
+		}
+	}
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] -= m_modularity_resolution * sumQ1[i]*sumQ2[j];
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] = m_modularity_matrix[j][i] = (m_modularity_matrix[i][j] + m_modularity_matrix[j][i]) / 2;
+}
+
+void Graph::FillModMatrix(int size, const std::vector<std::tuple<int, int, double>>& edges)
+{
+	m_total_weight = 0;
+	m_size = 0;
+	for (const tuple<int, int, double>& edge : edges) {
+		m_total_weight += get<2>(edge);
+		m_size = max(m_size, max(get<0>(edge), get<1>(edge)));
+	}
+	m_size++;
+	if (m_size > size) {
+		cerr << "Error in FillModMatrix: vertices' index cannot be greater than size-1" << endl;
+		return;
+	}
+	m_size = size;
+	m_modularity_matrix.assign(m_size, vector<double>(m_size, 0));
+	vector<double> sumQ2(m_size, 0.0);
+	vector<double> sumQ1(m_size, 0.0);
+	for (const tuple<int, int, double>& edge : edges) {
+		int source = get<0>(edge);
+		int destination = get<1>(edge);
+		double weight = get<2>(edge);
+		m_modularity_matrix[source][destination] += weight / m_total_weight;
+		if (!m_is_directed)
+			m_modularity_matrix[destination][source] += weight / m_total_weight;
+		sumQ1[source] += weight / m_total_weight;
+		sumQ2[destination] += weight / m_total_weight;
+		if (!m_is_directed) {
+			sumQ1[destination] += weight / m_total_weight;
+			sumQ2[source] += weight / m_total_weight;
+		}
+	}
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] -= m_modularity_resolution * sumQ1[i]*sumQ2[j];
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] = m_modularity_matrix[j][i] = (m_modularity_matrix[i][j] + m_modularity_matrix[j][i]) / 2;
+}
+
+double Graph::EdgeWeight(int u, int v) const
+{
+	return m_matrix[u][v];
+}
+
+void Graph::CalcModMatrix()
+{
+	if (!m_modularity_matrix.empty())
+		return;
+
+	m_modularity_matrix.assign(m_size, vector<double>(m_size, 0.0));
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] = EdgeWeight(i, j) / m_total_weight;
+	
+	vector<double> sumQ2(m_size, 0.0);
+	vector<double> sumQ1(m_size, 0.0);
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j) {
+			sumQ1[i] += m_modularity_matrix[i][j];
+			sumQ2[j] += m_modularity_matrix[i][j];
+		}
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] -= m_modularity_resolution * sumQ1[i]*sumQ2[j];
+	for (int i = 0; i < m_size; ++i)
+		for (int j = 0; j < m_size; ++j)
+			m_modularity_matrix[i][j] = m_modularity_matrix[j][i] = (m_modularity_matrix[i][j] + m_modularity_matrix[j][i]) / 2;
+}
+
+void Graph::Print() const
+{
+	cout << "Matrix:" << endl;
+	for (int i = 0; i < m_size; ++i) {
+		for (int j = 0; j < m_size; ++j) {
+			cout << m_matrix[i][j] << '\t';
+		}
+		cout << endl;
+	}
+	cout << "Modularity matrix:" << endl;
+	for (int i = 0; i < m_size; ++i) {
+		for (int j = 0; j < m_size; ++j) {
+			cout << m_modularity_matrix[i][j] << '\t';
+		}
+		cout << endl;
+	}
+}
+
+void Graph::PrintCommunity(const string& file_name) const
+{
+	ofstream file(file_name.c_str());
+	if (!file.is_open()) {
+        cerr << "File " << file_name << " can not be opened." << endl;
+		return;
+    }
+	for (int i = 0; i < m_size; ++i)
+		file << m_communities[i] << endl;
+	file.close();
+}
+
+void Graph::SetCommunities(const vector<int>& new_communities, int number)
+{
+	if (m_size != int(new_communities.size()))
+		return;
+	m_communities = new_communities;
+	if (number == -1)
+		m_number_of_communities = *max_element(m_communities.begin(), m_communities.end()) + 1;
+	else
+		m_number_of_communities = number;
+}
+
+double Graph::Modularity() const
+{
+	double modularity = 0;
+	for (size_t i = 0; i < m_modularity_matrix.size(); ++i)
+		for (size_t j = 0; j < m_modularity_matrix.size(); ++j)
+			if (m_communities[i] == m_communities[j])
+				modularity += m_modularity_matrix[i][j];
+	return modularity;
+}
+
+void Graph::PerformSplit(int origin, int destination, const vector<int>& to_be_moved)
+{
+	if (destination > m_number_of_communities)
+		destination = m_number_of_communities;
+	if (destination == m_number_of_communities)
+		++m_number_of_communities;
+	for (int i = 0; i < m_size; ++i)
+		if (m_communities[i] == origin && to_be_moved[i])
+			m_communities[i] = destination;
+}
+
+bool Graph::IsCommunityEmpty(int community) const
+{
+	for (int i = 0; i < m_size; ++i)
+		if (m_communities[i] == community)
+			return false;
+	return true;
+}
+
+bool Graph::DeleteCommunityIfEmpty(int community)
+{
+	if (IsCommunityEmpty(community)) {
+		set<int> community_labels;
+        for (int i = 0; i < m_size; ++i) {
+			if (m_communities[i] > community)
+				--m_communities[i];
+			community_labels.insert(m_communities[i]);
+		}
+		m_number_of_communities = community_labels.size();
+        return true;
+	}
+	return false;
+}
+
+vector<int> Graph::CommunityIndices(int community) const
+{
+	vector<int> res;
+	for (int i = 0; i < m_size; ++i)
+		if (m_communities[i] == community)
+			res.push_back(i);
+	return res;
+}
+
+vector< vector<double> > Graph::GetModularitySubmatrix(const vector<int>& indices) const
+{
+	vector< vector<double> > res(indices.size(), vector<double>(indices.size()));
+	for (size_t i = 0; i < indices.size(); ++i)
+		for (size_t j = 0; j < indices.size(); ++j)
+			res[i][j] = m_modularity_matrix[indices[i]][indices[j]];
+	return res;
+}
+
+vector<double> Graph::GetCorrectionVector(const vector<int>& orig_comm_ind, const vector<int>& dest_comm_ind) const
+{
+	vector<double> res(orig_comm_ind.size(), 0.0);
+	for (size_t i = 0; i < orig_comm_ind.size(); ++i)
+		for (size_t j = 0; j < dest_comm_ind.size(); ++j)
+			res[i] += m_modularity_matrix[dest_comm_ind[j]][orig_comm_ind[i]];
+	return res;
+}
diff --git a/src/Graph.h b/src/Graph.h
new file mode 100644
index 0000000..f761810
--- /dev/null
+++ b/src/Graph.h
@@ -0,0 +1,87 @@
+/*                                                                            
+    Copyright 2021
+    Alexander Belyi <alexander.belyi@gmail.com>,
+    Stanislav Sobolevsky <stanly@mit.edu>                                               
+                                                                            
+    This file is part of Combo algorithm.
+
+    Combo is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Combo is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Combo.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef GRAPH_H
+#define GRAPH_H
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+class Graph;
+
+Graph ReadGraphFromFile(const std::string& file_name, double mod_resolution = 1);
+
+// To save memory we store only modularity matrix and keep m_matrix empty
+class Graph
+{
+public:
+	explicit Graph(bool is_directed = false, double modularity_resolution = 1);
+	Graph(int size, const std::vector<int>& sources, const std::vector<int>& destinations, const std::vector<double>& weights,
+		bool is_directed, double modularity_resolution = 1);
+	Graph(int size, const std::vector<std::tuple<int, int, double>>& edges, bool is_directed, double modularity_resolution = 1);
+	Graph(const Graph& graph);
+	Graph(Graph&& graph) noexcept;
+	Graph& operator=(Graph graph);
+
+	void CalcModMatrix();
+	int Size() const {return m_size;}
+	int IsDirected() const {return m_is_directed;}
+	int ModularityResolution() const {return m_modularity_resolution;}
+	int NumberOfCommunities() const {return m_number_of_communities;};
+	double EdgeWeight(int u, int v) const;
+	bool IsCommunityEmpty(int community) const;
+
+	double Modularity() const;
+	std::vector< std::vector<double> > GetModularitySubmatrix(const std::vector<int>& indices) const;
+	std::vector<double> GetCorrectionVector(const std::vector<int>& orig_comm_ind, const std::vector<int>& dest_comm_ind) const;
+	
+	void SetCommunities(const std::vector<int>& new_communities, int number = -1);
+	std::vector<int> Communities() const {return m_communities;};
+	std::vector<int> CommunityIndices(int comm) const;
+
+	void PerformSplit(int origin, int destination, const std::vector<int>& split_communities);
+	bool DeleteCommunityIfEmpty(int community);
+	void Print() const;
+	void PrintCommunity(const std::string& file_name) const;
+
+	friend void swap(Graph& left, Graph& right);
+
+private:
+	void FillMatrix(int size, const std::vector<int>& sources, const std::vector<int>& destinations, const std::vector<double>& weights);
+	void FillMatrix(int size, const std::vector<std::tuple<int, int, double>>& edges);
+	void FillModMatrix(int size, const std::vector<int>& sources, const std::vector<int>& destinations, const std::vector<double>& weights);
+	void FillModMatrix(int size, const std::vector<std::tuple<int, int, double>>& edges);
+
+private:
+	int m_size;
+	double m_total_weight;
+	int m_number_of_communities;
+	bool m_is_directed;
+	// Modularity Resolution Parameter
+	// as per Newman 2016 (https://journals.aps.org/pre/abstract/10.1103/PhysRevE.94.052315)
+	double m_modularity_resolution;
+	std::vector<std::vector<double> > m_matrix;
+	std::vector<std::vector<double> > m_modularity_matrix;
+	std::vector<int> m_communities;
+};
+
+#endif //GRAPH_H
diff --git a/src/README.md b/src/README.md
new file mode 100755
index 0000000..dd322fc
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,4 @@
+# Combo
+
+For now this is vendorized version of `https://github.com/Casyfill/Combo`,
+which is itself a fork of a fork of original C++ Combo tool.
\ No newline at end of file


Test patch:
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
index 00befce..f13e44b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -27,7 +27,14 @@ def test_graph():
 
 
 @pytest.fixture(scope="function")
-def karate():
+def karate() -> nx.Graph:
     import networkx as nx
 
     return nx.karate_club_graph()
+
+
+@pytest.fixture(scope="session")
+def relaxed_caveman() -> nx.Graph:
+    import networkx as nx
+
+    return nx.relaxed_caveman_graph(100, 10, p=0.1, seed=42)
diff --git a/tests/test_modularity.py b/tests/test_modularity.py
index bca4689..5621671 100644
--- a/tests/test_modularity.py
+++ b/tests/test_modularity.py
@@ -1,16 +1,68 @@
 import pytest
 
 
-def test_modularity_test_graph(test_graph):
-    from pyCombo import combo  # , modularity
+def test_cCombo():
+    from pycombo.pyCombo import comboCPP
 
-    _, modularity_ = combo(test_graph, weight_prop="weight")
-    assert modularity_ == pytest.approx(0.16, 0.0001)
-    # assert modularity_ == pytest.approx(modularity(test_graph, partition, key='weight') )
+    assert comboCPP.__doc__ == "Python binding for Combo community detection algorithm"
 
 
-def test_modularity_karate(karate):
-    from pyCombo import combo
+def _comm_groups(partition: dict) -> list:
+    """convert partition node->comm
+    into list of sets of nodes, one set per community
+    """
+    result = dict()
+    for k, v in partition.items():
+        result[v] = result.get(v, list()) + [k]
+    return [set(el) for el in result.values()]
 
-    _, modularity_ = combo(karate)
-    assert modularity_ == 0.437212
+
+def test_modularity_karate(karate, benchmark):
+    # import networkx as nx
+    # karate = nx.karate_club_graph()
+    import networkx.algorithms.community as nx_comm
+    from pycombo.pyCombo import get_combo_partition
+
+    partition, modularity = benchmark(get_combo_partition, karate, random_seed=42)
+
+    assert isinstance(partition, dict)
+    assert len(partition) == len(karate)
+    comms = _comm_groups(partition)
+    networkx_modularity = nx_comm.modularity(karate, comms)
+
+    assert modularity == pytest.approx(networkx_modularity, 0.0001), (
+        modularity,
+        networkx_modularity,
+    )
+
+
+def test_relaxed_caveman(relaxed_caveman, benchmark):
+    import networkx.algorithms.community as nx_comm
+    from pycombo.pyCombo import get_combo_partition
+
+    partition, modularity = benchmark(
+        get_combo_partition, relaxed_caveman, weight_prop="weight", random_seed=42
+    )
+
+    assert isinstance(partition, dict)
+    assert len(partition) == len(relaxed_caveman)
+
+    comms = _comm_groups(partition)
+    networkx_modularity = nx_comm.modularity(relaxed_caveman, comms)
+
+    assert modularity == pytest.approx(networkx_modularity, 0.0001), (
+        modularity,
+        networkx_modularity,
+    )
+
+
+# def test_modularity_test_graph(test_graph, benchmark):
+#     from pyCombo.pyCombo import get_combo_partition
+
+#     partition, modularity = benchmark(
+#         get_combo_partition, test_graph, weight_prop="weight", random_seed=42
+#     )
+
+#     assert isinstance(partition, list)
+#     assert len(partition) == len(test_graph)
+#     assert modularity == pytest.approx(0.16, 0.0001)
diff --git a/tests/test_pyCombo.py b/tests/test_pyCombo.py
index fb4c079..05c25a8 100644
--- a/tests/test_pyCombo.py
+++ b/tests/test_pyCombo.py
@@ -17,22 +17,22 @@ def _partitionGroup(p):
     return sorted(v.values())
 
 
-@pytest.mark.parametrize("n", [3, 4, 5, 10])
-def test_fileojb_write_graph(n):
-    import tempfile
-    from pyCombo.pyCombo import _fileojb_write_graph
-    import networkx as nx
+# @pytest.mark.parametrize("n", [3, 4, 5, 10])
+# def test_fileojb_write_graph(n):
+#     import tempfile
+#     from pycombo.pyCombo import _fileojb_write_graph
+#     import networkx as nx
 
-    test_graph = nx.complete_graph(n)
-    n_edges = (n * (n - 1)) / 2
-    n_lines = 2 + n + n_edges
+#     test_graph = nx.complete_graph(n)
+#     n_edges = (n * (n - 1)) / 2
+#     n_lines = 2 + n + n_edges
 
-    with tempfile.NamedTemporaryFile("w") as tmp:
-        _ = _fileojb_write_graph(tmp, test_graph, weight=None)
+#     with tempfile.NamedTemporaryFile("w") as tmp:
+#         _ = _fileojb_write_graph(tmp, test_graph, weight=None)
 
-        with open(tmp.name, "r") as rtmp:
-            lines_list = list(rtmp.readlines())
-            assert len(lines_list) == n_lines, "".join(lines_list)
+#         with open(tmp.name, "r") as rtmp:
+#             lines_list = list(rtmp.readlines())
+#             assert len(lines_list) == n_lines, "".join(lines_list)
 
 
 # def test_basic_combo(test_graph):
@@ -44,16 +44,16 @@ def test_fileojb_write_graph(n):
 
 
 def test_errors():
-    from pyCombo import combo
+    from pycombo.pyCombo import get_combo_partition
     import networkx as nx
 
     graph = nx.Graph()  # empty
     with pytest.raises(ValueError):
-        combo(graph)
+        get_combo_partition(graph)
 
-    # number
+    # wrong value, number
     with pytest.raises(ValueError):
-        combo(42, weight_prop="weight")
+        get_combo_partition(42, weight_prop="weight")
 
 
 # @pytest.mark.parametrize('full_graph_size', [2,3, 10, 100])