From 3654ca367e9d16b68d0a8f52fb15c88c3aea53e8 Mon Sep 17 00:00:00 2001
From: apister <alexis.pister@inria.fr>
Date: Mon, 28 Jun 2021 14:58:37 +0200
Subject: [PATCH] dynCom Instant Optimal

---
 aleclust/clustering/clustering.py        |   2 +-
 aleclust/globals.py                      |   2 +-
 api.py                                   |  58 +++---
 dynCom/dynCom/InstantOptimal.py          |  80 +++++++++
 dynCom/dynCom/dynCommunities.py          |   6 +-
 dynCom/dynCom/temporal_trade_off.py      |   2 +-
 dynCom/notebooks/temporal_trad_off.ipynb | 220 +++++++++++------------
 dynCom/notebooks/tiles.ipynb             | 160 +++++++++++++++++
 dynCom/setup.py                          |  11 +-
 dynCom/tests/conftest.py                 |  10 +-
 dynCom/tests/test_InstantOptimal.py      |  10 ++
 dynCom/tests/test_temporal_trade_off.py  |  12 +-
 dynCom/tests/test_utils.py               |   8 +-
 setup.py                                 |   4 +-
 14 files changed, 418 insertions(+), 167 deletions(-)
 create mode 100644 dynCom/dynCom/InstantOptimal.py
 create mode 100644 dynCom/notebooks/tiles.ipynb
 create mode 100644 dynCom/tests/test_InstantOptimal.py

diff --git a/aleclust/clustering/clustering.py b/aleclust/clustering/clustering.py
index b0e92fc..63b24e4 100644
--- a/aleclust/clustering/clustering.py
+++ b/aleclust/clustering/clustering.py
@@ -17,7 +17,7 @@ import aleclust.utility as utility
 import aleclust.clustering.communities as nodeClustering
 
 # TODO : Move to metric
-from clustering.attributeCD import attribute_clustering_preprocessing
+from aleclust.clustering.attributeCD import attribute_clustering_preprocessing
 
 METRICS = {
     "coverage": community.quality.coverage,
diff --git a/aleclust/globals.py b/aleclust/globals.py
index c479f3b..b5ca9fc 100644
--- a/aleclust/globals.py
+++ b/aleclust/globals.py
@@ -364,7 +364,7 @@ METRICS_PARAMETERS = ["bimodularity_entity_type"]
 import dynCom.louvain as dynamic_louvain
 
 DYNAMIC_CDA = {
-    "louvain": {
+    "louvain_tdf": {
         "description": "Launch the Louvain algorithm at each snapshot, with an initial condition corresponding to the result of the previous timeslot. Use an heuristic to not find a local optimum too fast",
         "parameters": {
             "resolution": {
diff --git a/api.py b/api.py
index aa42f9e..e12e696 100644
--- a/api.py
+++ b/api.py
@@ -1,12 +1,4 @@
 # -*- coding: utf-8 -*-
-from flask import Flask, jsonify, request, send_file
-from flask_socketio import SocketIO, emit, send, Namespace
-from flask_cors import CORS
-from flask import current_app
-
-import requests
-from urllib.parse import urlparse
-from io import BytesIO
 
 import os
 import rapidjson
@@ -14,9 +6,18 @@ import copy
 import sys
 import time
 
+from flask import Flask, jsonify, request, send_file
+from flask_socketio import SocketIO, emit, send, Namespace
+from flask_cors import CORS
+from flask import current_app
+import requests
+from urllib.parse import urlparse
+from io import BytesIO
 import networkx as nx
 from networkx.readwrite import json_graph
 
+from dynCom.InstantOptimal import InstantOptimal
+
 import aleclust.processing.preprocessing as preprocessing
 import aleclust.utility as utility
 import aleclust.dynamiClustering.dynamiClustering as dynamiClustering
@@ -54,14 +55,16 @@ dataPaths = {
 
 @app.route("/", methods=["GET"])
 def base():
-    message = {"info": INFO,
-               "datasets": list(dataPaths.keys()),
-               "algorithms": {"unipartite": utility.dict_to_dict_of_strings(
-                   utility.delete_keys_from_dict(copy.deepcopy(UNIPARTITE_CDA), ["function"])),
-                   "bipartite": utility.dict_to_dict_of_strings(
-                       utility.delete_keys_from_dict(copy.deepcopy(BIPARTITE_CDA), ["function"]))},
-               "parameters": utility.dict_to_dict_of_strings(PARAMETERS),
-               "metrics": list(clustering.METRICS.keys())}
+    message = {
+        "info": INFO,
+        "datasets": list(dataPaths.keys()),
+        "algorithms": {"unipartite": utility.dict_to_dict_of_strings(
+           utility.delete_keys_from_dict(copy.deepcopy(UNIPARTITE_CDA), ["function"])),
+           "bipartite": utility.dict_to_dict_of_strings(
+               utility.delete_keys_from_dict(copy.deepcopy(BIPARTITE_CDA), ["function"]))},
+        "parameters": utility.dict_to_dict_of_strings(PARAMETERS),
+        "metrics": list(clustering.METRICS.keys())
+    }
     return jsonify(message)
 
 
@@ -111,17 +114,21 @@ def static_clustering_request(flask_request, clustering_algo, dataName=False, dy
 def dynamic_clustering_request(flask_request, clustering_algo):
     from dynCom import json_read, louvain
     json = flask_request.json
-    json_parser = PaohJsonParser(json, dynamic=True)
-    parameters: Parameters = json_parser.parameters
-    parameters.algorithm_list = DYNAMIC_CDA
 
-    # get URL arguments
-    args = flask_request.args
-    parameters.parse_parameters(clustering_algo, args)
+    if clustering_algo in DYNAMIC_CDA:
+        json_parser = PaohJsonParser(json, dynamic=True)
+        parameters: Parameters = json_parser.parameters
+        parameters.algorithm_list = DYNAMIC_CDA
 
-    dyn_graph = json_parser.own_json_to_graph(dynamic=True)
+        # get URL arguments
+        args = flask_request.args
+        parameters.parse_parameters(clustering_algo, args)
+        dyn_graph = json_parser.own_json_to_graph(dynamic=True)
 
-    model = DYNAMIC_CDA[clustering_algo]["function"](**parameters.clustering_parameters)
+        model = DYNAMIC_CDA[clustering_algo]["function"](**parameters.clustering_parameters)
+    else:
+        dyn_graph = PaohJsonParser.json_to_dyn_graph(json, True)
+        model = InstantOptimal(clustering_algo)
 
     dyn_coms = model.apply(dyn_graph)
     dyn_coms_json = dyn_coms.to_json()
@@ -278,4 +285,5 @@ if __name__ == "__main__":
         app.run(debug=debug, port=sys.argv[1])
     else:
         #        socketio.run(app, port=10080)
-        app.run(debug=debug, port=10080)
+        # app.run(debug=debug, port=10080)
+        app.run(debug=debug, port=10085)
diff --git a/dynCom/dynCom/InstantOptimal.py b/dynCom/dynCom/InstantOptimal.py
new file mode 100644
index 0000000..46a3aa4
--- /dev/null
+++ b/dynCom/dynCom/InstantOptimal.py
@@ -0,0 +1,80 @@
+import networkx as nx
+import dynetx as dn
+from cdlib import algorithms
+from cdlib import TemporalClustering
+from networkx.generators.community import LFR_benchmark_graph
+
+from dynCom import utils, dynCommunities
+
+
+def cdlib_handler(algorithm_name):
+    cdlib_function = algorithms.__dict__[algorithm_name]
+    return cdlib_function
+
+
+def t_com_id_to_com_id(t_com_id):
+    return t_com_id.split("_")[-1]
+
+
+def jaccard(x, y):
+    return len(set(x) & set(y)) / len(set(x) | set(y))
+
+
+class InstantOptimal:
+    def __init__(self, alg_name):
+        self.alg_name = alg_name
+
+        self.dyn_communities = None
+        self.temporal_clustering = None
+        self.snapshot_ids = None
+        self.matches = None
+        self.tcomids_to_comid = None
+        self.com_id = None
+
+    def apply(self, dyn_graph, **kwargs):
+        self.dyn_communities = dynCommunities.DynCommunities(dyn_graph)
+        self.temporal_clustering = TemporalClustering()
+        self.snapshot_ids = dyn_graph.temporal_snapshots_ids()
+
+        for i, snapshot_id in enumerate(self.snapshot_ids):
+            snapshot = utils.dyn_to_nxgraph(dyn_graph.time_slice(snapshot_id))
+            partition = cdlib_handler(self.alg_name)(snapshot, **kwargs)
+            self.temporal_clustering.add_clustering(partition, snapshot_id)
+
+        self.matches = self.temporal_clustering.community_matching(jaccard, two_sided=True)
+        self.matching_step()
+
+        self.save_to_dyn_communities()
+        return self.dyn_communities
+
+    def matching_step(self):
+        self.tcomids_to_comid = {}
+        self.com_id = 0
+        for match in self.matches:
+            similarity = match[2]
+            com_id_t0 = match[0]
+            com_id_t1 = match[1]
+
+            if com_id_t0 in self.tcomids_to_comid:
+                com_id = self.tcomids_to_comid[com_id_t0]
+            else:
+                com_id = self.com_id
+                self.tcomids_to_comid[com_id_t0] = com_id
+                self.com_id += 1
+
+            if similarity > 0.5:
+                self.tcomids_to_comid[com_id_t1] = com_id
+
+    def save_to_dyn_communities(self):
+        for snapshot_id in self.snapshot_ids:
+            for j, com in enumerate(self.temporal_clustering.get_clustering_at(snapshot_id).communities):
+                t_com_id = f"{snapshot_id}_{j}"
+                if t_com_id in self.tcomids_to_comid:
+                    com_id = self.tcomids_to_comid[f"{snapshot_id}_{j}"]
+                else:
+                    com_id = self.com_id
+                    self.com_id += 1
+
+                self.dyn_communities.communities[snapshot_id][com_id] = com
+
+        # print(self.dyn_communities.communities)
diff --git a/dynCom/dynCom/dynCommunities.py b/dynCom/dynCom/dynCommunities.py
index e85a6f2..05d86ae 100644
--- a/dynCom/dynCom/dynCommunities.py
+++ b/dynCom/dynCom/dynCommunities.py
@@ -12,10 +12,11 @@ def dict_to_list_communities(dict_communities):
 class DynCommunities:
     def __init__(self, dyn_graph=None):
         self.dyn_graph = dyn_graph
-        self.communities: dict[str, dict[int, list]] = {}
+        # self.communities: dict[str, dict[int, list]] = {}  # Time to com to node
+        self.communities: dict[str, dict[int, list]] = defaultdict(dict) # Time to com to node
         self.json = None
 
-        self.no_community_label = "None"
+        self.no_community_label = None
 
     def to_json(self):
         self.json = {}
@@ -33,4 +34,5 @@ class DynCommunities:
 
         self.json["nodes"] = dict_to_list_communities(nodes_communities)
         self.json["graph"] = {}
+
         return self.json
diff --git a/dynCom/dynCom/temporal_trade_off.py b/dynCom/dynCom/temporal_trade_off.py
index 60bd2fd..ea0044b 100644
--- a/dynCom/dynCom/temporal_trade_off.py
+++ b/dynCom/dynCom/temporal_trade_off.py
@@ -25,7 +25,7 @@ class GlobalOptUpdate:
         self.snapshot_ids = dyn_graph.temporal_snapshots_ids()
 
         for i, snapshot_id in enumerate(self.snapshot_ids):
-            print(i, snapshot_id)
+            # print(i, snapshot_id)
             snapshot = utils.dyn_to_nxgraph(dyn_graph.time_slice(snapshot_id))
 
             if i == 0:
diff --git a/dynCom/notebooks/temporal_trad_off.ipynb b/dynCom/notebooks/temporal_trad_off.ipynb
index 14b80a0..229a526 100644
--- a/dynCom/notebooks/temporal_trad_off.ipynb
+++ b/dynCom/notebooks/temporal_trad_off.ipynb
@@ -2,61 +2,8 @@
  "cells": [
   {
    "cell_type": "code",
-   "execution_count": 1,
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "1801\n",
-      "[(0, 1)]\n",
-      "[(0, 2)]\n",
-      "[(5, 6)]\n",
-      "[(5, 7)]\n",
-      "1802\n",
-      "[(0, 1)]\n",
-      "[(0, 2)]\n",
-      "[(0, 3)]\n",
-      "[(0, 4)]\n",
-      "[(4, 5)]\n",
-      "[(5, 6)]\n",
-      "[(5, 7)]\n",
-      "1803\n",
-      "[(0, 1)]\n",
-      "[(0, 3)]\n",
-      "[(0, 2), (0, 3), (0, 4), (0, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]\n",
-      "[(0, 5), (0, 6), (5, 6)]\n",
-      "[(0, 5), (0, 7), (0, 8), (0, 9), (5, 7), (5, 8), (5, 9), (7, 8), (7, 9), (8, 9)]\n",
-      "[(9, 10)]\n",
-      "[(10, 11)]\n",
-      "[(10, 11), (10, 12), (11, 12)]\n",
-      "1804\n",
-      "[(0, 10)]\n",
-      "[(0, 9), (0, 11), (9, 11)]\n",
-      "[(0, 9), (0, 10), (0, 12), (9, 10), (9, 12), (10, 12)]\n",
-      "[(2, 3)]\n",
-      "[(2, 3), (2, 4), (2, 5), (2, 7), (2, 8), (2, 9), (3, 4), (3, 5), (3, 7), (3, 8), (3, 9), (4, 5), (4, 7), (4, 8), (4, 9), (5, 7), (5, 8), (5, 9), (7, 8), (7, 9), (8, 9)]\n",
-      "[(5, 6)]\n",
-      "[(5, 7)]\n",
-      "[(9, 10)]\n",
-      "0 1801\n"
-     ]
-    },
-    {
-     "ename": "TypeError",
-     "evalue": "'module' object is not callable",
-     "output_type": "error",
-     "traceback": [
-      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
-      "\u001B[0;31mTypeError\u001B[0m                                 Traceback (most recent call last)",
-      "\u001B[0;32m<ipython-input-1-b2ac26fc56e7>\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m      4\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mjson_read\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m      5\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mprojection\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 6\u001B[0;31m \u001B[0;32mimport\u001B[0m \u001B[0mlouvain\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m      7\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mutils\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m      8\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mtemporal_trade_off\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;32m~/Projects/dynCom/louvain.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m     35\u001B[0m \u001B[0mlouv\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mLouvain\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     36\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 37\u001B[0;31m \u001B[0mp\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mlouv\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mapply\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mgraph_dyn_uni\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     38\u001B[0m \u001B[0;31m#\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     39\u001B[0m \u001B[0;31m# #%%\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;32m~/Projects/dynCom/temporal_trade_off.py\u001B[0m in \u001B[0;36mapply\u001B[0;34m(self, dyn_graph)\u001B[0m\n\u001B[1;32m     32\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     33\u001B[0m             \u001B[0;32mif\u001B[0m \u001B[0mi\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 34\u001B[0;31m                 \u001B[0mfirst_partition\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msnapshot_clustering\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0msnapshot\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mNone\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     35\u001B[0m                 \u001B[0;32mcontinue\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     36\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;32m~/Projects/dynCom/louvain.py\u001B[0m in \u001B[0;36msnapshot_clustering\u001B[0;34m(self, snapshot, init_partition)\u001B[0m\n\u001B[1;32m     20\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     21\u001B[0m     \u001B[0;32mdef\u001B[0m \u001B[0msnapshot_clustering\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0msnapshot\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0minit_partition\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---> 22\u001B[0;31m         \u001B[0mpartition\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcommunity_louvain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0msnapshot\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mpartition\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0minit_partition\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     23\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     24\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;31mTypeError\u001B[0m: 'module' object is not callable"
-     ]
-    }
-   ],
+   "execution_count": 3,
+   "outputs": [],
    "source": [
     "import rapidjson\n",
     "import networkx as nx\n",
@@ -76,7 +23,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 4,
    "metadata": {
     "collapsed": true
    },
@@ -92,48 +39,65 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 5,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "<dynetx.classes.dyngraph.DynGraph at 0x7fd2fbe9cb80>"
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "graph_dyn_uni = projection.projection(graph_dyn)\n",
+    "graph_dyn_uni\n"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]"
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "graph_dyn_uni.time_slice(1803).nodes()"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "1801\n",
-      "[(0, 1)]\n",
-      "[(0, 2)]\n",
-      "[(5, 6)]\n",
-      "[(5, 7)]\n",
-      "1802\n",
-      "[(0, 1)]\n",
-      "[(0, 2)]\n",
-      "[(0, 3)]\n",
-      "[(0, 4)]\n",
-      "[(4, 5)]\n",
-      "[(5, 6)]\n",
-      "[(5, 7)]\n",
-      "1803\n",
-      "[(0, 1)]\n",
-      "[(0, 3)]\n",
-      "[(0, 2), (0, 3), (0, 4), (0, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]\n",
-      "[(0, 5), (0, 6), (5, 6)]\n",
-      "[(0, 5), (0, 7), (0, 8), (0, 9), (5, 7), (5, 8), (5, 9), (7, 8), (7, 9), (8, 9)]\n",
-      "[(9, 10)]\n",
-      "[(10, 11)]\n",
-      "[(10, 11), (10, 12), (11, 12)]\n",
-      "1804\n",
-      "[(0, 10)]\n",
-      "[(0, 9), (0, 11), (9, 11)]\n",
-      "[(0, 9), (0, 10), (0, 12), (9, 10), (9, 12), (10, 12)]\n",
-      "[(2, 3)]\n",
-      "[(2, 3), (2, 4), (2, 5), (2, 7), (2, 8), (2, 9), (3, 4), (3, 5), (3, 7), (3, 8), (3, 9), (4, 5), (4, 7), (4, 8), (4, 9), (5, 7), (5, 8), (5, 9), (7, 8), (7, 9), (8, 9)]\n",
-      "[(5, 6)]\n",
-      "[(5, 7)]\n",
-      "[(9, 10)]\n"
+      "[0, 1, 2, 5, 6, 7, 3, 4, 8, 9, 10, 11, 12]\n"
      ]
     }
    ],
    "source": [
-    "graph_dyn_uni = projection.projection(graph_dyn)"
+    "print(graph_dyn_uni.nodes())"
    ],
    "metadata": {
     "collapsed": false,
@@ -144,7 +108,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 85,
+   "execution_count": 40,
    "outputs": [],
    "source": [
     "louv = louvain.Louvain()"
@@ -158,19 +122,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 86,
+   "execution_count": 41,
    "outputs": [
     {
-     "ename": "TypeError",
-     "evalue": "add_nodes_from() missing 1 required positional argument: 'nodes_for_adding'",
-     "output_type": "error",
-     "traceback": [
-      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
-      "\u001B[0;31mTypeError\u001B[0m                                 Traceback (most recent call last)",
-      "\u001B[0;32m<ipython-input-86-cbe6fc7cfdad>\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mp\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mlouv\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mapply\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mgraph_dyn_uni\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      2\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;32m~/Projects/dynCom/temporal_trade_off.py\u001B[0m in \u001B[0;36mapply\u001B[0;34m(self, dyn_graph)\u001B[0m\n\u001B[1;32m     16\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     17\u001B[0m         \u001B[0;32mfor\u001B[0m \u001B[0mi\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0msnapshot_id\u001B[0m \u001B[0;32min\u001B[0m \u001B[0menumerate\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0msnapshot_ids\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---> 18\u001B[0;31m             \u001B[0msnapshot\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mnx\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mGraph\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[0m\u001B[1;32m     19\u001B[0m             \u001B[0msnapshot\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_nodes_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdyn_graph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtime_slice\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0msnapshot_id\u001B[0m\u001B[0;34m)\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[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m     20\u001B[0m             \u001B[0;31m# snapshot.add_edges_from(dyn_graph.time_slice(snapshot_id).edges())\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;32m~/Projects/dynCom/utils.py\u001B[0m in \u001B[0;36mdyn_to_nxgraph\u001B[0;34m(dyn_graph)\u001B[0m\n\u001B[1;32m      4\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mdyn_to_nxgraph\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdyn_graph\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      5\u001B[0m     \u001B[0mgraph\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mnx\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mGraph\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----> 6\u001B[0;31m     \u001B[0mnodes\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mdyn_graph\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[0m\u001B[1;32m      7\u001B[0m     \u001B[0mgraph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_nodes_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mnodes_for_adding\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[0m\n\u001B[1;32m      8\u001B[0m     \u001B[0mgraph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_edges_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdyn_graph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minteractions\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;31mTypeError\u001B[0m: add_nodes_from() missing 1 required positional argument: 'nodes_for_adding'"
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "0 1801\n",
+      "1 1802\n",
+      "2 1803\n",
+      "3 1804\n"
      ]
     }
    ],
@@ -186,13 +147,36 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 80,
+   "execution_count": 42,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "{1801: {0: [0, 1, 2], 1: [5, 6, 7]},\n 1802: {0: [0, 1, 2, 3], 1: [4, 5, 6, 7]},\n 1803: {0: [0, 1, 2, 3, 4], 1: [5, 6, 7, 8, 9], 2: [10, 11, 12]},\n 1804: {0: [0, 9, 10, 11, 12], 1: [2, 3, 4, 5, 7, 8, 6]}}"
+     },
+     "execution_count": 42,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "p.communities"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
    "outputs": [
     {
      "data": {
       "text/plain": "[1801, 1802, 1803, 1804]"
      },
-     "execution_count": 80,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -209,13 +193,13 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 67,
+   "execution_count": 7,
    "outputs": [
     {
      "data": {
       "text/plain": "dynetx.classes.dyngraph.DynGraph"
      },
-     "execution_count": 67,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -233,13 +217,13 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 68,
+   "execution_count": 8,
    "outputs": [
     {
      "data": {
       "text/plain": "[0, 9, 10, 11, 12, 2, 3, 4, 5, 7, 8, 6]"
      },
-     "execution_count": 68,
+     "execution_count": 8,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -256,19 +240,15 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 60,
+   "execution_count": 9,
    "outputs": [
     {
-     "ename": "TypeError",
-     "evalue": "add_nodes_from() missing 1 required positional argument: 'nodes_for_adding'",
-     "output_type": "error",
-     "traceback": [
-      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
-      "\u001B[0;31mTypeError\u001B[0m                                 Traceback (most recent call last)",
-      "\u001B[0;32m<ipython-input-60-c57278ae38ca>\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mutils\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdyn_to_nxgraph\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0msnap\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      2\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n",
-      "\u001B[0;32m~/Projects/dynCom/utils.py\u001B[0m in \u001B[0;36mdyn_to_nxgraph\u001B[0;34m(dyn_graph)\u001B[0m\n\u001B[1;32m      4\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mdyn_to_nxgraph\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdyn_graph\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      5\u001B[0m     \u001B[0mgraph\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mnx\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mGraph\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----> 6\u001B[0;31m     \u001B[0mnodes\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mdyn_graph\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[0m\u001B[1;32m      7\u001B[0m     \u001B[0mgraph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_nodes_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mnodes_for_adding\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[0m\n\u001B[1;32m      8\u001B[0m     \u001B[0mgraph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_edges_from\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdyn_graph\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minteractions\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;31mTypeError\u001B[0m: add_nodes_from() missing 1 required positional argument: 'nodes_for_adding'"
-     ]
+     "data": {
+      "text/plain": "<networkx.classes.graph.Graph at 0x7ffa4527ecd0>"
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
@@ -283,7 +263,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 65,
+   "execution_count": 10,
    "outputs": [],
    "source": [
     "graph = nx.Graph()"
@@ -297,7 +277,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 87,
+   "execution_count": 11,
    "outputs": [],
    "source": [
     "graph.add_nodes_from(snap.nodes())"
diff --git a/dynCom/notebooks/tiles.ipynb b/dynCom/notebooks/tiles.ipynb
new file mode 100644
index 0000000..e7c31b8
--- /dev/null
+++ b/dynCom/notebooks/tiles.ipynb
@@ -0,0 +1,160 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import rapidjson\n",
+    "import networkx as nx\n",
+    "\n",
+    "import json_read\n",
+    "import projection\n",
+    "import louvain\n",
+    "import utils\n",
+    "import temporal_trade_off\n",
+    "from cdlib import algorithms"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "outputs": [],
+   "source": [
+    "fp = \"../data/Marie_Boucher_small.json\"\n",
+    "with open(fp, \"r\", encoding='utf-8') as read_file:\n",
+    "    json_data = rapidjson.loads(read_file.read())\n",
+    "\n",
+    "graph_dyn = json_read.paoh_json_reader(json_data)"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "outputs": [
+    {
+     "data": {
+      "text/plain": "<dynetx.classes.dyngraph.DynGraph at 0x7f460f684400>"
+     },
+     "execution_count": 21,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "graph_dyn_uni = projection.projection(graph_dyn)\n",
+    "graph_dyn_uni"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "[('_e_0', 0, '+', 1801), ('_e_0', 1, '+', 1801), ('_e_3', 0, '+', 1801), ('_e_3', 2, '+', 1801), ('_e_17', 5, '+', 1801), ('_e_17', 6, '+', 1801), ('_e_20', 5, '+', 1801), ('_e_20', 7, '+', 1801), ('_e_1', 0, '+', 1802), ('_e_1', 1, '+', 1802), ('_e_4', 0, '+', 1802), ('_e_4', 2, '+', 1802), ('_e_6', 0, '+', 1802), ('_e_6', 3, '+', 1802), ('_e_7', 0, '+', 1802), ('_e_7', 4, '+', 1802), ('_e_16', 4, '+', 1802), ('_e_16', 5, '+', 1802), ('_e_19', 5, '+', 1802), ('_e_19', 6, '+', 1802), ('_e_22', 5, '+', 1802), ('_e_22', 7, '+', 1802), ('_e_2', 0, '+', 1803), ('_e_2', 1, '+', 1803), ('_e_5', 0, '+', 1803), ('_e_5', 3, '+', 1803), ('_e_8', 0, '+', 1803), ('_e_8', 2, '+', 1803), ('_e_8', 3, '+', 1803), ('_e_8', 4, '+', 1803), ('_e_8', 5, '+', 1803), ('_e_9', 0, '+', 1803), ('_e_9', 5, '+', 1803), ('_e_9', 6, '+', 1803), ('_e_10', 0, '+', 1803), ('_e_10', 5, '+', 1803), ('_e_10', 7, '+', 1803), ('_e_10', 8, '+', 1803), ('_e_10', 9, '+', 1803), ('_e_24', 9, '+', 1803), ('_e_24', 10, '+', 1803), ('_e_25', 10, '+', 1803), ('_e_25', 11, '+', 1803), ('_e_26', 10, '+', 1803), ('_e_26', 11, '+', 1803), ('_e_26', 12, '+', 1803), ('_e_11', 0, '+', 1804), ('_e_11', 10, '+', 1804), ('_e_12', 0, '+', 1804), ('_e_12', 11, '+', 1804), ('_e_12', 9, '+', 1804), ('_e_13', 0, '+', 1804), ('_e_13', 9, '+', 1804), ('_e_13', 10, '+', 1804), ('_e_13', 12, '+', 1804), ('_e_14', 2, '+', 1804), ('_e_14', 3, '+', 1804), ('_e_15', 2, '+', 1804), ('_e_15', 3, '+', 1804), ('_e_15', 4, '+', 1804), ('_e_15', 5, '+', 1804), ('_e_15', 7, '+', 1804), ('_e_15', 8, '+', 1804), ('_e_15', 9, '+', 1804), ('_e_18', 5, '+', 1804), ('_e_18', 6, '+', 1804), ('_e_21', 5, '+', 1804), ('_e_21', 7, '+', 1804), ('_e_23', 9, '+', 1804), ('_e_23', 10, '+', 1804)]\n",
+      "[1801, 1802, 1803, 1804]\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(list(graph_dyn.stream_interactions()))\n",
+    "print(graph_dyn.temporal_snapshots_ids())"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 51,
+   "outputs": [],
+   "source": [
+    "coms = algorithms.tiles(graph_dyn_uni, 1)"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 52,
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "[1, 2, 3, 4, 5]\n",
+      "[]\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(coms.get_observation_ids())\n",
+    "print(coms.get_clustering_at(1).communities)\n"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "outputs": [],
+   "source": [],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "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.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/dynCom/setup.py b/dynCom/setup.py
index 20a5af5..335d482 100644
--- a/dynCom/setup.py
+++ b/dynCom/setup.py
@@ -17,7 +17,7 @@ def read(fname):
 
 setup(
     name="dynCom",
-    version="0.0.1",
+    version="0.0.2",
     author="Alexis Pister",
     author_email="alexis.pister@inria.fr",
     description="Dynamic Graph Clustering Library",
@@ -35,11 +35,12 @@ setup(
     # installed or upgraded on the target machine
     # install_requires=required,
     install_requires=[
-        "networkx>=2.3",
-        "dynetx==0.2.3"
+        "networkx>=2.5",
+        "dynetx>=0.2.3",
+        "cdlib>=0.2.3"
     ],
     package_data={
         # If any package contains *.md, *.txt or *.rst files, include them:
         'doc': ['*.md', '*.rst'],
-        }
-    )
+    }
+)
diff --git a/dynCom/tests/conftest.py b/dynCom/tests/conftest.py
index 47058f5..f063194 100644
--- a/dynCom/tests/conftest.py
+++ b/dynCom/tests/conftest.py
@@ -20,7 +20,7 @@ def graph_dyn_bipartite():
 
 
 @pytest.fixture(scope="session", params=[marie_boucher_small_fp, "generate"])
-def graph_dyn(request):
+def graphs_dyn(request):
     if request.param == "generate":
         G = dn.DynGraph(edge_removal=True)
         G.add_interactions_from([(1, 2), (2, 3), (10, 11)], t=10)
@@ -33,3 +33,11 @@ def graph_dyn(request):
         G = projection.projection(G_bipartite)
 
     return G
+
+@pytest.fixture(scope="session")
+def marie_boucher_graph():
+    json_data = rapidjson.loads(open(marie_boucher_small_fp, "r").read())
+    G_bipartite = json_read.paoh_json_reader(json_data)
+    G = projection.projection(G_bipartite)
+    return G
+
diff --git a/dynCom/tests/test_InstantOptimal.py b/dynCom/tests/test_InstantOptimal.py
new file mode 100644
index 0000000..12ea478
--- /dev/null
+++ b/dynCom/tests/test_InstantOptimal.py
@@ -0,0 +1,10 @@
+from dynCom import InstantOptimal
+
+def test_apply(marie_boucher_graph):
+    model = InstantOptimal.InstantOptimal("louvain")
+    dyn_communities = model.apply(marie_boucher_graph)
+    print(dyn_communities.to_json())
+
+    model = InstantOptimal.InstantOptimal("infomap")
+    dyn_communities = model.apply(marie_boucher_graph)
+    print(dyn_communities.to_json())
diff --git a/dynCom/tests/test_temporal_trade_off.py b/dynCom/tests/test_temporal_trade_off.py
index 28a83a9..f562594 100644
--- a/dynCom/tests/test_temporal_trade_off.py
+++ b/dynCom/tests/test_temporal_trade_off.py
@@ -5,18 +5,20 @@ class TestGlobalOptUpdate:
     def test_snapshot_clustering(self):
         assert True
 
-    def test_apply(self, graph_dyn):
+    def test_apply(self, graphs_dyn):
         louvain_model = louvain.Louvain()
-        dyn_communities = louvain_model.apply(graph_dyn)
+        dyn_communities = louvain_model.apply(graphs_dyn)
 
-        assert len(dyn_communities.communities) == len(graph_dyn.temporal_snapshots_ids())
+        assert len(dyn_communities.communities) == len(graphs_dyn.temporal_snapshots_ids())
 
-        for snapshot_id in graph_dyn.temporal_snapshots_ids():
+        for snapshot_id in graphs_dyn.temporal_snapshots_ids():
             print(dyn_communities.communities[snapshot_id])
-            snapshot = graph_dyn.time_slice(snapshot_id)
+            snapshot = graphs_dyn.time_slice(snapshot_id)
             assert len(snapshot) == len([node for com_id, com in dyn_communities.communities[snapshot_id].items() for
                                          node
                                          in com])
 
+        print(dyn_communities.to_json())
+
     def test_save_to_dyn_communities(self):
         assert True
diff --git a/dynCom/tests/test_utils.py b/dynCom/tests/test_utils.py
index 31444ed..2ba49d8 100644
--- a/dynCom/tests/test_utils.py
+++ b/dynCom/tests/test_utils.py
@@ -3,10 +3,10 @@ import networkx as nx
 from dynCom.utils import dyn_to_nxgraph
 
 
-def test_dyn_to_nxgraph(graph_dyn):
-    graph = dyn_to_nxgraph(graph_dyn)
+def test_dyn_to_nxgraph(graphs_dyn):
+    graph = dyn_to_nxgraph(graphs_dyn)
 
     assert type(graph) == nx.Graph
-    assert set(graph_dyn.nodes()) == set(graph.nodes())
-    assert {(u,v) for u, v, t in graph_dyn.interactions()} == set(graph.edges())
+    assert set(graphs_dyn.nodes()) == set(graph.nodes())
+    assert {(u,v) for u, v, t in graphs_dyn.interactions()} == set(graph.edges())
 
diff --git a/setup.py b/setup.py
index 5970858..885c998 100644
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ setup(
     # install_requires=required,
     install_requires=[
         "python-louvain==0.13",
-        "cdlib==0.1.8",
+        "cdlib>=0.1.8",
         "docutils==0.15.2",
         "pquality==0.0.7",
         "bimlpa==0.1.2",
@@ -66,7 +66,7 @@ setup(
         "pulp==1.6.10",
         "python-rapidjson==0.9.1",
         "pytiled-parser==0.9.3",
-        "networkx==2.3",
+        "networkx>=2.3",
         "requests>=2.22.0",
         "scikit-learn",
         "pandas",
-- 
GitLab