Repository: GiulioRossetti/cdlib (language: python)

Issue:
Network datasets
Added support for remote dataset repository


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml
new file mode 100644
index 00000000..754f2aa8
--- /dev/null
+++ b/.github/workflows/greetings.yml
@@ -0,0 +1,13 @@
+name: Greetings
+
+on: [pull_request, issues]
+
+jobs:
+  greeting:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/first-interaction@v1
+      with:
+        repo-token: ${{ secrets.GITHUB_TOKEN }}
+        issue-message: 'Thanks for submitting your first issue!'
+        pr-message: 'Thanks for submitting your first pull request!'
diff --git a/.pyup.yml b/.pyup.yml
new file mode 100644
index 00000000..c3022c99
--- /dev/null
+++ b/.pyup.yml
@@ -0,0 +1,54 @@
+# configure updates globally
+# default: all
+# allowed: all, insecure, False
+update: insecure
+
+# configure dependency pinning globally
+# default: True
+# allowed: True, False
+pin: False
+
+# set the default branch
+# default: empty, the default branch on GitHub
+branch: master
+
+# update schedule
+# default: empty
+# allowed: "every day", "every week", ..
+schedule: "every day"
+
+# search for requirement files
+# default: True
+# allowed: True, False
+search: True
+
+# Specify requirement files by hand, default is empty
+# default: empty
+# allowed: list
+requirements:
+  - requirements.txt:
+      # update all dependencies and pin them
+      update: insecure
+      pin: False
+  - requirements.docs.txt:
+      # don't update dependencies, use global 'pin' default
+      update: False
+  - requirements_optional.txt:
+      # update insecure only, pin all
+      update: insecure
+      pin: False
+
+# add a label to pull requests, default is not set
+# requires private repo permissions, even on public repos
+# default: empty
+label_prs: update
+
+# assign users to pull requests, default is not set
+# requires private repo permissions, even on public repos
+# default: empty
+assignees:
+ - giuliorossetti
+
+# allow to close stale PRs
+# default: True
+close_prs: True
\ No newline at end of file
diff --git a/README.md b/README.md
index 0d19c8df..51446750 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ Alternatively use pip:
 pip install cdlib
 ```
 
-### Optional Dependencies:
+### Optional Dependencies
 ``CDlib`` relies on a few packages calling C code (namely: ``python-igraph``, ``leidenalg``, ``angel_cd`` and ``infomap``).
 The default installation will not set up such requirements since their configuration under non *nix systems is not trivial and cannot be easily automated.
 
diff --git a/cdlib/algorithms/attribute_clustering.py b/cdlib/algorithms/attribute_clustering.py
index d45bdca3..01e1f736 100644
--- a/cdlib/algorithms/attribute_clustering.py
+++ b/cdlib/algorithms/attribute_clustering.py
@@ -93,7 +93,6 @@ def ilouvain(g_original, labels, id):
          1. Combe D., Largeron C., Géry M., Egyed-Zsigmond E. "I-Louvain: An Attributed Graph Clustering Method". <https://link.springer.com/chapter/10.1007/978-3-319-24465-5_16> In: Fromont E., De Bie T., van Leeuwen M. (eds) Advances in Intelligent Data Analysis XIV. IDA (2015). Lecture Notes in Computer Science, vol 9385. Springer, Cham
           """
 
-
     g = convert_graph_formats(g_original, nx.Graph)
     nx.set_node_attributes(g, labels)
     id = dict()
diff --git a/cdlib/algorithms/biparitte_clustering.py b/cdlib/algorithms/biparitte_clustering.py
deleted file mode 100644
index dde11a43..00000000
--- a/cdlib/algorithms/biparitte_clustering.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from cdlib import BiNodeClustering
-
-import networkx as nx
-from cdlib.utils import convert_graph_formats
-
-
-__all__ = ['bimlpa']
-
-
-def bimlpa(g_original, theta=0.3, lambd=7):
-    """
-    BiMLPA is designed to detect the many-to-many correspondence community in bipartite networks using multi-label propagation algorithm.
-
-    :param g_original: a networkx/igraph object
-    :param theta: Label weights threshold. Default 0.3.
-    :param lambd: The max number of labels. Default 7.
-    :return: BiNodeClustering object
-
-
-    :Example:
-
-    >>> from cdlib import algorithms
-    >>> import networkx as nx
-    >>> G = nx.algorithms.bipartite.generators.random_graph(100, 20, 0.1)
-    >>> coms = algorithms.bimlpa(G)
-
-    :References:
-
-    Taguchi, Hibiki, Tsuyoshi Murata, and Xin Liu. "BiMLPA: Community Detection in Bipartite Networks by Multi-Label Propagation." International Conference on Network Science. Springer, Cham, 2020.
-
-    .. note:: Reference implementation: https://github.com/hbkt/BiMLPA
-    """
-    from BiMLPA import BiMLPA_SqrtDeg, relabeling, output_community
-
-    g = convert_graph_formats(g_original, nx.Graph)
-
-    bimlpa = BiMLPA_SqrtDeg(g, theta, lambd)
-    bimlpa.start()
-    relabeling(g)
-    top_coms, bottom_coms = output_community(g)
-
-    return BiNodeClustering(top_coms, bottom_coms, g_original, "BiMLPA", method_parameters={"theta": theta, "lambd": lambd})
diff --git a/cdlib/algorithms/bipartite_clustering.py b/cdlib/algorithms/bipartite_clustering.py
new file mode 100644
index 00000000..75bf7522
--- /dev/null
+++ b/cdlib/algorithms/bipartite_clustering.py
@@ -0,0 +1,191 @@
+from cdlib import BiNodeClustering
+
+import networkx as nx
+
+try:
+    import infomap as imp
+except ModuleNotFoundError:
+    imp = None
+
+try:
+    from wurlitzer import pipes
+except ModuleNotFoundError:
+    pipes = None
+
+try:
+    import igraph as ig
+except ModuleNotFoundError:
+    ig = None
+
+try:
+    import leidenalg
+except ModuleNotFoundError:
+    leidenalg = None
+
+from cdlib.utils import convert_graph_formats
+from collections import defaultdict
+
+__all__ = ['bimlpa', 'CPM_Bipartite', 'infomap_bipartite']
+
+
+def bimlpa(g_original, theta=0.3, lambd=7):
+    """
+    BiMLPA is designed to detect the many-to-many correspondence community in bipartite networks using multi-label propagation algorithm.
+
+    :param g_original: a networkx/igraph object
+    :param theta: Label weights threshold. Default 0.3.
+    :param lambd: The max number of labels. Default 7.
+    :return: BiNodeClustering object
+
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.algorithms.bipartite.generators.random_graph(100, 20, 0.1)
+    >>> coms = algorithms.bimlpa(G)
+
+    :References:
+
+    Taguchi, Hibiki, Tsuyoshi Murata, and Xin Liu. "BiMLPA: Community Detection in Bipartite Networks by Multi-Label Propagation." International Conference on Network Science. Springer, Cham, 2020.
+
+    .. note:: Reference implementation: https://github.com/hbkt/BiMLPA
+    """
+    from BiMLPA import BiMLPA_SqrtDeg, relabeling, output_community
+
+    g = convert_graph_formats(g_original, nx.Graph)
+
+    if not nx.algorithms.bipartite.is_bipartite(g):
+        raise ValueError("The graph is not bipartite")
+
+    bimlpa = BiMLPA_SqrtDeg(g, theta, lambd)
+    bimlpa.start()
+    relabeling(g)
+    top_coms, bottom_coms = output_community(g)
+
+    return BiNodeClustering(top_coms, bottom_coms, g_original, "BiMLPA",
+                            method_parameters={"theta": theta, "lambd": lambd})
+
+
+def CPM_Bipartite(g_original, resolution_parameter_01,
+                  resolution_parameter_0=0, resolution_parameter_1=0, degree_as_node_size=False, seed=0):
+    """
+    CPM_Bipartite is the extension of CPM to bipartite graphs
+
+    :param g_original: a networkx/igraph object
+    :param resolution_parameter_01: Resolution parameter for in between two classes.
+    :param resolution_parameter_0: Resolution parameter for class 0.
+    :param resolution_parameter_1: Resolution parameter for class 1.
+    :param degree_as_node_size: If ``True`` use degree as node size instead of 1, to mimic modularity
+    :param seed: the random seed to be used in CPM method to keep results/partitions replicable
+    :return: BiNodeClustering object
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.algorithms.bipartite.generators.random_graph(100, 20, 0.5)
+    >>> coms = algorithms.CPM_Bipartite(G, 1)
+
+    :References:
+
+    Barber, M. J. (2007). Modularity and community detection in bipartite networks. Physical Review E, 76(6), 066102. 10.1103/PhysRevE.76.066102
+
+    .. note:: Reference implementation: https://leidenalg.readthedocs.io/en/stable/multiplex.html?highlight=bipartite#bipartite
+    """
+    if ig is None or leidenalg is None:
+        raise ModuleNotFoundError("Optional dependency not satisfied: install igraph and leidenalg to use the "
+                                  "selected feature.")
+
+    g = convert_graph_formats(g_original, ig.Graph)
+
+    try:
+        g.vs['name']
+    except:
+        g.vs['name'] = [v.index for v in g.vs]
+
+    optimiser = leidenalg.Optimiser()
+    leidenalg.Optimiser.set_rng_seed(self=optimiser, value=seed)
+
+    p_01, p_0, p_1 = leidenalg.CPMVertexPartition.Bipartite(g, resolution_parameter_01=resolution_parameter_01,
+                                                            resolution_parameter_0=resolution_parameter_0,
+                                                            resolution_parameter_1=resolution_parameter_1,
+                                                            degree_as_node_size=degree_as_node_size)
+    optimiser.optimise_partition_multiplex([p_01, p_0, p_1], layer_weights=[1, -1, -1])
+
+    coms = defaultdict(list)
+    for n in g.vs:
+        coms[p_01.membership[n.index]].append(n.index)
+
+    return BiNodeClustering(list(coms.values()), [], g_original, "CPM_Bipartite",
+                            method_parameters={"resolution_parameter_0": resolution_parameter_01,
+                                               "resolution_parameter_0": resolution_parameter_0,
+                                               "resolution_parameter_1": resolution_parameter_1,
+                                               "degree_as_node_size": degree_as_node_size, "seed": seed})
+
+
+def infomap_bipartite(g_original):
+    """
+    Infomap is based on ideas of information theory.
+    The algorithm uses the probability flow of random walks on a bipartite network as a proxy for information flows in the real system and it decomposes the network into modules by compressing a description of the probability flow.
+
+    :param g_original: a networkx/igraph object
+    :return: BiNodeClustering object
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.karate_club_graph()
+    >>> coms = algorithms.infomap_bipartite(G)
+
+    :References:
+
+    Rosvall M, Bergstrom CT (2008) `Maps of random walks on complex networks reveal community structure. <https://www.pnas.org/content/105/4/1118/>`_ Proc Natl Acad SciUSA 105(4):1118–1123
+
+    .. note:: Reference implementation: https://pypi.org/project/infomap/
+    """
+
+    if imp is None:
+        raise ModuleNotFoundError("Optional dependency not satisfied: install infomap to use the selected feature.")
+    if pipes is None:
+        raise ModuleNotFoundError("Optional dependency not satisfied: install package wurlitzer to use infomap.")
+
+    g = convert_graph_formats(g_original, nx.Graph)
+
+    g.is_directed()
+
+    g1 = nx.convert_node_labels_to_integers(g, label_attribute="name")
+    name_map = nx.get_node_attributes(g1, 'name')
+    if not nx.algorithms.bipartite.is_bipartite(g1):
+        raise ValueError("The graph is not bipartite")
+
+    X, Y = nx.algorithms.bipartite.sets(g1)
+    X = {x: n for n, x in enumerate(X)}
+    Y = {y: n+max(X.values())+1 for n, y in enumerate(Y)}
+    Z = {**X, **Y}
+
+    g1 = nx.relabel_nodes(g1, Z)
+    inv_Z = {v: k for k, v in Z.items()}
+
+    coms_to_node = defaultdict(list)
+
+    with pipes():
+        im = imp.Infomap()
+        im.bipartite_start_id = min(Y.keys())
+        for e in g1.edges(data=True):
+            if len(e) == 3 and 'weight' in e[2]:
+                im.addLink(e[0], e[1], e[2]['weight'])
+            else:
+                im.addLink(e[0], e[1])
+        im.run()
+
+        for node in im.iterTree():
+            if node.isLeaf():
+                nid = node.physicalId
+                module = node.moduleIndex()
+                nm = name_map[inv_Z[nid]]
+                coms_to_node[module].append(nm)
+
+    coms_infomap = [list(c) for c in coms_to_node.values()]
+    return BiNodeClustering(coms_infomap, [], g_original, "Infomap Bipartite", method_parameters={"": ""})
diff --git a/cdlib/algorithms/crisp_partition.py b/cdlib/algorithms/crisp_partition.py
index e434034a..8647a2d8 100644
--- a/cdlib/algorithms/crisp_partition.py
+++ b/cdlib/algorithms/crisp_partition.py
@@ -1,17 +1,17 @@
 try:
     import infomap as imp
 except ModuleNotFoundError:
-        imp = None
-        
+    imp = None
+
 try:
     from wurlitzer import pipes
 except ModuleNotFoundError:
-        pipes = None
+    pipes = None
 
 try:
     import igraph as ig
 except ModuleNotFoundError:
-        ig = None
+    ig = None
 
 try:
     import leidenalg
@@ -23,19 +23,25 @@
 except ModuleNotFoundError:
     gt = None
 
-
 from cdlib.algorithms.internal import DER
 import community as louvain_modularity
-
+import warnings
 from collections import defaultdict
 from cdlib import NodeClustering, FuzzyNodeClustering
+from cdlib.algorithms.internal.belief_prop import detect_belief_communities
 from cdlib.algorithms.internal.em import EM_nx
 from cdlib.algorithms.internal.scan import SCAN_nx
 from cdlib.algorithms.internal.GDMP2_nx import GDMP2
 from cdlib.algorithms.internal.AGDL import Agdl
 from cdlib.algorithms.internal.FuzzyCom import fuzzy_comm
 from cdlib.algorithms.internal.Markov import markov
+from cdlib.algorithms.internal.ga import ga_community_detection
+from cdlib.algorithms.internal.SiblinarityAntichain import matrix_node_recursive_antichain_partition
 from karateclub import EdMot
+import markov_clustering as mc
+from chinese_whispers import chinese_whispers as cw
+from chinese_whispers import aggregate_clusters
+
 import networkx as nx
 
 from cdlib.utils import convert_graph_formats, __from_nx_to_graph_tool, affiliations2nodesets, nx_node_integer_mapping
@@ -43,7 +49,7 @@
 __all__ = ["louvain", "leiden", "rb_pots", "rber_pots", "cpm", "significance_communities", "surprise_communities",
            "greedy_modularity", "der", "label_propagation", "async_fluid", "infomap", "walktrap", "girvan_newman", "em",
            "scan", "gdmp2", "spinglass", "eigenvector", "agdl", "frc_fgsn", "sbm_dl", "sbm_dl_nested",
-           "markov_clustering", "edmot"]
+           "markov_clustering", "edmot", "chinesewhispers", "siblinarity_antichain", "ga", "belief"]
 
 
 def girvan_newman(g_original, level):
@@ -149,7 +155,7 @@ def scan(g_original, epsilon, mu):
     algorithm = SCAN_nx(g, epsilon, mu)
     coms = algorithm.execute()
     return NodeClustering(coms, g_original, "SCAN", method_parameters={"epsilon": epsilon,
-                                                              "mu": mu})
+                                                                       "mu": mu})
 
 
 def gdmp2(g_original, min_threshold=0.75):
@@ -252,19 +258,17 @@ def eigenvector(g_original):
 
     communities = [g.vs[x]['name'] for x in coms]
 
-    return NodeClustering(communities, g_original, "Eigenvector", method_parameters={"":""})
+    return NodeClustering(communities, g_original, "Eigenvector", method_parameters={"": ""})
 
 
-def agdl(g_original, number_communities, number_neighbors, kc, a):
+def agdl(g_original, number_communities, kc):
     """
     AGDL is a graph-based agglomerative algorithm, for clustering high-dimensional data.
     The algorithm uses  the indegree and outdegree to characterize the affinity between two clusters.
 
     :param g_original: a networkx/igraph object
     :param number_communities: number of communities
-    :param number_neighbors: Number of neighbors to use for KNN
     :param kc: size of the neighbor set for each cluster
-    :param a: range(-infinity;+infinty). From the authors: a=np.arange(-2,2.1,0.5)
     :return: NodeClustering object
 
      :Example:
@@ -272,7 +276,7 @@ def agdl(g_original, number_communities, number_neighbors, kc, a):
     >>> from cdlib import algorithms
     >>> import networkx as nx
     >>> G = nx.karate_club_graph()
-    >>> com = algorithms.agdl(g, number_communities=3, number_neighbors=3, kc=4, a=1)
+    >>> com = algorithms.agdl(g, number_communities=3, kc=4)
 
     :References:
 
@@ -283,15 +287,14 @@ def agdl(g_original, number_communities, number_neighbors, kc, a):
 
     g = convert_graph_formats(g_original, nx.Graph)
 
-    communities = Agdl(g, number_communities, number_neighbors, kc, a)
+    communities = Agdl(g, number_communities, kc)
     nodes = {k: v for k, v in enumerate(g.nodes())}
     coms = []
     for com in communities:
         coms.append([nodes[n] for n in com])
 
     return NodeClustering(coms, g_original, "AGDL", method_parameters={"number_communities": number_communities,
-                                                              "number_neighbors": number_neighbors,
-                                                              "kc": kc, "a": a})
+                                                                       "kc": kc})
 
 
 def louvain(g_original, weight='weight', resolution=1., randomize=False):
@@ -335,8 +338,9 @@ def louvain(g_original, weight='weight', resolution=1., randomize=False):
         coms_to_node[c].append(n)
 
     coms_louvain = [list(c) for c in coms_to_node.values()]
-    return NodeClustering(coms_louvain, g_original, "Louvain", method_parameters={"weight": weight, "resolution": resolution,
-                                                                         "randomize": randomize})
+    return NodeClustering(coms_louvain, g_original, "Louvain",
+                          method_parameters={"weight": weight, "resolution": resolution,
+                                             "randomize": randomize})
 
 
 def leiden(g_original, initial_membership=None, weights=None):
@@ -377,7 +381,7 @@ def leiden(g_original, initial_membership=None, weights=None):
                                     )
     coms = [g.vs[x]['name'] for x in part]
     return NodeClustering(coms, g_original, "Leiden", method_parameters={"initial_membership": initial_membership,
-                                                                "weights": weights})
+                                                                         "weights": weights})
 
 
 def rb_pots(g_original, initial_membership=None, weights=None, resolution_parameter=1):
@@ -426,8 +430,8 @@ def rb_pots(g_original, initial_membership=None, weights=None, resolution_parame
                                     initial_membership=initial_membership, weights=weights)
     coms = [g.vs[x]['name'] for x in part]
     return NodeClustering(coms, g_original, "RB Pots", method_parameters={"initial_membership": initial_membership,
-                                                                 "weights": weights,
-                                                                 "resolution_parameter": resolution_parameter})
+                                                                          "weights": weights,
+                                                                          "resolution_parameter": resolution_parameter})
 
 
 def rber_pots(g_original, initial_membership=None, weights=None, node_sizes=None, resolution_parameter=1):
@@ -474,8 +478,9 @@ def rber_pots(g_original, initial_membership=None, weights=None, node_sizes=None
                                     )
     coms = [g.vs[x]['name'] for x in part]
     return NodeClustering(coms, g_original, "RBER Pots", method_parameters={"initial_membership": initial_membership,
-                                                                   "weights": weights, "node_sizes": node_sizes,
-                                                                   "resolution_parameter": resolution_parameter})
+                                                                            "weights": weights,
+                                                                            "node_sizes": node_sizes,
+                                                                            "resolution_parameter": resolution_parameter})
 
 
 def cpm(g_original, initial_membership=None, weights=None, node_sizes=None, resolution_parameter=1):
@@ -530,8 +535,8 @@ def cpm(g_original, initial_membership=None, weights=None, node_sizes=None, reso
                                     weights=weights, node_sizes=node_sizes, )
     coms = [g.vs[x]['name'] for x in part]
     return NodeClustering(coms, g_original, "CPM", method_parameters={"initial_membership": initial_membership,
-                                                             "weights": weights, "node_sizes": node_sizes,
-                                                             "resolution_parameter": resolution_parameter})
+                                                                      "weights": weights, "node_sizes": node_sizes,
+                                                                      "resolution_parameter": resolution_parameter})
 
 
 def significance_communities(g_original, initial_membership=None, node_sizes=None):
@@ -575,7 +580,7 @@ def significance_communities(g_original, initial_membership=None, node_sizes=Non
                                     node_sizes=node_sizes)
     coms = [g.vs[x]['name'] for x in part]
     return NodeClustering(coms, g_original, "Significance", method_parameters={"initial_membership": initial_membership,
-                                                                      "node_sizes": node_sizes})
+                                                                               "node_sizes": node_sizes})
 
 
 def surprise_communities(g_original, initial_membership=None, weights=None, node_sizes=None):
@@ -622,7 +627,8 @@ def surprise_communities(g_original, initial_membership=None, weights=None, node
                                     weights=weights, node_sizes=node_sizes)
     coms = [g.vs[x]['name'] for x in part]
     return NodeClustering(coms, g_original, "Surprise", method_parameters={"initial_membership": initial_membership,
-                                                                  "weights": weights, "node_sizes": node_sizes})
+                                                                           "weights": weights,
+                                                                           "node_sizes": node_sizes})
 
 
 def greedy_modularity(g_original, weight=None):
@@ -679,9 +685,7 @@ def infomap(g_original):
     if pipes is None:
         raise ModuleNotFoundError("Optional dependency not satisfied: install package wurlitzer to use infomap.")
 
-    g = convert_graph_formats(g_original, nx.Graph)
-
-    g.is_directed()
+    g = convert_graph_formats(g_original, nx.Graph, directed=g_original.is_directed())
 
     g1 = nx.convert_node_labels_to_integers(g, label_attribute="name")
     name_map = nx.get_node_attributes(g1, 'name')
@@ -737,7 +741,7 @@ def walktrap(g_original):
     for c in coms:
         communities.append([g.vs[x]['name'] for x in c])
 
-    return NodeClustering(communities, g_original, "Walktrap", method_parameters={"":""})
+    return NodeClustering(communities, g_original, "Walktrap", method_parameters={"": ""})
 
 
 def label_propagation(g_original):
@@ -770,7 +774,7 @@ def label_propagation(g_original):
     coms = list(nx.algorithms.community.label_propagation_communities(g))
     coms = [list(x) for x in coms]
 
-    return NodeClustering(coms, g_original, "Label Propagation", method_parameters={"":""})
+    return NodeClustering(coms, g_original, "Label Propagation", method_parameters={"": ""})
 
 
 def async_fluid(g_original, k):
@@ -842,7 +846,7 @@ def der(g_original, walk_len=3, threshold=.00001, iter_bound=50):
         coms.append([maps[n] for n in c])
 
     return NodeClustering(coms, g_original, "DER", method_parameters={"walk_len": walk_len, "threshold": threshold,
-                                                                 "iter_bound": iter_bound})
+                                                                      "iter_bound": iter_bound})
 
 
 def frc_fgsn(g_original, theta, eps, r):
@@ -890,10 +894,10 @@ def frc_fgsn(g_original, theta, eps, r):
         coms = [list(c) for c in communities]
 
     return FuzzyNodeClustering(coms, fuzz_assoc, g_original, "FuzzyComm", method_parameters={"theta": theta,
-                                                                                        "eps": eps, "r": r})
+                                                                                             "eps": eps, "r": r})
 
 
-def sbm_dl(g_original, B_min=None,B_max=None, deg_corr=True, **kwargs):
+def sbm_dl(g_original, B_min=None, B_max=None, deg_corr=True, **kwargs):
     """Efficient Monte Carlo and greedy heuristic for the inference of stochastic block models.
 
     Fit a non-overlapping stochastic block model (SBM) by minimizing its description length using an agglomerative heuristic.
@@ -932,11 +936,12 @@ def sbm_dl(g_original, B_min=None,B_max=None, deg_corr=True, **kwargs):
     affiliations = state.get_blocks().get_array()
     affiliations = {label_map[i]: affiliations[i] for i in range(len(affiliations))}
     coms = affiliations2nodesets(affiliations)
-    coms = [list(v) for k,v in coms.items()]
-    return NodeClustering(coms, g_original, "SBM", method_parameters={"B_min": B_min, "B_max": B_max, "deg_corr": deg_corr})
+    coms = [list(v) for k, v in coms.items()]
+    return NodeClustering(coms, g_original, "SBM",
+                          method_parameters={"B_min": B_min, "B_max": B_max, "deg_corr": deg_corr})
 
 
-def sbm_dl_nested(g_original, B_min=None,B_max=None, deg_corr=True, **kwargs):
+def sbm_dl_nested(g_original, B_min=None, B_max=None, deg_corr=True, **kwargs):
     """Efficient Monte Carlo and greedy heuristic for the inference of stochastic block models. (nested)
 
     Fit a nested non-overlapping stochastic block model (SBM) by minimizing its description length using an agglomerative heuristic.
@@ -977,49 +982,117 @@ def sbm_dl_nested(g_original, B_min=None,B_max=None, deg_corr=True, **kwargs):
     affiliations = level0.get_blocks().get_array()
     affiliations = {label_map[i]: affiliations[i] for i in range(len(affiliations))}
     coms = affiliations2nodesets(affiliations)
-    coms = [list(v) for k,v in coms.items()]
-    return NodeClustering(coms, g_original, "SBM_nested", method_parameters={"B_min": B_min, "B_max": B_max, "deg_corr": deg_corr})
+    coms = [list(v) for k, v in coms.items()]
+    return NodeClustering(coms, g_original, "SBM_nested",
+                          method_parameters={"B_min": B_min, "B_max": B_max, "deg_corr": deg_corr})
 
 
-def markov_clustering(g_original,  max_loop=1000):
+def markov_clustering(g_original, expansion=2, inflation=2, loop_value=1, iterations=100, pruning_threshold=0.001,
+                      pruning_frequency=1, convergence_check_frequency=1):
     """
     The Markov clustering algorithm (MCL) is based on simulation of (stochastic) flow in graphs.
     The MCL algorithm finds cluster structure in graphs by a mathematical bootstrapping procedure. The process deterministically computes (the probabilities of) random walks through the graph, and uses two operators transforming one set of probabilities into another. It does so using the language of stochastic matrices (also called Markov matrices) which capture the mathematical concept of random walks on a graph.
     The MCL algorithm simulates random walks within a graph by alternation of two operators called expansion and inflation.
 
     :param g_original: a networkx/igraph object
-    :param max_loop: maximum number of iterations, default 1000
-    :return: NodeClustering object
+    :param expansion: The cluster expansion factor
+    :param inflation: The cluster inflation factor
+    :param loop_value: Initialization value for self-loops
+    :param iterations: Maximum number of iterations
+           (actual number of iterations will be less if convergence is reached)
+    :param pruning_threshold: Threshold below which matrix elements will be set set to 0
+    :param pruning_frequency: Perform pruning every 'pruning_frequency'
+           iterations.
+    :param convergence_check_frequency: Perform the check for convergence
+           every convergence_check_frequency iterations
+    :return:  NodeClustering object
 
     :Example:
 
     >>> from cdlib import algorithms
     >>> import networkx as nx
     >>> G = nx.karate_club_graph()
-    >>> coms = algorithms.markov_clustering(G, max_loop=1000)
+    >>> coms = algorithms.markov_clustering(G)
 
     :References:
 
     Enright, Anton J., Stijn Van Dongen, and Christos A. Ouzounis. `An efficient algorithm for large-scale detection of protein families. <https://www.ncbi.nlm.nih.gov/pubmed/11917018/>`_ Nucleic acids research 30.7 (2002): 1575-1584.
 
-    .. note:: Reference implementation: https://github.com/HarshHarwani/markov-clustering-for-graphs
+    .. note:: Reference implementation: https://github.com/GuyAllard/markov_clustering
     """
 
     g = convert_graph_formats(g_original, nx.Graph)
     g, maps = nx_node_integer_mapping(g)
 
-    communities = markov(g, max_loop)
+    if maps is not None:
+        matrix = nx.to_scipy_sparse_matrix(g, nodelist=range(len(maps)))
+    else:
+        matrix = nx.to_scipy_sparse_matrix(g)
 
+    result = mc.run_mcl(matrix, expansion=expansion, inflation=inflation, loop_value=loop_value, iterations=iterations,
+                        pruning_threshold=pruning_threshold, pruning_frequency=pruning_frequency,
+                        convergence_check_frequency=convergence_check_frequency)  # run MCL with default parameters
+    clusters = mc.get_clusters(result)
+
+    coms = []
     if maps is not None:
-        communities = []
-        for c in communities:
-            communities.append([tuple(maps[n]) for n in c])
+        for c in clusters:
+            coms.append([maps[n] for n in c])
+
+        nx.relabel_nodes(g, maps, False)
+    else:
+        coms = [list(c) for c in clusters]
+
+    return NodeClustering(coms, g_original, "Markov Clustering",
+                          method_parameters={'expansion': expansion, 'inflation': inflation, 'loop_value': loop_value,
+                                             'iterations': iterations, 'pruning_threshold': pruning_threshold,
+                                             'pruning_frequency': pruning_frequency,
+                                             'convergence_check_frequency': convergence_check_frequency})
+
+
+def chinesewhispers(g_original, weighting='top', iterations=20, seed=None):
+    """
+
+    Fuzzy graph clustering that (i) creates an intermediate representation of the input graph, which reflects the “ambiguity” of its nodes,
+    and (ii) uses hard clustering to discover crisp clusters in such “disambiguated” intermediate graph.
+
+    :param g_original:
+    :param weighting: edge weighing schemas. Available modalities: ['top', 'lin', 'log']
+    :param iterations: number of iterations
+    :param seed: random seed
+    :return: NodeClustering object
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.karate_club_graph()
+    >>> coms = algorithms.chinesewhispers(G)
+
+    :References:
+
+    Ustalov, D., Panchenko, A., Biemann, C., Ponzetto, S.P.: `Watset: Local-Global Graph Clustering with Applications in Sense and Frame Induction.`_  Computational Linguistics 45(3), 423–479 (2019)
+
+    .. note:: Reference implementation: https://github.com/nlpub/chinese-whispers-python
+    """
+
+    g = convert_graph_formats(g_original, nx.Graph)
+    g, maps = nx_node_integer_mapping(g)
+
+    cw(g, weighting=weighting, iterations=iterations, seed=seed)
+
+    coms = []
+    if maps is not None:
+        for _, cluster in sorted(aggregate_clusters(g).items(), key=lambda e: len(e[1]), reverse=True):
+            coms.append([maps[n] for n in cluster])
 
         nx.relabel_nodes(g, maps, False)
     else:
-        communities = [list(c) for c in communities]
+        for _, cluster in sorted(aggregate_clusters(g).items(), key=lambda e: len(e[1]), reverse=True):
+            coms.append(list(cluster))
 
-    return NodeClustering(communities, g_original, "Markov Clustering", method_parameters={"max_loop": max_loop})
+    return NodeClustering(coms, g_original, "Chinese Whispers",
+                          method_parameters={'weighting': weighting, 'iterations': iterations})
 
 
 def edmot(g_original, component_count=2, cutoff=10):
@@ -1036,7 +1109,7 @@ def edmot(g_original, component_count=2, cutoff=10):
     >>> from cdlib import algorithms
     >>> import networkx as nx
     >>> G = nx.karate_club_graph()
-    >>> coms = algorithms.markov_clustering(G, max_loop=1000)
+    >>> coms = algorithms.edmot(G, max_loop=1000)
 
     :References:
 
@@ -1058,4 +1131,145 @@ def edmot(g_original, component_count=2, cutoff=10):
 
     coms = [list(c) for c in coms_to_node.values()]
 
-    return NodeClustering(coms, g_original, "EdMot", method_parameters={"component_count": component_count, "cutoff": cutoff})
+    return NodeClustering(coms, g_original, "EdMot",
+                          method_parameters={"component_count": component_count, "cutoff": cutoff})
+
+
+def siblinarity_antichain(g_original, forwards_backwards_on=True, backwards_forwards_on=False,
+                          Lambda=1, with_replacement=False, space_label=None, time_label=None):
+    """
+    The algorithm extract communities from a DAG that (i) respects its intrinsic order and (ii) are composed of similar nodes.
+    The approach takes inspiration from classic similarity measures of bibliometrics, used to assess how similar two publications are, based on their relative citation patterns.
+
+    :param g_original: a networkx/igraph object representing a DAG (directed acyclic graph)
+    :param forwards_backwards_on: checks successors' similarity. Boolean, default True
+    :param backwards_forwards_on: checks predecessors' similarity. Boolean, default True
+    :param Lambda: desired resolution of the partition. Default 1
+    :param with_replacement: If True he similarity of a node to itself is equal to the number of its neighbours based on which the similarity is defined. Boolean, default True.
+    :return: NodeClustering object
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.karate_club_graph()
+    >>> coms = algorithms.siblinarity_antichain(G, Lambda=1)
+
+    :References:
+
+    Vasiliauskaite, V., Evans, T.S. Making communities show respect for order. Appl Netw Sci 5, 15 (2020). https://doi.org/10.1007/s41109-020-00255-5
+
+    .. note:: Reference implementation: https://github.com/vv2246/siblinarity_antichains
+    """
+
+    g = convert_graph_formats(g_original, nx.Graph)
+
+    if not nx.is_directed_acyclic_graph(g):
+        raise Exception("The Siblinarity Antichain algorithm require as input a Directed Acyclic Graph (DAG).")
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+        result_list = matrix_node_recursive_antichain_partition(g, forwards_backwards_on=forwards_backwards_on,
+                                                            backwards_forwards_on=backwards_forwards_on,
+                                                            Q_check_on=True,
+                                                            Lambda=Lambda, with_replacement=with_replacement,
+                                                            space_label=None, time_label=None)
+
+    node_partition = {}
+    for n in g.nodes():
+        p_at_level = result_list[0]["n_to_p"][n]
+        for i in range(1, len(result_list) - 1):
+            p_at_level = result_list[i]["n_to_p"][p_at_level]
+        node_partition[n] = p_at_level
+
+    partition = defaultdict(list)
+    for key, val in node_partition.items():
+        partition[val].append(key)
+
+    coms = [list(c) for c in partition.values()]
+
+    return NodeClustering(coms, g_original, "Siblinarity Antichain",
+                          method_parameters={"forwards_backwards_on": forwards_backwards_on,
+                                             "backwards_forwards_on": backwards_forwards_on,
+
+                                             "Lambda": Lambda,
+                                             "with_replacement": with_replacement,
+                                             "space_label": space_label,
+                                             "time_label": time_label})
+
+
+def ga(g_original, population=300, generation=30, r=1.5):
+    """
+    Genetic based approach to discover communities in social networks.
+    GA optimizes a simple but efficacious fitness function able to identify densely connected groups of nodes with sparse connections between groups.
+
+    :param g_original: a networkx/igraph object
+    :param population:
+    :param generation:
+    :param r:
+    :return: NodeClustering object
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.karate_club_graph()
+    >>> coms = algorithms.ga(G)
+
+    :References:
+
+     Pizzuti, C. (2008). Ga-net: A genetic algorithm for community detection in social networks. In Inter conf on parallel problem solving from nature, pages 1081–1090.Springer.
+
+    .. note:: Reference implementation: https://github.com/hariswb/ga-community-detection
+    """
+
+    g = convert_graph_formats(g_original, nx.Graph)
+    coms = ga_community_detection(g, population, generation, r)
+
+    return NodeClustering(coms, g_original, "ga",
+                          method_parameters={"population": population, "generation": generation, 'r': r})
+
+
+def belief(g_original, max_it=100, eps=0.0001, reruns_if_not_conv=5, threshold=0.005, q_max=7):
+    """
+    Belief community seeks the consensus of many high-modularity partitions.
+    It does this with a scalable message-passing algorithm, derived by treating the modularity as a Hamiltonian and applying the cavity method.
+
+    :param g_original: a networkx/igraph object
+    :param max_it:
+    :param eps:
+    :param reruns_if_not_conv:
+    :param threshold:
+    :param q_max:
+    :return: NodeClustering object
+
+    :Example:
+
+    >>> from cdlib import algorithms
+    >>> import networkx as nx
+    >>> G = nx.karate_club_graph()
+    >>> coms = algorithms.belief(G)
+
+    :References:
+
+    Zhang, Pan, and Cristopher Moore. "Scalable detection of statistically significant communities and hierarchies, using message passing for modularity." Proceedings of the National Academy of Sciences 111.51 (2014): 18144-18149.
+
+    .. note:: Reference implementation: https://github.com/weberfm/belief_propagation_community_detection
+    """
+
+    g = convert_graph_formats(g_original, nx.Graph)
+
+    mapping = {n: i for i, n in enumerate(g.nodes())}
+    inv_map = {v: k for k, v in mapping.items()}
+    g = nx.relabel_nodes(g, mapping)
+
+    coms = detect_belief_communities(g, max_it=max_it, eps=eps, reruns_if_not_conv=reruns_if_not_conv, threshold=threshold, q_max=q_max)
+
+    res = []
+    for com in coms:
+        com = [inv_map[c] for c in com]
+        res.append(com)
+
+    return NodeClustering(res, g_original, "Belief",
+                          method_parameters={"max_it": max_it, "eps": eps, 'reruns_if_not_conv': reruns_if_not_conv,
+                                             "threshold": threshold, "q_max": q_max})
\ No newline at end of file
diff --git a/cdlib/algorithms/internal/AGDL.py b/cdlib/algorithms/internal/AGDL.py
index 481f5b5d..01ba8d69 100644
--- a/cdlib/algorithms/internal/AGDL.py
+++ b/cdlib/algorithms/internal/AGDL.py
@@ -11,8 +11,8 @@ def __knn(k, x):
     return distance, indices
 
 
-def __w_matrix(data, distance, indices, ks, a=10):
-    n = len(data)
+def __w_matrix(distance, indices, ks, a=10):
+    n = len(distance)
 
     weight_matrix = np.zeros([n, n])
 
@@ -21,22 +21,21 @@ def __w_matrix(data, distance, indices, ks, a=10):
     if ks == 1:
         for i in range(n):
             j = indices[i][0]
-            weight_matrix[i][j] = np.exp(-1 * (np.linalg.norm(data[i] - data[j]) ** 2) / sigma2)
+            weight_matrix[i][j] = np.exp(-1 * (distance[i, 0] ** 2) / sigma2)
     else:
         for i in range(n):
-            for j in indices[i]:
-                weight_matrix[i][j] = np.exp(-1 * (np.linalg.norm(data[i] - data[j]) ** 2) / sigma2)
+            for k, j in enumerate(indices[i]):
+                weight_matrix[i][j] = np.exp(-1 * (distance[i, k] ** 2) / sigma2)
 
     return weight_matrix, sigma2
 
 
-def __k0graph(x, distance, indices, a=10):
-    w, sigma2 = __w_matrix(x, distance, indices, 1, a)
+def __k0graph(similarity):
+    x = np.arange(len(similarity))
+    y = [np.argmax(row) for row in similarity]
 
     vc = []
 
-    x, y = np.where(w > 0)
-
     for i in range(len(x)):
         x_index, y_index = -1, -1
         for k in range(len(vc)):
@@ -45,7 +44,7 @@ def __k0graph(x, distance, indices, a=10):
             if x[i] in vc[k]:
                 x_index = k
 
-        if x_index == y_index:
+        if x_index == y_index and x_index != -1 and y_index != -1:
             continue
         elif x_index < 0 and y_index < 0:
             vc.append([x[i], y[i]])
@@ -110,17 +109,25 @@ def __get_neighbor(vc, kc, w):
     return ns, as_
 
 
-def Agdl(g, target_cluster_num, ks, kc, a):
-    data = nx.to_numpy_matrix(g)
+def preprocess(data, ks, a):
+    """
+    From data to graph.
+    __knn and __w_matrix only use for preprocess
+    """
     distance, indices = __knn(ks, data)
+    # convert distance to similarity
+    similarity, _ = __w_matrix(distance, indices, ks, a)
+    g = nx.from_numpy_matrix(similarity, create_using=nx.DiGraph)
+    return g
 
-    cluster = __k0graph(data, distance, indices, a)
-    length = 0
-    for i in range(len(cluster)):
-        length += len(cluster[i])
 
-    w, sigma = __w_matrix(data, distance, indices, ks, a)
-    neighbor_set, affinity_set = __get_neighbor(cluster, kc, w)
+def Agdl(g, target_cluster_num, kc):
+    similarity = nx.to_numpy_matrix(g)
+    # Using k0grpha to initilize cluster
+
+    cluster = __k0graph(similarity)
+
+    neighbor_set, affinity_set = __get_neighbor(cluster, kc, similarity)
     current_cluster_num = len(cluster)
 
     while current_cluster_num > target_cluster_num:
@@ -162,7 +169,7 @@ def Agdl(g, target_cluster_num, ks, kc, a):
                 continue
 
             if max_index1 in neighbor_set[i]:
-                aff_update = __get_affinity_btw_cluster(cluster[i], cluster[max_index1], w)
+                aff_update = __get_affinity_btw_cluster(cluster[i], cluster[max_index1], similarity)
 
                 p = neighbor_set[i].index(max_index1)
                 affinity_set[i][p] = aff_update  # fix the affinity values
@@ -174,7 +181,7 @@ def Agdl(g, target_cluster_num, ks, kc, a):
                 del affinity_set[i][p]
 
                 if max_index1 not in neighbor_set[i]:
-                    aff_update = __get_affinity_btw_cluster(cluster[i], cluster[max_index1], w)
+                    aff_update = __get_affinity_btw_cluster(cluster[i], cluster[max_index1], similarity)
                     neighbor_set[i].append(max_index1)
                     affinity_set[i].append(aff_update)
 
@@ -190,7 +197,7 @@ def Agdl(g, target_cluster_num, ks, kc, a):
 
         for i in range(len(neighbor_set[max_index1])):
             target_index = neighbor_set[max_index1][i]
-            new_affinity = __get_affinity_btw_cluster(cluster[target_index], cluster[max_index1], w)
+            new_affinity = __get_affinity_btw_cluster(cluster[target_index], cluster[max_index1], similarity)
             affinity_set[max_index1].append(new_affinity)
 
         if len(affinity_set[max_index1]) > kc:
@@ -210,8 +217,5 @@ def Agdl(g, target_cluster_num, ks, kc, a):
     for i in range(len(cluster)):
         if len(cluster[i]) != 0:
             reduced_cluster.append(cluster[i])
-    length = 0
-    for i in range(len(reduced_cluster)):
-        length += len(reduced_cluster[i])
 
     return reduced_cluster
diff --git a/cdlib/algorithms/internal/ILouvain.py b/cdlib/algorithms/internal/ILouvain.py
index 2e23b6b4..fd13b059 100644
--- a/cdlib/algorithms/internal/ILouvain.py
+++ b/cdlib/algorithms/internal/ILouvain.py
@@ -1,20 +1,11 @@
-#!/usr/bin/python
-
 from __future__ import division
-
-#from pprint import pprint
-
-#import argparse
 import numpy as np
-#from scipy.spatial.distance import pdist, squareform
 import os.path
-
-# -*- coding: utf-8 -*-
 """
 This module implements community detection.
 """
-__all__ = ["partition_at_level", "modularity", "best_partition", "generate_dendogram", "induced_graph"]
-__author__ = """Thomas Aynaud (thomas.aynaud@lip6.fr)"""
+
+__author__ = ["Thomas Aynaud (thomas.aynaud@lip6.fr)"]
 #    Copyright (C) 2009 by
 #    Thomas Aynaud <thomas.aynaud@lip6.fr>
 #    All rights reserved.
@@ -23,7 +14,7 @@
 import networkx as nx
 
 
-class ML2:
+class ML2(object):
     __MIN = 0.000001
     __PASS_MAX = -1
     LOGOPERATIONS = False
@@ -49,9 +40,6 @@ def __init__(self, graph, attributes, authorIndex):
         self.status_list = list()
 
     def critereCombinaison(self):
-        # if(args.verbose):
-        #    print("Mod1: " + str(self.__modularity(self.statusTab[0])))
-        #    print("Mod2: " + str(self.__modularity(self.statusTab[1])))
         return (self.__modularity(self.statusTab[0]) + self.__modularity(self.statusTab[1])) / 2.
 
     def findPartition(self):
@@ -75,11 +63,6 @@ def findPartition(self):
             self.__one_level(giniMatrix=giniMatrix)
             new_mod = self.critereCombinaison()
             if new_mod - mod < self.__MIN:
-                # if(args.verbose):
-                #    print("modularities")
-                #    print(self.__modularity(self.statusTab[0]))
-                #    print(self.__modularity(self.statusTab[1]))
-                #    print("Modularity Final: " + str(self.__modularity(self.statusTab[1]) + self.__modularity(self.statusTab[0])))
                 break
             partition, bijection = self.__renumber()
 
@@ -103,17 +86,6 @@ def findPartition(self):
                 partition[node] = dendogram[index][community]
 
         return partition
-        # for elem, part in sorted(partition.iteritems()) :
-        # if(args.verbose):
-        #    print(str(self.authorIndex[elem]) + " " + str(part) + " " + str(self.attributes[self.authorIndex[elem]]))
-        # else:
-        #    out = str(self.authorIndex[elem]) + " " + str(part)
-        # if(args.multipleDataset != None):
-        #    f = open(args.dataset + "_" + str(curDatasetIdx) + ".2ModLouvain",'a')
-        # else:
-        #    f = open(args.dataset + ".2ModLouvain",'a')
-        #    f.write(out + "\n")
-        #    f.close()
 
     def dist(self, v1, v2):
         attrV1 = self.attributes[v1]
@@ -317,7 +289,7 @@ def __modularity(self, status):
         return result
 
 
-class Status:
+class Status(object):
     """
     To handle several data in one struct.
     Could be replaced by named tuple, but don't want to depend on python 2.6
@@ -351,7 +323,6 @@ def initAttribStatus(self, graph, authorIndex, attributes):
 
         variance = {}
         for node in sorted(graph.nodes()):
-            distanceToCenterOfGravity = 0.
             for attrId, attrValue in meanVector.items():
                 variance[attrId] = variance.get(attrId, 0.) + (
                             (attrValue - attributes[authorIndex[node]].get(attrId, 0.)) ** 2)
@@ -359,8 +330,6 @@ def initAttribStatus(self, graph, authorIndex, attributes):
         for v in variance.values():
             inertieTot += (v / N)
 
-        # if(args.verbose):
-        #    print("# Total inertia:", inertieTot)
         self.total_weight = (0.0 - inertieTot)
 
         for node in sorted(graph.nodes()):
@@ -426,7 +395,7 @@ def loadDataset(path):
     graph = nx.Graph()
 
     # Read the graph
-    if (not os.path.isfile(path + ".edgeList")):
+    if not os.path.isfile(path + ".edgeList"):
         print("Error: file '" + path + ".edgeList' not found")
         exit(-1)
     with open(path + ".edgeList") as f:
@@ -442,7 +411,7 @@ def loadDataset(path):
     for n in graph:
         attributes[n] = {}
 
-    if (not os.path.isfile(path + ".attributes")):
+    if not os.path.isfile(path + ".attributes"):
         print("Error: file '" + path + ".attributes' not found")
         exit(-1)
 
@@ -468,37 +437,3 @@ def loadDataset(path):
         os.remove(path + ".2ModLouvain")
 
     return graph, attributes, authorIndex
-
-
-def readToyGraph():
-    graph = nx.Graph()
-    graph.add_node("a")
-    graph.add_node("b")
-    graph.add_node("c")
-    graph.add_node("d")
-    graph.add_node("e")
-    graph.add_edge("a", "b")
-    graph.add_edge("b", "c")
-    graph.add_edge("c", "d")
-    graph.add_edge("d", "e")
-    graph.add_edge("a", "e")
-    graph.add_edge("b", "e")
-    graph.add_edge("c", "e")
-    graph.add_edge("b", "d")
-    graph.add_edge("a", "c")
-    graph.add_edge("a", "d")
-
-    authorIndex = {}
-    authorIndex["a"] = 0
-    authorIndex["b"] = 1
-    authorIndex["c"] = 2
-    authorIndex["d"] = 3
-    authorIndex["e"] = 4
-
-    attributes = {
-        0: {0: 2., 1: 4},
-        1: {0: 8., 1: 1},
-        2: {0: 7., 1: 5},
-        3: {0: 12., 1: 6},
-        4: {0: 1., 1: 4}}
-    return graph, attributes, authorIndex
\ No newline at end of file
diff --git a/cdlib/algorithms/internal/SiblinarityAntichain.py b/cdlib/algorithms/internal/SiblinarityAntichain.py
new file mode 100644
index 00000000..da9a5f02
--- /dev/null
+++ b/cdlib/algorithms/internal/SiblinarityAntichain.py
@@ -0,0 +1,683 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Jun 20 12:52:55 2018
+@author: Vaiva & Tim
+"""
+
+from collections import defaultdict
+import itertools
+import numpy as np
+import random
+import itertools
+import math
+import networkx as nx
+import scipy.sparse as sparse
+
+__authors__ = ['Vaiva Vasiliauskaite', 'T.S. Evans']
+__all__ = ['matrix_node_recursive_antichain_partition']
+
+
+def is_weakly_connected(graph, source_nodes, target_nodes):
+    """
+    Tests whether a list of source nodes in a graph have a path in either direction between a list of target nodes.
+
+    Parameters
+    ----------
+    graph = networkx graph
+    source_nodes = list of source nodes for paths
+    target_nodes = list of target nodes for paths
+
+    Returns
+    -------
+    Bool:
+        True if there is a path from at least one source node to at least one target node or the other way round
+        False otherwise
+    """
+    for s, t in itertools.product(source_nodes, target_nodes):
+        if nx.has_path(graph, s, t) or nx.has_path(graph, t, s):
+            return True
+    return False
+
+
+class Quality_matrix:
+    '''
+    Quality measures for use in antichains. Similarity matrix implementation.
+    '''
+
+    # class variables
+    def __init__(self, node_id_dict, similarity_matrix, Lambda, with_replacement):
+        # Initial Quality Measures
+        self.similarity_matrix = similarity_matrix
+        self.node_id_dict = node_id_dict
+        self.strength = similarity_matrix.sum(axis=0)
+        self.strength = {n: self.strength[0, node_id_dict[n]] for n in node_id_dict.keys()}  # sum over rows?
+        self.total_weight = similarity_matrix.sum()  # /2
+        self.Lambda = Lambda
+        self.with_replacement = with_replacement
+
+    def delta_strength_quality_unnormalised(self, partition1, partition2):
+        """
+        Using in-strength null model calculate the change in unnormalised quality if two partitions are combined.
+
+        Definition is that used for weighted graph.
+
+        Q = \sum_{u \in partition1} \sum_{v \in partition2}
+            ( S_ij
+             - k_i*k_j/W )
+        where W = total strength of edges in the graph ((sum_{i,j}S_ij)/2),
+        S_{ij} - i,j^th entry in the similarity matrix. For instance, A.A^T is successors-based similarity;
+                A^T.A is predecessors-based similarity.
+
+        Note this is not normalised.
+
+        Note no test for connectedness of nodes in partitions.
+
+        Note both partitions must be non-empty otherwise TypeError raised.
+
+        Note no test to see if partitions are sets or if they share common elements.
+
+        Input
+        partition1 - iterable list or set of the nodes in first partition
+        partition2 - iterable list or set of the nodes in second partition
+
+        Return
+        Contribution of the quality Q from the all pairs of nodes with one from partition1, second from partition2
+        """
+
+        return sum([self.similarity_matrix[self.node_id_dict[node1], self.node_id_dict[node2]]
+                    - self.Lambda * self.strength[node1] * self.strength[node2] / self.total_weight
+                    for node1, node2 in itertools.product(partition1, partition2)])
+
+    def quality_one_partition(self, partition):
+        return sum([self.similarity_matrix[self.node_id_dict[node1], self.node_id_dict[node2]]
+                    - self.Lambda * self.strength[node1] * self.strength[node2] / self.total_weight
+                    for node1, node2 in itertools.combinations(partition, 2)])
+
+    def total_strength_quality_unnormalised(self, partitions):
+        """
+        Calculate the total unnormalised quality using strength null model
+
+        Definition is that used for weighted graph.
+
+        Q = \sum_{u \in partition1} \sum_{v \in partition2}
+            ( S_ij
+             - k_i*k_j/W )
+        where W = total strength of edges in the graph ((sum_{i,j}S_ij)/2),
+        S_{ij} - i,j^th entry in the similarity matrix. For instance, A.A^T is successors-based similarity;
+                A^T.A is predecessors-based similarity.
+
+        Note this is not normalised.
+
+        Note no test for connectedness of nodes in partitions.
+
+        Input
+        -----
+        partition - iterable list of sets of nodes in partitions so that
+                    partition[p] is an set (or any iterable list) of the nodes in partition p
+
+        Return
+        ------
+        Total value of the quality Q from the all pairs of nodes
+        """
+        return sum([
+            sum([self.similarity_matrix[self.node_id_dict[node1], self.node_id_dict[node2]]
+                 - self.Lambda * self.strength[node1] * self.strength[node2] / self.total_weight
+                 for node1, node2 in itertools.combinations(p, 2)])
+            for p in partitions])
+
+
+def get_edge_weight(G, node1, node2, weight_attribute='weight'):
+    """
+    Get Edge Weight
+
+    Returns edge weight for edge in G from node1 to node2
+    If edge exists and has weight_attribute, this value is returned.
+    If edge exists but has not weight_attribute, 1 is returned.
+    Otherwise 0 is returned
+
+    Input
+    -----
+    G - networkx graph
+    node1 - source node
+    node2 - target node
+    weight_attribute='weight' - attribute of edge containing weight value
+
+    Return
+    ------
+    edge weight, 1 if edge exists but no weight attribute exists, 0 otherwise.
+
+    """
+    edge_data = G.get_edge_data(node1, node2)
+    if edge_data is None:
+        return 0
+    elif weight_attribute in edge_data:
+        return edge_data[weight_attribute]
+    else:
+        return 1
+
+
+def get_node_attribute_value(G, node1, node_attribute=None):
+    """
+    Get Node Attribute Value
+
+    Returns node attribute as a float.
+    Otherwise 0.0 is returned
+
+    Input
+    -----
+    G - networkx graph
+    node1 - node
+    node_attribute=None - attribute of node required
+
+    Return
+    ------
+    node attribute as a float
+
+    """
+    try:
+        node_data = G.node[node1][node_attribute]
+        return float(node_data)
+    except:
+        pass
+    return 0.0
+
+
+def is_weakly_connected(graph, source_nodes, target_nodes):
+    """
+    Tests whether a list of source nodes in a graph have a path in either direction between a list of target nodes.
+
+    Parameters
+    ----------
+    graph = networkx graph
+    source_nodes = list of source nodes for paths
+    target_nodes = list of target nodes for paths
+
+    Returns
+    -------
+    Bool:
+        True if there is a path from at least one source node to at least one target node or the other way round
+        False otherwise
+    """
+    for s, t in itertools.product(source_nodes, target_nodes):
+        if nx.has_path(graph, s, t) or nx.has_path(graph, t, s):
+            return True
+    return False
+
+
+def coarse_grain(G, node_to_partition_label, partition_label_to_nodes, weight_attribute='weight',
+                 time_label='t', space_label='x'):
+    """
+    Coarse Grain
+
+    The new graph H has the partitions of G as the nodes in H.
+    An edges from partition1 to partition2 in H is present if
+    there is an edge from a node in G in partition1 of G to
+    a node in G in partition2.  The total weight of the edge
+    from partition1 to partition2 in H will be the sum of all
+    the weights of all such edges in G
+    from nodes in partition1 to nodes in partition2.
+    If unweighted, weights are assumed to be 1.
+    If time_label or space_label are set, these are assumed to be numerical
+    values (e.g. coordinates) and nodes in the new graph get the average value from
+    the partition of nodes they represent in the old graph.
+
+
+    Input
+    ----
+    G - networkx graph
+    node_to_partition_label - dictionary from G node key to its partition label
+    partition_label_to_nodes - dictionary from partition label to the set of nodes in G in that partition
+    weight_attribute='weight' - attribute on edge containing edge weight data
+    time_label='t': Node key for time coordinate (used as y/vertical coordinate)
+    space_label='x': Node key for space coordinate (used as x/horizontal coordinate)
+
+    Return
+    ------
+    H - coarse grained graph, nodes are the partitions labels, weights are under eight_attribute of edges
+
+    """
+    H = nx.DiGraph()
+    H.add_nodes_from(list(partition_label_to_nodes.keys()))
+    for partition in partition_label_to_nodes.keys():
+        # nodes_in_partition = partition_label_to_nodes[partition]
+        number_in_partition = len(partition_label_to_nodes[partition])
+        if time_label is not None:
+            average_time = sum([get_node_attribute_value(G, n, node_attribute=time_label) for n in
+                                partition_label_to_nodes[partition]]) / number_in_partition
+        H.nodes[partition][time_label] = average_time
+        if space_label is not None:
+            average_space = sum([get_node_attribute_value(G, n, node_attribute=space_label) for n in
+                                 partition_label_to_nodes[partition]]) / number_in_partition
+        H.nodes[partition][space_label] = average_space
+
+    for partition1, partition2 in itertools.combinations(partition_label_to_nodes.keys(), 2):
+        w = sum([get_edge_weight(G, node1, node2, weight_attribute) for node1, node2 in
+                 itertools.product(partition_label_to_nodes[partition1], partition_label_to_nodes[partition2])])
+        if w > 0:
+            H.add_edge(partition1, partition2, weight_attribute=w)
+        w = sum([get_edge_weight(G, node2, node1, weight_attribute) for node1, node2 in
+                 itertools.product(partition_label_to_nodes[partition1], partition_label_to_nodes[partition2])])
+        if w > 0:
+            H.add_edge(partition2, partition1, weight_attribute=w)
+    return H
+
+
+def similarity_matrix(DAG, similarity="intersection", neighbours="successors"):
+    """
+    Function to produce a similarity matrix based on neighbourhoods of nodes in DAG.
+
+    Input
+    -----
+    DAG - networkx directed acyclic graph
+    similarity - type of similarity of sets. Currently only implemented for the size of intersection
+    neighbours - type of neighbours to consider in the similarity. Can be either successors or predecessors
+
+    Return
+    -----
+    A - symmetric similarity matrix where entry A[i,j] represents similarity between nodes of indices i, j
+    nodedict - dictionary of node names and their indices in the similarity matrix
+    """
+
+    nodes = list(DAG.nodes())
+    nodedict = {}
+    for i in range(len(nodes)):
+        nodedict[nodes[i]] = i
+    nodelist = list(nodedict.keys())
+
+    A = (nx.adjacency_matrix(DAG, nodelist)).todense()
+
+    if similarity == "intersection" and neighbours == "successors":
+        A = A.dot(A.transpose())
+        np.fill_diagonal(A, 0)
+        return A, nodedict
+
+    elif similarity == "intersection" and neighbours == "predecessors":
+        A = (A.transpose()).dot(A)
+        np.fill_diagonal(A, 0)
+        return A, nodedict
+
+
+def similarity_matrix_sparse(DAG, similarity="intersection", neighbours="successors", with_replacement=False):
+    """
+    Function to produce a sparse similarity matrix based on neighbourhoods of nodes in DAG.
+
+    Input
+    -----
+    DAG - networkx directed acyclic graph
+    similarity - type of similarity of sets. Currently only implemented for the size of intersection
+    neighbours - type of neighbours to consider in the similarity. Can be either successors or predecessors or both
+
+    Return
+    -----
+    A - scipy sparse symmetric similarity matrix where entry A[i,j] represents similarity between nodes of indices i, j
+    nodedict - dictionary of node names and their indices in the similarity matrix
+    """
+
+    nodes = list(DAG.nodes())
+    nodedict = {}
+    for i in range(len(nodes)):
+        nodedict[nodes[i]] = i
+    nodelist = list(nodedict.keys())
+
+    A = (nx.adjacency_matrix(DAG, nodelist))
+
+    if similarity == "intersection" and neighbours == "successors":
+        A = A.dot(A.transpose())
+        if not with_replacement:
+            A.setdiag(0)
+        return A, nodedict
+
+    elif similarity == "intersection" and neighbours == "predecessors":
+        A = A.transpose().dot(A)
+        if not with_replacement:
+            A.setdiag(0)
+        return A, nodedict
+    elif similarity == "intersection" and neighbours == "both":
+        A = A.transpose().dot(A) + A.dot(A.transpose())
+        if not with_replacement:
+            A.setdiag(0)
+        return A, nodedict
+
+
+def has_path_matrix(DAG, nodedict, cutoff=350):
+    nodes = list(nodedict.keys())
+    A = (nx.adjacency_matrix(DAG, nodes)).todense()
+    A_sum = np.copy(A)
+    if nx.is_directed_acyclic_graph(DAG):
+        L_max = len(nx.dag_longest_path(DAG))
+    else:
+        L_max = cutoff
+    current_length = 1
+    while current_length <= L_max:
+        A_sum = np.dot(A_sum, A)
+        current_length += 1
+
+    return (A_sum > 0).astype(np.int8)
+
+
+def find_paths_sparse(A, length_max=10):
+    """
+    Tim's numpy path implementation updated by Vaiva to sparse matrices
+    Scipy sparse matrix implementation to find all paths
+    Given adjacency matrix A will find all the paths between all vertices
+
+    Input
+    A: numpy square adjacency matrix,can be weighted
+    Return
+    #tuple current_length,path_length,path_bool where
+    #current_length = one more than the maximum length found.
+                   If equals length_max then may have terminated because reachd maximum requested length
+    #non_zero_entries = number of non-zero entries in P=(A)^current_length
+    #path_length = matrix of longest paths lengths
+                   path_length[target,source]= longest path from source to target
+    path_bool = matrix of booleans indicating if path exists. Paths from vertex to slef (length zero) gives True on diagonal
+                   path_bool[target,source]= True (False) if path from source to target
+    """
+
+    # Assume vertices start connected to selves only path path of length zero.
+    m, n = np.shape(A)
+    path_bool = sparse.eye(m, n, dtype=bool)
+    path_bool = path_bool.tolil()
+    path_length = sparse.lil_matrix((m, n), dtype="int32")
+    current_length = 1
+    P = A.copy()
+    non_zero_entries = P.count_nonzero()
+
+    while non_zero_entries > 0 and current_length < length_max:
+        non_zero_entries = P.nonzero()
+        path_bool[non_zero_entries[0], non_zero_entries[1]] = True
+        path_length[non_zero_entries] = current_length
+        P = P.dot(A)
+        current_length += 1
+        non_zero_entries = P.count_nonzero()
+    return path_bool
+
+
+def is_weakly_connected_matrix(path_matrix, nodedict, source_nodes, target_nodes):
+    """
+    Function to check whether nodes in the source_nodes are not weakly connected to
+    nodes in the target_nodes.
+
+    Input
+    -----
+    path_matrix - 1/0 matrix where entry P[i,j] = 1 if nodes with indices i,j are weakly connected
+    nodedict - dictionary where keys are node names and values are their corresponding indices in the path matrix
+    source_nodes - list of nodes
+    target_nodes - list of nodes
+
+    Return
+    ------
+    True - if nodes in source_nodes and target_nodes form a weakly_connected subgraph
+    False - if not
+    """
+    source_nodes_id, target_nodes_id = [nodedict[s] for s in source_nodes], [nodedict[t] for t in target_nodes]
+
+    for s, t in itertools.product(source_nodes_id, target_nodes_id):
+        if path_matrix[s, t] == 1 or path_matrix[t, s] == 1:
+            return True
+    return False
+
+
+def node_matrix_greedy_antichain_partition(G, level, Lambda, with_replacement,
+                                           random_on=False, seed=None, max_number_sweeps=None,
+                                           backwards_forwards_on=True, forwards_backwards_on=False,
+                                           Q_check_on=True, weight_attribute="weight"):
+    """
+    In this implementation we iterate over nodes in the graph moving individual nodes until no changes occur .
+
+    We start with each node in its own partition.
+    In one sweep we look at each partition ac in turn.
+    We find all the backwards-forwards neighbours of the nodes in partition ac
+    and collect all their partition labels, excluding the current partition ac.
+    For each of these we find if we can increase the quality function by merging
+    current partition ac with one of its bf-neighbouring partitions. If we do we then
+    do the merge removing the current partition ac.
+    We continue the sweep looking at remaining partitions and trying to merge them.  Note that
+    later partitions will see some previous partitions already merged.  The option to randomise the order
+    that we visit the partitions will lead to different results.
+
+    After each sweep, if at least one partition was merged then we sweep through again.
+    We only stop when no more merges are found on one sweep, or if the number of sweeps exceed
+    the maximum requested.
+
+    Note that we do NOT create an explicit hybrid graph. We only use a weakly connected check
+    of the
+
+    Input
+    -----
+    G - networkx graph.
+    random_on=False - if true will shuffle the order in which partitions are examined
+    seed=None - used as seed if shuffle is on.  If None then time is used as seed
+    max_number_sweeps=None - this is the maximum number of sweeps to consider. If less than 1 or None, then uses number of nodes.
+    backwards_forwards_on=True  - find possible new partitions by making a backwards step then a forwards step from node being considered for a move
+    forwards_backwards_on=False - find possible new partitions by making a forwards step then a backwards step from node being considered for a move
+    Q_check_on=False - check to see if change in Q is correct by printing out total value and changes
+    weight_attribute - edge attribute of weight. if None, unweighted quality functions are used. Note, weight must be integer
+
+    Return
+    ------
+    tuple node_to_partition_label, partition_label_to_nodes
+    where
+    node_to_partition_label is a dictionary from node key to its partition label
+    partition_label_to_nodes is a dictionary from partition label to the set of nodes in that partition
+
+    """
+    if not (forwards_backwards_on or backwards_forwards_on):
+        raise ValueError("At least one of forwards_backwards_on or backwards_forwards_on parameters must be True")
+
+    if backwards_forwards_on == True and forwards_backwards_on == True:
+        adj_matrix, nodedict = similarity_matrix_sparse(G, similarity="intersection", neighbours="both",
+                                                        with_replacement=with_replacement)
+
+    elif backwards_forwards_on == True and forwards_backwards_on == False:
+        # use in-degree quality with backwards-forwards step (predecessors)
+        adj_matrix, nodedict = similarity_matrix_sparse(G, similarity="intersection", neighbours="predecessors",
+                                                        with_replacement=with_replacement)
+    elif forwards_backwards_on == True and backwards_forwards_on == False:
+        # use out-degree quality with forwards-backwards step (successors)
+
+        adj_matrix, nodedict = similarity_matrix_sparse(G, similarity="intersection", neighbours="successors",
+                                                        with_replacement=with_replacement)
+    Q = Quality_matrix(nodedict, adj_matrix, Lambda, with_replacement)
+    Q_method = Q.delta_strength_quality_unnormalised
+    Q_total = Q.total_strength_quality_unnormalised
+
+    number_of_nodes = G.number_of_nodes()
+
+    if max_number_sweeps is None or max_number_sweeps < 1:
+        max_number_sweeps = number_of_nodes
+    if random_on:
+        random.seed(seed)
+    # set up partition node dictionaries
+    # These play the role of induced graphs
+    # first place each node into its own partition
+    node_to_partition_label = {}
+    partition_label_to_nodes = {}
+    next_partition_label = 0
+
+    for n in G:
+        node_to_partition_label[n] = next_partition_label
+        partition_label_to_nodes[next_partition_label] = {n}
+        next_partition_label += 1
+    moved = True
+    number_sweeps = 0
+
+    if Q_check_on:
+        Q_total_current = Q_total(partition_label_to_nodes.values())
+    count = 0
+
+    while moved == True and number_sweeps < max_number_sweeps:
+        # Start of one sweep through all current partitions.
+        # Check every partition ac in turn, doing the best merge you can for each partition ac.
+        # Note the partition ac under study will be removed in the merge.
+        # That means the list of partition labels will be altered and therefore
+        # this list can not be used as a list of partition labels in the ac loop.
+        # For that reason we need a deep copy of the current list of partition labels
+        # which we get from the keys of the partition label to node set dictionary
+        # Conveniently we can shuffle the list used in teh ac loop if we want to randomise the greedy
+        # algorithm
+        number_sweeps += 1
+        number_moves = 0
+        node_list = list(G.nodes())
+        if random_on:
+            random.shuffle(node_list)
+        for n in node_list:
+            count += 1
+            # check to see if node n should be moved
+            moved = False
+            # ac is the partition containing node n
+            ac = node_to_partition_label[n]
+            # now find the contribution from Q that comes if we move n into its own partition
+            partition_ac_no_n = set(partition_label_to_nodes[ac])
+            partition_ac_no_n.discard(n)
+            if len(partition_ac_no_n) > 0:
+                delta_Q_remove_n = Q_method([n], partition_ac_no_n)
+            else:
+                delta_Q_remove_n = 0
+
+            # now find the neighbouring partitions via backwards-forward step
+            bf_nearest_neighbours_all = set()
+            if backwards_forwards_on:
+                for p in G.predecessors(n):
+                    bf_nearest_neighbours_all.update(G.successors(p))
+            if forwards_backwards_on:
+                for p in G.successors(n):
+                    bf_nearest_neighbours_all.update(G.predecessors(p))
+            bf_nearest_neighbour_partitions = set(node_to_partition_label[bf_nn] for bf_nn in bf_nearest_neighbours_all)
+            # remove current partition ac from neighbours
+            try:
+                bf_nearest_neighbour_partitions.remove(ac)
+            except KeyError:  # ac is not in set, must have no neighbours or no b-f nearest neighbours
+                pass
+
+                # dictionary from partition label to delta quality value,
+            # so delta_Q_nn[ac_nn] is change in quality if node n was to join partition ac_nn
+            delta_Q_nn = {}
+            # Loop round bf nearest neighbour partitions ac_nn.
+            # Check each ac_nn partition to make sure it is not weakly connected to partition ac
+            # then calculate the modularity change if partitions ac and ac_nn are merged
+            for ac_nn in bf_nearest_neighbour_partitions:
+                if not is_weakly_connected(G, [n], partition_label_to_nodes[ac_nn]):
+                    delta_Q_nn[ac_nn] = Q_method([n], partition_label_to_nodes[ac_nn])
+            if len(delta_Q_nn) > 0:
+                # Note nice use of operator.itemgetter to get key with largest value
+                # https://stackoverflow.com/questions/268272/getting-key-with-maximum-value-in-dictionary
+                ac_max = max(delta_Q_nn, key=delta_Q_nn.get)
+                if delta_Q_nn[ac_max] > delta_Q_remove_n and ac_max != ac:
+                    # now merge partition ac into partition ac_max
+                    number_moves += 1
+                    node_to_partition_label[n] = ac_max
+                    partition_label_to_nodes[ac_max].add(n)
+                    partition_label_to_nodes[ac].remove(n)
+                    if len(partition_label_to_nodes[ac]) == 0:  # no more elements in this partition
+                        partition_label_to_nodes.pop(ac, None)  # remove ac from this dictionary
+
+                    if Q_check_on:
+                        dQ = delta_Q_nn[ac_max] - delta_Q_remove_n
+                        Q_total_old = Q_total_current
+                        Q_total_current = Q_total(partition_label_to_nodes.values())
+
+                        _error = Q_total_current - Q_total_old - dQ
+                    moved = True
+
+                elif delta_Q_remove_n < 0:
+                    number_moves += 1
+                    node_to_partition_label[n] = next_partition_label
+                    partition_label_to_nodes[next_partition_label] = {n}
+                    next_partition_label += 1
+
+        # keeping looping through all partitions until can not merge any more
+    # keep doing new  sweeps as long as something changed
+    return node_to_partition_label, partition_label_to_nodes
+
+
+def matrix_node_recursive_antichain_partition(G, time_label='t', space_label='x',
+                                              random_on=False, seed=None, max_number_sweeps=None,
+                                              backwards_forwards_on=True, forwards_backwards_on=False,
+                                              Q_check_on=True,
+                                              plot_on=False,
+                                              filenameroot=None, extlist=['pdf'],
+                                              ScreenOn=False, Lambda=1, with_replacement=False):
+    """
+
+    Use , **kwargs in func defn and call with kawargs the dictionary for named arguments
+    used for the partition
+
+    """
+
+    result_list = list()
+
+    _matrix_node_recursive_antichain_partition_step(G,
+                                                    time_label=time_label, space_label=space_label,
+                                                    level=0,
+                                                    result_list=result_list,
+                                                    random_on=random_on,
+                                                    seed=seed,
+                                                    max_number_sweeps=max_number_sweeps,
+                                                    backwards_forwards_on=backwards_forwards_on,
+                                                    forwards_backwards_on=forwards_backwards_on,
+                                                    Q_check_on=Q_check_on,
+                                                    plot_on=plot_on,
+                                                    filenameroot=filenameroot, extlist=extlist,
+                                                    ScreenOn=ScreenOn, Lambda=Lambda, with_replacement=with_replacement)
+
+    return result_list
+
+
+def _matrix_node_recursive_antichain_partition_step(G, Lambda, with_replacement, time_label='t', space_label='x',
+                                                    level=0, result_list=None,
+                                                    random_on=False,
+                                                    seed=None, max_number_sweeps=None,
+                                                    backwards_forwards_on=True, forwards_backwards_on=False,
+                                                    Q_check_on=True,
+                                                    plot_on=False,
+                                                    filenameroot=None, extlist=['pdf'],
+                                                    ScreenOn=False):
+    # Internal routine to perform recursive version of node greedy
+    result_list.append(None)
+    node_to_partition_label, partition_label_to_nodes = node_matrix_greedy_antichain_partition(G,
+                                                                                               random_on=random_on,
+                                                                                               seed=seed,
+                                                                                               max_number_sweeps=max_number_sweeps,
+                                                                                               backwards_forwards_on=backwards_forwards_on,
+                                                                                               forwards_backwards_on=forwards_backwards_on,
+                                                                                               Q_check_on=Q_check_on,
+                                                                                               level=level,
+                                                                                               Lambda=Lambda,
+                                                                                               with_replacement=with_replacement)
+    if len(partition_label_to_nodes.keys()) == G.number_of_nodes():
+        return
+        # optional plot
+    if plot_on:
+        # ScreenOnValue=True
+        node_labels_on_value = True
+        cluster_labels_on_value = True
+        plot_dag(G, time_label=time_label, space_label=space_label,
+                 filenameroot=filenameroot + '_l{0:d}'.format(level),
+                 extlist=extlist,
+                 messageString='Plot',
+                 ScreenOn=ScreenOn,
+                 node_labels_on=node_labels_on_value,
+                 cluster_dict=node_to_partition_label,
+                 cluster_labels_on=cluster_labels_on_value,
+                 cluster_to_nodes=partition_label_to_nodes)
+
+    new_G = coarse_grain(G, node_to_partition_label, partition_label_to_nodes)
+    _matrix_node_recursive_antichain_partition_step(new_G,
+                                                    time_label=time_label, space_label=space_label,
+                                                    level=level + 1,
+                                                    result_list=result_list,
+                                                    random_on=random_on,
+                                                    seed=seed,
+                                                    max_number_sweeps=max_number_sweeps,
+                                                    backwards_forwards_on=backwards_forwards_on,
+                                                    forwards_backwards_on=forwards_backwards_on,
+                                                    Q_check_on=Q_check_on,
+                                                    plot_on=plot_on,
+                                                    filenameroot=filenameroot,
+                                                    extlist=extlist,
+                                                    ScreenOn=ScreenOn,
+                                                    Lambda=Lambda,
+                                                    with_replacement=with_replacement)
+    result_list[level] = {'level': level, 'n_to_p': node_to_partition_label, 'p_to_n': partition_label_to_nodes}
+    return
diff --git a/cdlib/algorithms/internal/belief_prop.py b/cdlib/algorithms/internal/belief_prop.py
new file mode 100644
index 00000000..eafa73d5
--- /dev/null
+++ b/cdlib/algorithms/internal/belief_prop.py
@@ -0,0 +1,270 @@
+import numpy as np
+import scipy.sparse as sp
+import networkx as nx
+from collections import defaultdict
+import math
+
+__all__ = ["detect_belief_communities"]
+
+
+def get_degree_vector(g: nx.Graph):
+    # compute the vector of node degrees
+    return sp.csr_matrix(np.array([g.degree()[i] for i in range(g.number_of_nodes())]))
+
+
+def init_beliefs(q: int, g: nx.Graph):
+    # initialize the beliefs
+
+    n_n = g.number_of_nodes()
+    b = sp.random(n_n, q, density=1, data_rvs=lambda x: np.random.random(x), format='csr')
+    b = sp.csr_matrix(b/b.sum(axis=1))
+
+    return b
+
+
+def init_messages(q: int, g: nx.Graph):
+    # initialize the messages
+
+    n_n = g.number_of_nodes()
+    messages = sp.dok_matrix((n_n*n_n, q))
+
+    for i in range(q):
+        for node in g.nodes:
+            for neighbor in g.neighbors(node):
+                messages[node*n_n + neighbor, i] = np.random.random()
+
+    sum = messages.sum(axis=1).transpose().tolist()[0]
+    norm = sp.diags(sum, format='csr')
+    norm[norm.nonzero()] = 1/norm[norm.nonzero()]
+    messages = messages.tocsr(copy=True)
+
+    return norm.dot(messages)
+
+
+def compute_theta(beliefs: sp.csr_matrix, degrees: sp.csr_matrix):
+    # compute_theta(beliefs, degrees)[t,0] returns theta of community t
+    return degrees.dot(beliefs)
+
+
+def update_matrix(g: nx.Graph):
+    # compute the matrix needed to update the messages
+
+    n_n = g.number_of_nodes()
+    update_mat = sp.dok_matrix((n_n*n_n, n_n*n_n), dtype=np.int8)
+    for node in g.nodes:
+        for neighbor in g.neighbors(node):
+            for node_neighbor in g.neighbors(node):
+                if node_neighbor != neighbor:
+                    update_mat[node*n_n+neighbor, node_neighbor*n_n + node] = 1
+
+    update_mat = update_mat.tocsr(copy=True)
+
+    return update_mat
+
+
+def belief_matrix(g: nx.Graph):
+    # comupte the matrix needed to calculate the beliefs
+
+    n_n = g.number_of_nodes()
+    update_mat = sp.dok_matrix((n_n, n_n * n_n), dtype=np.int8)
+    for node in g.nodes:
+        for neighbor in g.neighbors(node):
+                update_mat[node, neighbor*n_n + node] = 1
+
+    update_mat = update_mat.tocsr(copy=True)
+
+    return update_mat
+
+
+def get_external_field_beliefs(theta: sp.csr_matrix, g: nx.Graph, beta: float):
+    # compute the external field based on theta for the computation of the beliefs
+
+    m = g.number_of_edges()
+
+    degrees = get_degree_vector(g)
+
+    ext_field = -(beta/(2*m))*degrees.transpose().dot(theta)
+
+    return ext_field
+
+
+def external_field_update_matrix(g: nx.Graph, beta: float, q: int):
+    # compute the matrix needed to update the external field
+
+    n_n = g.number_of_nodes()
+
+    m = g.number_of_edges()
+
+    ext_field_update_matrix = sp.dok_matrix((n_n*n_n, n_n))
+
+    for i in range(q):
+        for node in g.nodes:
+            for neighbor in g.neighbors(node):
+                ext_field_update_matrix[(node)*n_n + neighbor, neighbor] = -((beta*g.degree()[node])/(2*m))
+
+    ext_field_update_matrix = ext_field_update_matrix.tocsr()
+
+    return ext_field_update_matrix
+
+
+def get_external_field(theta: sp.csr_matrix, ext_update: sp.csr_matrix):
+    # update the external field for the message update
+
+    n_n = ext_update.shape[1]
+
+    ones_vec = sp.csr_matrix(np.ones((n_n, 1)))
+
+    theta_transformed = ones_vec*theta
+
+    ext_field = ext_update*theta_transformed
+
+    return ext_field
+
+
+def run_bp_community_detection(g: nx.Graph, q: int = 2, beta: float = 1.012, max_it: int = 30, eps: float = 0.0001,
+                                                                                        reruns_if_not_conv: int = 20):
+    # run belief propagation community detection with given parameters
+
+    n_n = g.number_of_nodes()
+
+    # building update_matrix, belief_update_matrix, degree_vector
+    update_mat = update_matrix(g)
+    belief_update_mat = belief_matrix(g)
+    degrees = get_degree_vector(g)
+
+    # building external_field_update_matrix
+    ext_field_update_matrix = external_field_update_matrix(g=g, beta=beta, q=q)
+
+    converged = False
+    runs = 0
+    for i in range(reruns_if_not_conv):
+
+        if converged:
+            break
+
+        runs = runs+1
+
+        # initializing beliefs and messages
+        beliefs = init_beliefs(q, g)
+        messages = init_messages(q, g)
+
+        # building a vector with ones at certain positions, needed for computation
+        ones_vector = sp.dok_matrix((n_n * n_n, q))
+        ones_vector[messages.nonzero()] = 1
+        ones_vector = ones_vector.tocsr(copy=True)
+
+        # print('Run: ', i+1)
+
+        belief_diff = 1
+
+        iterations = 0
+        for j in range(max_it):
+            iterations = iterations+1
+            if belief_diff < eps:
+                converged = True
+                break
+
+            theta = compute_theta(beliefs, degrees)
+
+            # update beliefs with updated messages
+            new_messages = ones_vector + (messages * (np.exp(beta) - 1))
+            new_messages.data = np.log(new_messages.data)
+
+            updated_beliefs = belief_update_mat.dot(new_messages)
+            old_beliefs = beliefs.copy()
+
+            beliefs = updated_beliefs + get_external_field_beliefs(theta=theta, g=g, beta=beta)
+            beliefs.data = np.exp(beliefs.data)
+
+            b_sum = beliefs.sum(axis=1).transpose().tolist()[0]
+            b_norm = sp.diags(b_sum, format='csr')
+            b_norm.data = 1 / b_norm.data
+            beliefs = b_norm.dot(beliefs)
+            beliefs = beliefs.tocsr(copy=True)
+
+            # update messages
+
+            external_field = get_external_field(theta=theta, ext_update=ext_field_update_matrix)
+
+            updated_messages = update_mat.dot(new_messages)
+
+            messages = external_field + updated_messages
+
+            messages.data = np.exp(messages.data)
+
+            m_sum = messages.sum(axis=1).transpose().tolist()[0]
+            m_norm = sp.diags(m_sum, format='csr')
+            m_norm.data = 1 / m_norm.data
+            messages = m_norm.dot(messages)
+            messages = messages.tocsr(copy=True)
+
+            belief_diff = np.linalg.norm(beliefs.todense() - old_beliefs)
+
+    detected_communities = [np.argmax(beliefs[i, :]) for i in range(beliefs.shape[0])]
+
+    return beliefs, detected_communities, runs, iterations, converged
+
+
+def compute_opt_beta(q: int, c: int):
+    # compute the optimal value for the parameter beta
+
+    return math.log(q/(math.sqrt(c)-1) + 1)
+
+
+def detect_belief_communities(g: nx.Graph, max_it: int = 100, eps: float = 0.0001, reruns_if_not_conv: int = 5,
+                              threshold: float = 0.005, q_max: int = 7):
+
+    from networkx.algorithms.community.quality import modularity
+
+    # determine number of optimal communities and run community detection for a given network
+    # The nodes have to be labeled form 0 to n
+
+    modularity_0 = 0
+    modularity_1 = threshold
+    q = 1
+    c = 2*g.number_of_edges()/g.number_of_nodes()
+    partition = ()
+
+    # run belief propagation community detection with increasing number of communities until the modularity of the
+    # detected partition does not increase more then given threshold
+
+    while modularity_1 - modularity_0 >= threshold:
+        old_partition = partition
+        beta = compute_opt_beta(q,c)
+        modularity_0 = modularity_1
+        partition = run_bp_community_detection(g=g, q=q, beta=beta, max_it=max_it, eps=eps,
+                                               reruns_if_not_conv=reruns_if_not_conv)
+
+        modularity_1 = modularity(g, [{i for i in range(len(partition[1])) if partition[1][i] == j}
+                                      for j in set(partition[1])])
+
+        if not partition[4]:
+            curr_partition = partition
+            partition = old_partition
+            modularity_1 = modularity_0
+            modularity_0 = modularity_1 - threshold
+
+        if q == 1:
+            modularity_0 = modularity_1 - threshold
+
+        if q > q_max:
+            break
+
+        q = q + 1
+
+    if len(old_partition) != 0:
+        return prepare_coms(g,old_partition[1])
+    else:
+        return prepare_coms(g, curr_partition[1])
+
+
+def prepare_coms(g, cms):
+    ass = list(zip(g.nodes(), cms))
+
+    coms = defaultdict(list)
+    for i in ass:
+        coms[i[1]].append(i[0])
+
+    return list(coms.values())
+
+
diff --git a/cdlib/algorithms/internal/ga.py b/cdlib/algorithms/internal/ga.py
new file mode 100644
index 00000000..b57164a8
--- /dev/null
+++ b/cdlib/algorithms/internal/ga.py
@@ -0,0 +1,148 @@
+import networkx as nx
+import numpy as np
+import pandas as pd
+
+__all__ = ["ga_community_detection"]
+
+
+def ga_community_detection(graph, population=300, generation=30, r=1.5):
+    Adj = nx.adjacency_matrix(graph)
+    nodes_length = len(graph.nodes())
+
+    d = {"chrom": [__generate_chrom(nodes_length, Adj) for n in range(population)]}
+    dframe = pd.DataFrame(data=d)
+    dframe["subsets"] = dframe["chrom"].apply(__find_subsets)
+    dframe["community_score"] = dframe.apply(lambda x: __community_score(x["chrom"], x["subsets"], r, Adj), axis=1)
+
+    gen = 0
+    population_count = population
+    while gen < generation:
+        for i in range(int(np.floor(population / 10))):
+            elites = dframe.sort_values("community_score", ascending=True)[int(np.floor(population / 10)):]
+            p1 = __roulette_selection(elites)
+            p2 = __roulette_selection(elites)
+            child = __uniform_crossover(dframe["chrom"][p1], dframe["chrom"][p2], 0.8)
+            if len(child) == 0:
+                continue
+            child = __mutation(child, Adj, 0.2)
+            child_subsets = __find_subsets(child)
+            child_cs = __community_score(child, child_subsets, r, Adj)
+            dframe.loc[population_count] = [child, child_subsets, child_cs]
+            population_count += 1
+        dfsorted = dframe.sort_values("community_score", ascending=False)
+        to_drop = dfsorted.index[population:]
+        dframe.drop(to_drop, inplace=True)
+        gen += 1
+    sorted_df = dframe.sort_values("community_score", ascending=False).index[0]
+
+    nodes_subsets = dframe["subsets"][sorted_df]
+    nodes_list = list(graph.nodes())
+    result = []
+    for subs in nodes_subsets:
+        subset = []
+        for n in subs:
+            subset.append(nodes_list[n])
+        result.append(subset)
+    return result
+
+
+def __generate_chrom(nodes_length, Adj):
+    chrom = np.array([], dtype=int)
+    for x in range(nodes_length):
+        rand = np.random.randint(0, nodes_length)
+        while Adj[x, rand] != 1:
+            rand = np.random.randint(0, nodes_length)
+        chrom = np.append(chrom, rand)
+    return chrom
+
+
+def __merge_subsets(sub):
+    arr = []
+    to_skip = []
+    for s in range(len(sub)):
+        if sub[s] not in to_skip:
+            new = sub[s]
+            for x in sub:
+                if sub[s] & x:
+                    new = new | x
+                    to_skip.append(x)
+            arr.append(new)
+    return arr
+
+
+def __find_subsets(chrom):
+    sub = [{x, chrom[x]} for x in range(len(chrom))]
+    result = sub
+    i = 0
+    while i < len(result):
+        candidate = __merge_subsets(result)
+        if candidate != result:
+            result = candidate
+        else:
+            break
+        # result = candidate
+        i += 1
+    return result
+
+
+def __community_score(chrom, subsets, r, Adj):
+    matrix = Adj.toarray()
+    CS = 0
+    for s in subsets:
+        submatrix = np.zeros((len(chrom), len(chrom)), dtype=int)
+        for i in s:
+            for j in s:
+                submatrix[i][j] = matrix[i][j]
+        M = 0
+        v = 0
+        for row in list(s):
+            row_mean = np.sum(submatrix[row]) / len(s)
+            v += np.sum(submatrix[row])
+            M += (row_mean ** r) / len(s)
+        CS += M * v
+    return CS
+
+
+def __roulette_selection(df_elites):
+    prob = np.random.random_sample()
+    sum_cs = np.sum(df_elites["community_score"])
+    x = 0
+    chosen = 0
+    for i in df_elites.index:
+        x += df_elites["community_score"][i]
+        X = x / sum_cs
+        if prob < X:
+            chosen = i
+            break
+    return chosen
+
+
+def __uniform_crossover(parent_1, parent_2, crossover_rate):
+    if np.random.random_sample() < crossover_rate:
+        length = len(parent_1)
+        mask = np.random.randint(2, size=length)
+        child = np.zeros(length, dtype=int)
+        for i in range(len(mask)):
+            if mask[i] == 1:
+                child[i] = parent_1[i]
+            else:
+                child[i] = parent_2[i]
+        return child
+    else:
+        return np.array([])
+
+
+def __mutation(chrom, Adj, mutation_rate):
+    if np.random.random_sample() < mutation_rate:
+        chrom = chrom
+        neighbor = []
+        while len(neighbor) < 2:
+            mutant = np.random.randint(1, len(chrom))
+            row = Adj[mutant].toarray()[0]
+            neighbor = [i for i in range(len(row)) if row[i] == 1]
+            if len(neighbor) > 1:
+                neighbor.remove(chrom[mutant])
+                to_change = int(np.floor(np.random.random_sample() * (len(neighbor))))
+                chrom[mutant] = neighbor[to_change]
+                neighbor.append(chrom[mutant])
+    return chrom
diff --git a/cdlib/algorithms/internal/weightedCommunity.py b/cdlib/algorithms/internal/weightedCommunity.py
new file mode 100644
index 00000000..d112d245
--- /dev/null
+++ b/cdlib/algorithms/internal/weightedCommunity.py
@@ -0,0 +1,218 @@
+__authors__ = ['Marco Cardia <cardiamc@gmail.com>', 'Francesco Sabiu <fsabiu@gmail.com>']
+
+
+class weightedCommunity(object):
+
+    def __init__(self, G, min_bel_degree, threshold_bel_degree, weightName='weight', save=False,
+                 outfile_name="weighted_communities.txt"):
+        """
+        Constructor
+
+        :param G: an igraph.Graph object
+        :param min_bel_degree: the tolerance, in terms of beloging degree, required in order to add a node in a community
+        :param threshold_bel_degree: the tolerance, in terms of beloging degree, required in order to add a node in a 'NLU' community
+        :param weightName: Name of the attribute containing the weights
+        """
+        self.G = G
+        self.N = len(self.G.vs)
+        self.L = len(self.G.es)
+        self.min_bel_degree = min_bel_degree
+        self.threshold_bel_degree = threshold_bel_degree
+        self.weightName = weightName
+        self.save = save
+        self.outfile_name = outfile_name
+        self.communities = []
+        self.strengths = []
+        self.T = 0
+
+        # Labels
+        self.G.vs['label'] = ['F'] * self.N
+
+    # Strength of a node
+    def strength(self, node):
+        strength = 0
+        for j in self.G.neighbors(node):
+            strength += self.G.es[self.G.get_eid(node, j)][self.weightName]
+        return strength
+
+    # Belonging degree
+    def belonging_degree(self, node, community):
+        strength = 0
+        strength = sum(
+            [self.G.es[self.G.get_eid(node, i)][self.weightName] for i in self.G.neighbors(node) if i in community])
+        return strength / self.strength(node)
+
+    # Modularity of a graph
+    def modularity(self):
+        Q_new = 0
+        for c in self.communities:
+            pair_components = list(itertools.combinations(c, 2))
+            pair_components = sample(pair_components, 1000)  # Reducing execution time!
+            for pair_c in pair_components:
+                e_id = self.G.get_eid(pair_c[0], pair_c[1], error=False)
+                weight = self.G.es[e_id][self.weightName] if e_id != -1 else 0
+                Q_new += self.belonging_degree(pair_c[0], c) * self.belonging_degree(pair_c[1], c) * (
+                            weight - self.strength(pair_c[0]) * self.strength(pair_c[1]) / (2 * self.L))
+
+        Q_new = Q_new / (2 * self.L)
+
+        return Q_new
+
+    def allStrengths(self):
+        for node in self.G.vs:
+            self.strengths.append(self.strength(node))
+
+        # Updating graph
+        self.G.vs['strength'] = self.strengths
+
+    def strongestNotLabeled(self):
+        # Getting indices labeled with 'F'
+        indices = [i for i in range(len(self.G.vs)) if self.G.vs[i]['label'] == 'F']
+
+        # Getting strengths for such items
+        ss = {}
+        for i in indices:
+            ss[self.strengths[i]] = i
+
+        # Returning (one of) the strongest node index(es)
+        return ss[max(ss.keys())]
+
+    def nodesRemotion(self, c, min_bel_degree):
+        old_c = 0
+        while (old_c != len(c)):
+            old_c = 0
+            c_list = [el for el in c]
+            for node in c_list:
+                old_c = len(c_list)
+                if (self.belonging_degree(node, c) < min_bel_degree):
+                    try:
+                        c.remove(node)
+                    except:
+                        pass
+        return c
+
+    def initialCommunityDetection(self):
+        c = set()
+        # Getting strongest labeled with 'F'
+        strongest = self.strongestNotLabeled()
+
+        # Adding strongest to community
+        c.add(strongest)
+        self.G.vs[strongest]['label'] = 'T'
+
+        # Adding strongest's neighbors
+        for neighbor in self.G.neighbors(strongest):
+            c.add(neighbor)
+
+        # Until convergence
+        # if belonging degree of the community nodes is lower than min_bel_degree, we remove them.
+        c = self.nodesRemotion(c, self.min_bel_degree)
+
+        # If commuty is empty, add strongest to it
+        if len(c) == 0:
+            c.add(strongest)
+
+        return c
+
+    def find_initial_community_neighbors(self, c):
+        ### Finding initial community neighbors
+        c_neighbors = set()
+
+        for node in c:
+            for nb in self.G.neighbors(node):
+                c_neighbors.add(nb)
+        return c_neighbors
+
+    def define_nu_nlu(self, c, c_neighbors):
+        """### Find Nu (b > 0.5) and Nlu (0.4 < b < 0.5) starting from the set of neighbors"""
+        nu = set()
+        nlu = set()
+
+        for nb in c_neighbors:
+            # Nu set: neighbor with belonging degree >= min_bel_degree
+            if (self.belonging_degree(nb, c) >= self.min_bel_degree):
+                nu.add(nb)
+            # Nlu set neighbor with threshold < belonging degree < min_bel_degree
+            elif (self.belonging_degree(nb, c) > self.threshold_bel_degree):
+                nlu.add(nb)
+
+        return nu, nlu
+
+    def add_nlu_to_community(self, c, nlu):
+        while len(nlu) > 0:
+            Q = self.modularity()
+
+            candidate = nlu.pop()
+            c.add(candidate)
+            Q_new = self.modularity()
+            if (Q_new > Q):
+                Q = Q_new
+            else:
+                c.remove(candidate)
+        return c
+
+    def expandCommunity(self, c):
+        """##  Expanding the community
+        """
+        c_old = 0
+        nu, nlu = set(), set()
+
+        while (c_old != len(c)):  # While it adds nodes belonging to nlu in the community
+            while (c_old != len(c)):  # While it adds nodes belonging to nu in the community
+                c_neighbors = self.find_initial_community_neighbors(c)
+
+                # Computing nu and nlu
+                nu, nlu = self.define_nu_nlu(c, c_neighbors)
+
+                # Add Nu nodes to initial community and return to neighbors search
+                c_old = len(c)
+                c = c.union(nu)
+
+                # Asserting size
+                if (len(c) != len(c) + len(nu) - len(c.intersection(nu))):
+                    print('Incompatible sizes')
+                    return -1
+
+            # Add the nodes of Nlu to the community only if their presence increases the community modularity Q0
+            c_old = len(c)
+            c = self.add_nlu_to_community(c, nlu)
+
+        # Mark the community nodes with 'T' label
+        self.G.vs['label'] = [v['label'] if not v.index in c else 'T' for v in self.G.vs]
+
+        # Updating T label counter
+        self.T = len([v for v in self.G.vs['label'] if v == 'T'])
+
+        return c
+
+    def getCommunities(self):
+        return self.communities
+
+    def computeCommunity(self):
+        if (len(self.communities) == 0):  # Just the 1st time
+            # Strenghts
+            self.allStrengths()
+
+        # Initial community detection
+        c = self.initialCommunityDetection()
+
+        # Community expansion
+        c = self.expandCommunity(c)
+
+        self.communities.append(c)
+
+    def computeCommunities(self):
+        while (self.N != self.T):
+            self.computeCommunity()
+
+        # output communities
+        if self.save and len(self.getCommunities()) > 0:
+            out_file_com = open(self.outfile_name, "w")
+
+            for cid, c in enumerate(self.getCommunities()):
+                out_file_com.write("%d\t%s\n" % (cid, str(c)))
+
+            out_file_com.flush()
+            out_file_com.close()
+
+        return self.getCommunities()
diff --git a/cdlib/algorithms/overlapping_partition.py b/cdlib/algorithms/overlapping_partition.py
index 4af6390e..50500854 100644
--- a/cdlib/algorithms/overlapping_partition.py
+++ b/cdlib/algorithms/overlapping_partition.py
@@ -23,11 +23,12 @@
 from cdlib.algorithms.internal.multicom import MultiCom
 from cdlib.algorithms.internal.PercoMCV import percoMVC
 from karateclub import DANMF, EgoNetSplitter, NNSED, MNMF, BigClam
+from cdlib.algorithms.internal.weightedCommunity import weightedCommunity
 from ASLPAw_package import ASLPAw
 
 __all__ = ["ego_networks", "demon", "angel", "node_perception", "overlapping_seed_set_expansion", "kclique", "lfm",
            "lais2", "congo", "conga", "lemon", "slpa", "multicom", "big_clam", "danmf", "egonet_splitter", "nnsed",
-           "nmnf", "aslpaw", "percomvc"]
+           "nmnf", "aslpaw", "percomvc", "wCommunity"]
 
 
 def ego_networks(g_original, level=1):
@@ -833,3 +834,45 @@ def percomvc(g_original):
     communities = percoMVC(g)
 
     return NodeClustering(communities, g_original, "PercoMVC", method_parameters={}, overlap=True)
+
+def wCommunity(g_original, min_bel_degree=0.7, threshold_bel_degree=0.7, weightName="weight"):
+    """
+        Algorithm to identify overlapping communities in weighted graphs
+
+        :param g_original: a networkx/igraph object
+        :param min_bel_degree: the tolerance, in terms of beloging degree, required in order to add a node in a community
+        :param threshold_bel_degree: the tolerance, in terms of beloging degree, required in order to add a node in a 'NLU' community
+        :param weightName: name of the edge attribute containing the weights
+        :return: NodeClustering object
+
+        :Example:
+
+        >>> from cdlib import algorithms
+        >>> import networkx as nx
+        >>> G = nx.karate_club_graph()
+        >>> nx.set_edge_attributes(G, values=1, name='weight')
+        >>> coms = algorithms.wCommunity(G, min_bel_degree=0.6, threshold_bel_degree=0.6)
+
+        :References:
+
+        Chen, D., Shang, M., Lv, Z., & Fu, Y. (2010). Detecting overlapping communities of weighted networks via a local algorithm. Physica A: Statistical Mechanics and its Applications, 389(19), 4177-4187.
+
+        .. note:: Implementation provided by Marco Cardia <cardiamc@gmail.com> and Francesco Sabiu <fsabiu@gmail.com> (Computer Science Dept., University of Pisa, Italy)
+        """
+
+    if ig is None:
+        raise ModuleNotFoundError("Optional dependency not satisfied: install igraph to use the selected feature.")
+
+    g = convert_graph_formats(g_original, ig.Graph)
+    # Initialization
+    comm = weightedCommunity(g, min_bel_degree=min_bel_degree, threshold_bel_degree=threshold_bel_degree,
+                             weightName=weightName)
+    # Community computation
+    comm.computeCommunities()
+    # Result
+    coms = comm.getCommunities()
+    coms = [list(c) for c in coms]
+    return NodeClustering(coms, g_original, "wCommunity",
+                          method_parameters={"min_bel_degree": min_bel_degree,
+                                             "threshold_bel_degree": threshold_bel_degree, 'weightName': weightName},
+                          overlap=True)
\ No newline at end of file
diff --git a/cdlib/classes/attr_node_clustering.py b/cdlib/classes/attr_node_clustering.py
index 6dc48679..10df769d 100644
--- a/cdlib/classes/attr_node_clustering.py
+++ b/cdlib/classes/attr_node_clustering.py
@@ -14,7 +14,7 @@ class AttrNodeClustering(NodeClustering):
       :param overlap: boolean, whether the partition is overlapping or not
       """
 
-    def __init__(self, communities, graph, method_name, coms_labels=None, method_parameters=None, overlap=False):
+    def __init__(self, communities, graph, method_name="", coms_labels=None, method_parameters=None, overlap=False):
         super().__init__(communities, graph, method_name, method_parameters, overlap)
         self.coms_labels = coms_labels
 
diff --git a/cdlib/classes/bipartite_node_clustering.py b/cdlib/classes/bipartite_node_clustering.py
index 3f2b18e9..65dea337 100644
--- a/cdlib/classes/bipartite_node_clustering.py
+++ b/cdlib/classes/bipartite_node_clustering.py
@@ -13,7 +13,7 @@ class BiNodeClustering(NodeClustering):
       :param overlap: boolean, whether the partition is overlapping or not
       """
 
-    def __init__(self, left_communities, right_communities, graph, method_name, method_parameters=None, overlap=False):
+    def __init__(self, left_communities, right_communities, graph, method_name="", method_parameters=None, overlap=False):
         self.left_communities = left_communities
         self.right_communities = right_communities
         super().__init__(self.left_communities + self.right_communities, graph, method_name, method_parameters, overlap)
diff --git a/cdlib/classes/clustering.py b/cdlib/classes/clustering.py
index 7b18a429..5b6886d5 100644
--- a/cdlib/classes/clustering.py
+++ b/cdlib/classes/clustering.py
@@ -19,7 +19,7 @@ def __convert_back_to_original_nodes_names_if_needed(communities):
                 return to_return
         return communities
 
-    def __init__(self, communities, graph, method_name, method_parameters=None, overlap=False):
+    def __init__(self, communities, graph, method_name="", method_parameters=None, overlap=False):
         """
         Communities representation.
 
diff --git a/cdlib/classes/edge_clustering.py b/cdlib/classes/edge_clustering.py
index c8c6d94c..6b57bd03 100644
--- a/cdlib/classes/edge_clustering.py
+++ b/cdlib/classes/edge_clustering.py
@@ -18,7 +18,7 @@ class EdgeClustering(Clustering):
     :param overlap: boolean, whether the partition is overlapping or not
     """
 
-    def __init__(self, communities, graph, method_name, method_parameters=None, overlap=False):
+    def __init__(self, communities, graph, method_name="", method_parameters=None, overlap=False):
         super().__init__(communities, graph, method_name, method_parameters, overlap)
         
         if graph is not None:
diff --git a/cdlib/classes/fuzzy_node_clustering.py b/cdlib/classes/fuzzy_node_clustering.py
index 33800691..0608209b 100644
--- a/cdlib/classes/fuzzy_node_clustering.py
+++ b/cdlib/classes/fuzzy_node_clustering.py
@@ -13,7 +13,7 @@ class FuzzyNodeClustering(NodeClustering):
     :param overlap: boolean, whether the partition is overlapping or not
     """
 
-    def __init__(self, communities, node_allocation, graph, method_name, method_parameters=None, overlap=False):
+    def __init__(self, communities, node_allocation, graph, method_name="", method_parameters=None, overlap=False):
         super().__init__(communities, graph, method_name, method_parameters, overlap)
         self.allocation_matrix = node_allocation
 
diff --git a/cdlib/classes/node_clustering.py b/cdlib/classes/node_clustering.py
index cb25c64f..8ba929be 100644
--- a/cdlib/classes/node_clustering.py
+++ b/cdlib/classes/node_clustering.py
@@ -20,7 +20,7 @@ class NodeClustering(Clustering):
     :param overlap: boolean, whether the partition is overlapping or not
     """
 
-    def __init__(self, communities, graph, method_name, method_parameters=None, overlap=False):
+    def __init__(self, communities, graph, method_name="", method_parameters=None, overlap=False):
         super().__init__(communities, graph, method_name, method_parameters, overlap)
 
         if graph is not None:
diff --git a/cdlib/datasets/__init__.py b/cdlib/datasets/__init__.py
new file mode 100644
index 00000000..c2ef39f7
--- /dev/null
+++ b/cdlib/datasets/__init__.py
@@ -0,0 +1,1 @@
+from .remote import *
diff --git a/cdlib/datasets/remote.py b/cdlib/datasets/remote.py
new file mode 100644
index 00000000..befb1d5b
--- /dev/null
+++ b/cdlib/datasets/remote.py
@@ -0,0 +1,240 @@
+import pooch
+from cdlib.readwrite import *
+import networkx as nx
+
+try:
+    import igraph as ig
+except ModuleNotFoundError:
+    ig = None
+
+__all__ = ["available_networks", "available_ground_truths", "fetch_network_data", "fetch_ground_truth_data",
+           "fetch_network_ground_truth"]
+
+__networks = pooch.create(
+    path=pooch.os_cache("cdlib"),
+    base_url="https://github.com/GiulioRossetti/cdlib_datasets/raw/main/networks/",
+    version_dev="master",
+    env="cdlib_data_dir",
+    registry={"karate_club.csv.gz": "0bac1a017e59f505f073aef6dc1783c03826a3fd4f38ffc6a50fde582948e2f2",
+              "dblp.csv.gz": "6b64a23e60083fa52d64320a2cd4366ff1b33d8a89ac4fa1b30f159e88c1730c",
+              "amazon.csv.gz": "aea015386f62b21ba31e097553a298fb3df610ec8dc722addb06a1a59e646cd3",
+              "youtube.csv.gz": "dad87a04c648b31565448e7bef62ace569157aa58f7d2a0bb435eb490d0bf33e",
+              "LFR_N100000_ad5_mc50_mu0.1.csv.gz": "54eb36e619fa7f6ff2dcbb4e5a43ee9885c8f4d9634fd9e48cdef3de963581ae",
+              "LFR_N100000_ad5_mc50_mu0.2.csv.gz": "486e2cf7ce009b32ad21b4d290d6b1c2fc093a614deed101acea787e759d1b32",
+              "LFR_N100000_ad5_mc50_mu0.3.csv.gz": "41f875cd0db105ee90b4f4f4e2d256dd7230f30916f498faf9e36ef724ec71f3",
+              "LFR_N100000_ad5_mc50_mu0.4.csv.gz": "7eb1023a02574459b3649aefe8b3661476104e00116699b15575eca31bbd6ba2",
+              "LFR_N100000_ad5_mc50_mu0.5.csv.gz": "e398d2ef53d3fb3f7f54aa3756deb1333610ce37fe5ada7ac3689d4bd0a77bc6",
+              "LFR_N100000_ad5_mc50_mu0.6.csv.gz": "7ab7fd9405260c0561806cb1f23c2eb1e420b1219baa69bf9ffa7183b36fd4d3",
+              "LFR_N100000_ad5_mc50_mu0.7.csv.gz": "d4a9e94f16823706a9193f5f7053fca33a8bc9ab2e3e5f68c094deb02bbb86c3",
+              "LFR_N100000_ad5_mc50_mu0.8.csv.gz": "875147fd1d9ae14770b3c0e461223e5675712d06b57f591c77113dc443177f7d",
+              "LFR_N100000_ad5_mc50_mu0.9.csv.gz": "ec6b9fbad572bc806239cef31f77c50afd0b10d2acdf5a047981f4756409f5b7",
+              "LFR_N10000_ad5_mc50_mu0.1.csv.gz": "7ea99a90647402b55e8f87bb7dcad60875b5ec17c84c61b0b73ce6a3988137bf",
+              "LFR_N10000_ad5_mc50_mu0.2.csv.gz": "d477a10a8be4cb7c8dd625bd2784af14f9b7aa0de63fc116c49c1effea017ba2",
+              "LFR_N10000_ad5_mc50_mu0.3.csv.gz": "8572198101891bf69365fdcf42c59beec6798371b5f8ae47d6cc31aa4f3178ab",
+              "LFR_N10000_ad5_mc50_mu0.4.csv.gz": "288f45fd4626a58d27cebaaca903ab5ca3d8eaae9e393df276097f3da4244e8a",
+              "LFR_N10000_ad5_mc50_mu0.5.csv.gz": "83ba2ce5565542cc0726f254aeca28ca60ede1a237b1dd0e8c7a2e992f5b4e16",
+              "LFR_N10000_ad5_mc50_mu0.6.csv.gz": "1dda29356ef25f01088ba03167890c2af33a6c13b519d778d32021154ae63eda",
+              "LFR_N10000_ad5_mc50_mu0.7.csv.gz": "ba7b82d01e0c96e1b83637a44104d51d4fcee2f3ff6e0c6c12e9cde0f82538a6",
+              "LFR_N10000_ad5_mc50_mu0.8.csv.gz": "3271d6c7591f92ccf8f7ec85c776e295454f282d556de63efe3f66657c4c9ec0",
+              "LFR_N10000_ad5_mc50_mu0.9.csv.gz": "0f4f127af47d7a22dd5b3a641014b90eab30f1856d397b56aa5fda766ca7e217",
+              "LFR_N1000_ad5_mc50_mu0.1.csv.gz": "a575245a1a3464da9dbebe85e3b68a391a43eaf9c14ce6777aa621483d5f58b3",
+              "LFR_N1000_ad5_mc50_mu0.2.csv.gz": "efaafd318f1835f0e58d24309d5caa472c5dca9b72282c2b0466c0c264a6f0ea",
+              "LFR_N1000_ad5_mc50_mu0.3.csv.gz": "d418d7a6dc8930b5ec7f0b33ceace4265f3077bd10272762ae1e31e506e605a8",
+              "LFR_N1000_ad5_mc50_mu0.4.csv.gz": "9ee8112441a1ee9f1733e627e9b88f5048c237c5ae479203bae00736c357175f",
+              "LFR_N1000_ad5_mc50_mu0.5.csv.gz": "09bd8a15ec08877dcc93d9c7fc6b80b9b5a04d7126a4f5eef25f9570fe8d6d75",
+              "LFR_N1000_ad5_mc50_mu0.6.csv.gz": "90d29e105c63a01743e85def469aa301d786cebd41134e2f797a612f00dd6cca",
+              "LFR_N1000_ad5_mc50_mu0.7.csv.gz": "956707249e7b1539c666c602dbba714b855884bceac1d47246c86b0666ac8afd",
+              "LFR_N1000_ad5_mc50_mu0.8.csv.gz": "f798f48acf14e7a337987cad14e774cbb6984d899e3a910da94977f0865fe4ee",
+              "LFR_N1000_ad5_mc50_mu0.9.csv.gz": "29a4001c29f9e35f1b8d52fa85410b676c655ffe10e622c70204b4325d182e83",
+              "LFR_N50000_ad5_mc50_mu0.1.csv.gz": "bebc3640db551389b6b7a819c6512fe00cee965fb59bfcd28e7246f2707ec822",
+              "LFR_N50000_ad5_mc50_mu0.2.csv.gz": "b6cafbe62d0cd64f04286d9fa29f9d8b4eb6cc788383000effa78b46ca2269db",
+              "LFR_N50000_ad5_mc50_mu0.3.csv.gz": "ada823356120f5039ee3620aefb1e9a334f8aaa6711a3150823aac3cb08e18c5",
+              "LFR_N50000_ad5_mc50_mu0.4.csv.gz": "e8062aeefc37e506d39a2622c27206179dcc71340b82b27ccd81f190ab9a719c",
+              "LFR_N50000_ad5_mc50_mu0.5.csv.gz": "fb51f02e1a47adea3bcd6d37e6dc9c330be1f6538becb1f5ff5f8c391ce4b403",
+              "LFR_N50000_ad5_mc50_mu0.6.csv.gz": "fbbcaec9fa7a762c0d62c59ca0543db3710b6ca1d8244503581b23e012c68c0e",
+              "LFR_N50000_ad5_mc50_mu0.7.csv.gz": "1f2c50098d55227cbac5f007a8814ec08d451c2eaa2e5b75ebe97f61617a8d70",
+              "LFR_N50000_ad5_mc50_mu0.8.csv.gz": "20e7b2d99633ddeac315576bdc3cb7dd94ac042d6cd622ef171c2cfe14a73f55",
+              "LFR_N50000_ad5_mc50_mu0.9.csv.gz": "e97e9686e4b5e2efdc314bf2ed2f9bc55bd29adfb32db778e301e302511324f1",
+              "LFR_N5000_ad5_mc50_mu0.1.csv.gz": "25c551db0407721897d7f3d10c087214c3593a48b5b8ce37f73fcbb61230721a",
+              "LFR_N5000_ad5_mc50_mu0.2.csv.gz": "f89486f9e7df512457bacc91595317eb10f0144cf0a58d902f915fedcd43fc35",
+              "LFR_N5000_ad5_mc50_mu0.3.csv.gz": "4df6ef918a258204e05a18efb3f6ff1876a11a66ed8c319d96e65b540b3f3f3c",
+              "LFR_N5000_ad5_mc50_mu0.4.csv.gz": "4037d7a18792d8584e1f05fca6a1f85628e12dc428d07baaebfe1d129f4084e8",
+              "LFR_N5000_ad5_mc50_mu0.5.csv.gz": "dd6efeb9ac17e845ed6ffe0488508fb847fbaf2c0b5db218a028342ea20dbf55",
+              "LFR_N5000_ad5_mc50_mu0.6.csv.gz": "b1fd8e3fa9761a0a9b61250c8b5cc4add546b9e7447c71076b7ed12e6ae837f7",
+              "LFR_N5000_ad5_mc50_mu0.7.csv.gz": "0c8fd15bf9121ed274a5da3c607b13f8637a8028ffcac42a6b045f301fc6320f",
+              "LFR_N5000_ad5_mc50_mu0.8.csv.gz": "b171f72d53950720da049d37e1f6dae3ce2f433344e0e84967826d144de0fccb",
+              "LFR_N5000_ad5_mc50_mu0.9.csv.gz": "5716355473ffb7eb9f37ea9959b05dfed6c3b910db51d71c6597bdef88584729",
+              }
+)
+
+__ground_truths = pooch.create(
+    path=pooch.os_cache("cdlib"),
+    base_url="https://github.com/GiulioRossetti/cdlib_datasets/raw/main/ground_truth/",
+    version_dev="master",
+    env="cdlib_data_dir",
+    registry={"karate_club.json.gz": "198fd42c3df9ab49e3eea5932f0d6e4cceac25db147c5108e0f8e9a4c55e11b7",
+              "dblp.json.gz": "ca7dba98bd3bdc76999fd2991d1667b7b531e8bac777986247a8dcac302c085d",
+              "amazon.json.gz": "c6a03909f2b14082523108be13616e5b24cfe830b96e416d784e90ab23d12bd7",
+              "youtube.json.gz": "affa73428b300d896944adbbf334b77370b6fc3a43f5adbf54350fd5fafaeed5",
+              "LFR_N100000_ad5_mc50_mu0.1.json.gz": "96f2022ff76448448547bbb46d0f803eee345eb96141d962de086a66b650fb81",
+              "LFR_N100000_ad5_mc50_mu0.2.json.gz": "e7e4083d0f849af9dabf87363a50e7de85ebcf68bc8f114e82a01bfe827a33aa",
+              "LFR_N100000_ad5_mc50_mu0.3.json.gz": "aa45e07b74aa57b89c17b3978b33a7c5cf36dd1e1110bc94ee563c4325f00879",
+              "LFR_N100000_ad5_mc50_mu0.4.json.gz": "60a7b1392d051a4773bfff428014e1b24111588a770dc600c7d251097c447ee3",
+              "LFR_N100000_ad5_mc50_mu0.5.json.gz": "c38a19b512ad44d308ebaa76051259f69d35e8289b3724d0c01e84db51d8802c",
+              "LFR_N100000_ad5_mc50_mu0.6.json.gz": "08ef0a2cd65ad063b7ec54c4d38f3b7e531bbc1a18a9d9e49d68121b9f0160e1",
+              "LFR_N100000_ad5_mc50_mu0.7.json.gz": "9338497d042b97d128a1df96a631fd3ad15c104469ab5c8a7e425f6a11e75e18",
+              "LFR_N100000_ad5_mc50_mu0.8.json.gz": "342062125e4c102b5e0a5e7fc9217dc68c39c002f27da6364a4f48f3fee11434",
+              "LFR_N100000_ad5_mc50_mu0.9.json.gz": "ef262d3f1944cbb4f7c5863ede9feba21d1c15e87901fbb0070edb85e8a56b0a",
+              "LFR_N10000_ad5_mc50_mu0.1.json.gz": "8c363cdc0a4816b24e7a5e6adbbfa0b398414d76d13806454955b1244f1b1fe0",
+              "LFR_N10000_ad5_mc50_mu0.2.json.gz": "f9c6b1c2345fb18ded37ded5d9348004db9aacda681f8b4551253d6962fec54d",
+              "LFR_N10000_ad5_mc50_mu0.3.json.gz": "43104baaf276c034b597e7ca79fcb3224828ff942fa93178e209e8dc0853cbcd",
+              "LFR_N10000_ad5_mc50_mu0.4.json.gz": "b0eacf94b9b74a9fe405b3b2f017c4b04a1403f848238dda19c20cb67bdbbd36",
+              "LFR_N10000_ad5_mc50_mu0.5.json.gz": "13d0d4a27708ea6c19244ce21d34b07d8c5338d32e4e40bb7af1bc8ae0b90cf8",
+              "LFR_N10000_ad5_mc50_mu0.6.json.gz": "7f3b760490832644bfa05fd901ede1c799dfa754950ab7a4143871f2c042ca59",
+              "LFR_N10000_ad5_mc50_mu0.7.json.gz": "50082826be9e123f4ebc083dd4449eb327fe0e9726a356839bd4be03e7e5f0ce",
+              "LFR_N10000_ad5_mc50_mu0.8.json.gz": "6dba81412195f54f58a18f51e095918ddffc80c5feb5b91e7f3abbbe41f886c7",
+              "LFR_N10000_ad5_mc50_mu0.9.json.gz": "6447a14df7c4d9e8d4d9c8670178428066e3ee7b0ac0359d733a000d2adb4983",
+              "LFR_N1000_ad5_mc50_mu0.1.json.gz": "07e297bc63793c297076e15587196f6311a583e32d6fa58469c88471062ab715",
+              "LFR_N1000_ad5_mc50_mu0.2.json.gz": "8d75a29a87d075b1460fcba657bdd2edf10dfca96a3ea240cc6f1c266cbb914c",
+              "LFR_N1000_ad5_mc50_mu0.3.json.gz": "e41f900abce6352f1ea8c4e21c8ef27456d397d2af0e4a15c016256971c3dbea",
+              "LFR_N1000_ad5_mc50_mu0.4.json.gz": "5e13918acb890a238eff549a5153da720f8d88ef295d5314777fc035e76e2cdf",
+              "LFR_N1000_ad5_mc50_mu0.5.json.gz": "9203c87763aca467cfb176f5cab5a2b09f02dc70bdd139822368155ba7589505",
+              "LFR_N1000_ad5_mc50_mu0.6.json.gz": "a6682eb003ac8091975a99929bbdcfdb610bedb541ea58f01e4472fedb6329e1",
+              "LFR_N1000_ad5_mc50_mu0.7.json.gz": "cf15587099eecf7fb567b2f3796216c48b32812e9d3dbf9898570b730fee9e47",
+              "LFR_N1000_ad5_mc50_mu0.8.json.gz": "9dd4ddd0cfe9083d8a6864ecc590b75423d66f45629b3f3aeb2edb264b7adbab",
+              "LFR_N1000_ad5_mc50_mu0.9.json.gz": "ec1b36bb6e761dfb901e68a6be54b873914de669ced44b3ba2ceaf3a9fbc2910",
+              "LFR_N50000_ad5_mc50_mu0.1.json.gz": "8f93424c0c678e290a15e3d1e1d9830d6a4f093f65808b25b65cec6ec4746f86",
+              "LFR_N50000_ad5_mc50_mu0.2.json.gz": "696ee1368acf4d4437a23fea8c7f96b651e34f6e3d4936865982eea68d4b1234",
+              "LFR_N50000_ad5_mc50_mu0.3.json.gz": "dd032117d5baf699ba09a5d612290f7c5b4562103f3688ce08647509667f1016",
+              "LFR_N50000_ad5_mc50_mu0.4.json.gz": "cbe54f699af50c6059a5d29391f7a9397753731ad51a91c1f500a86fdca04130",
+              "LFR_N50000_ad5_mc50_mu0.5.json.gz": "d71f92c8425f3af01b041d2bd2549a8863e8f294e5252b74afd7acbcbafd9bfd",
+              "LFR_N50000_ad5_mc50_mu0.6.json.gz": "999286832cb585b98a651ea5d5008bc6194f052326bd24cac2ae73b78dfb803c",
+              "LFR_N50000_ad5_mc50_mu0.7.json.gz": "9d129ce9ba90b46ea68617485418ea535d97dd24cad9fd566b7e2c36d44f210c",
+              "LFR_N50000_ad5_mc50_mu0.8.json.gz": "20989d85b58818b799b526e1a40c742cd19dfd593fe94ca0f0dba54dc9a86b99",
+              "LFR_N50000_ad5_mc50_mu0.9.json.gz": "dd75836ff29cbeef130889cbbf1725af09e6e7a1e8038af106bdcadd8628a63c",
+              "LFR_N5000_ad5_mc50_mu0.1.json.gz": "cc4ea02e10184eee588978a79a8aa2b9f0805090ba1c3d4b8c7224c242f96517",
+              "LFR_N5000_ad5_mc50_mu0.2.json.gz": "9598c8e20857951edfc41cc5b42f841d01b423982552196440fc37284c0191d3",
+              "LFR_N5000_ad5_mc50_mu0.3.json.gz": "a6f04d0a483775ca812004ab435ffb4df992152b446d432d3ba6eedaa76e278c",
+              "LFR_N5000_ad5_mc50_mu0.4.json.gz": "b678994df15c884cb3f0ce4d441039aff4f48c30acfc08cf6d61cbe03366e4ce",
+              "LFR_N5000_ad5_mc50_mu0.5.json.gz": "b0281d2c36562743d582b7021160263a5f9db31f4970c698a332b063cb4c6d7c",
+              "LFR_N5000_ad5_mc50_mu0.6.json.gz": "4c1e95a89e27676b55e851992d724a9248ccf01994ac0a83415b25ac17607e2d",
+              "LFR_N5000_ad5_mc50_mu0.7.json.gz": "fc211d363947a41b028ccd259a7a1f02933abd7a5a458874c9505faa9d24d662",
+              "LFR_N5000_ad5_mc50_mu0.8.json.gz": "e09ca4318e4e63b10e3c27837221c17e5d904a4c5e954c698a0ba98cc419bd47",
+              "LFR_N5000_ad5_mc50_mu0.9.json.gz": "5287d16543efcac9f5748744f690eadeb61d46b14cb95569b104308935a74583",
+              }
+)
+
+
+def available_networks():
+    """
+    List the remotely available network datasets.
+
+    :return: list of network names
+
+    :Example:
+
+    >>> from cdlib import datasets
+    >>> graph_name_list = datasets.available_networks()
+
+    """
+    return [x.split(".csv")[0] for x in __networks.registry.keys()]
+
+
+def available_ground_truths():
+    """
+    List the remotely available network ground truth datasets.
+
+    :return: list of network names
+
+    :Example:
+
+    >>> from cdlib import datasets
+    >>> graph_name_list = datasets.available_ground_truths()
+
+    """
+    return [x.split(".json")[0] for x in __ground_truths.registry.keys()]
+
+
+def fetch_network_data(net_name="karate_club", net_type="igraph"):
+    """
+    Load the required network from the remote repository
+
+    :param net_name: network name
+    :param net_type: desired graph object among "networkx" and "igraph". Default, igraph.
+    :return: a graph object
+
+    :Example:
+
+    >>> from cdlib import datasets
+    >>> G = datasets.fetch_network_data(net_name="karate_club", net_type="igraph")
+
+    """
+
+    download = pooch.HTTPDownloader(progressbar=True)
+    fname = __networks.fetch(f"{net_name}.csv.gz", processor=pooch.Decompress(), downloader=download)
+
+    if net_type == "networkx":
+        g = nx.Graph()
+        with open(fname) as f:
+            for l in f:
+                l = l.replace(" ", "\t").split("\t")
+                g.add_edge(int(l[0]), int(l[1]))
+    else:
+        if ig is None:
+            raise ModuleNotFoundError("Optional dependency not satisfied: install python-igraph to use the selected "
+                                      "feature.")
+
+        edges = []
+        with open(fname) as f:
+            for l in f:
+                l = l.replace(" ", "\t").split("\t")
+                edges.append((int(l[0]), int(l[1])))
+        g = ig.Graph.TupleList(edges)
+
+    return g
+
+
+def fetch_ground_truth_data(net_name="karate_club", graph=None):
+    """
+    Load the required ground truth clustering from the remote repository
+
+    :param net_name: network name
+    :param graph: the graph object associated to the ground truth (optional)
+    :return: a NodeClustering object
+
+    :Example:
+
+    >>> from cdlib import datasets
+    >>> gt_coms = datasets.fetch_network_data(fetch_ground_truth_data="karate_club")
+
+    """
+
+    download = pooch.HTTPDownloader(progressbar=True)
+    fname = __ground_truths.fetch(f"{net_name}.json.gz", processor=pooch.Decompress(), downloader=download)
+    gt = read_community_json(fname)
+    if graph is not None:
+        gt.graph = graph
+    return gt
+
+
+def fetch_network_ground_truth(net_name="karate_club", net_type="igraph"):
+    """
+    Load the required network, along with its ground truth partition, from the remote repository.
+
+    :param net_name: network name
+    :param net_type: desired graph object among "networkx" and "igraph". Default, igraph.
+    :return: a tuple of (graph_object, NodeClustering)
+
+    :Example:
+
+    >>> from cdlib import datasets
+    >>> G, gt_coms = datasets.fetch_network_ground_truth(fetch_ground_truth_data="karate_club", net_type="igraph")
+
+    """
+
+    if net_name not in available_networks() or net_name not in available_ground_truths():
+        raise ValueError(f"{net_name} is not present in the remote repository")
+
+    g = fetch_network_data(net_name, net_type)
+    gt = fetch_ground_truth_data(net_name, g)
+    return g, gt
diff --git a/cdlib/readwrite/io.py b/cdlib/readwrite/io.py
index c7f21476..d6068ff0 100644
--- a/cdlib/readwrite/io.py
+++ b/cdlib/readwrite/io.py
@@ -1,17 +1,19 @@
 from cdlib import NodeClustering, FuzzyNodeClustering, EdgeClustering
 import json
+import gzip
 
 __all__ = ["write_community_csv", "read_community_csv", "write_community_json",
            "read_community_json", "read_community_from_json_string"]
 
 
-def write_community_csv(communities,  path, delimiter=","):
+def write_community_csv(communities,  path, delimiter=",", zip=False):
     """
     Save community structure to comma separated value (csv) file.
 
     :param communities: a NodeClustering object
     :param path: output filename
     :param delimiter: column delimiter
+    :param zip: wheter to copress the csv, default False
 
     :Example:
 
@@ -22,19 +24,25 @@ def write_community_csv(communities,  path, delimiter=","):
     >>> readwrite.write_community_csv(coms, "communities.csv", ",")
 
     """
-    with open(path, "w") as f:
+    if zip:
+        op = gzip.open
+    else:
+        op = open
+
+    with op(path, "wt") as f:
         for cid, community in enumerate(communities.communities):
             res = delimiter.join(list(map(str, community)))
             f.write("%s\n" % res)
 
 
-def read_community_csv(path, delimiter=",", nodetype=str):
+def read_community_csv(path, delimiter=",", nodetype=str, zip=False):
     """
     Read community list from comma separated value (csv) file.
 
     :param path: input filename
     :param delimiter: column delimiter
     :param nodetype: specify the type of node labels, default str
+    :param zip: wheter the file is compressed or not, default False
     :return: NodeClustering object
 
     :Example:
@@ -48,7 +56,13 @@ def read_community_csv(path, delimiter=",", nodetype=str):
 
     """
     communities = []
-    with open(path) as f:
+
+    if zip:
+        op = gzip.open
+    else:
+        op = open
+
+    with op(path, "rt") as f:
         for row in f:
             community = list(map(nodetype, row.rstrip().split(delimiter)))
             communities.append(list(community))
@@ -56,12 +70,13 @@ def read_community_csv(path, delimiter=",", nodetype=str):
     return NodeClustering(communities, None, "")
 
 
-def write_community_json(communities, path):
+def write_community_json(communities, path, zip=False):
     """
     Generate a JSON representation of the clustering object
 
     :param communities: a cdlib clustering object
     :param path: output filename
+    :param zip: wheter to copress the JSON, default False
     :return: a JSON formatted string representing the object
 
     :Example:
@@ -82,15 +97,22 @@ def write_community_json(communities, path):
         pass
 
     js_dmp = json.dumps(partition)
-    with open(path, "w") as f:
+
+    if zip:
+        op = gzip.open
+    else:
+        op = open
+
+    with op(path, "wt") as f:
         f.write(js_dmp)
 
 
-def read_community_json(path):
+def read_community_json(path, zip=False):
     """
     Read community list from JSON file.
 
     :param path: input filename
+    :param zip: wheter the file is in a copress format, default False
     :return: a Clustering object
 
     :Example:
@@ -103,7 +125,12 @@ def read_community_json(path):
     >>> readwrite.read_community_json(coms, "communities.json")
     """
 
-    with open(path, "r") as f:
+    if zip:
+        op = gzip.open
+    else:
+        op = open
+
+    with op(path, "rt") as f:
         coms = json.load(f)
 
     nc = NodeClustering([list(c) for c in coms['communities']], None, coms['algorithm'],
diff --git a/cdlib/utils.py b/cdlib/utils.py
index 5a23b7d6..4ae444ce 100644
--- a/cdlib/utils.py
+++ b/cdlib/utils.py
@@ -82,6 +82,7 @@ def __from_nx_to_igraph(g, directed=None):
     :param directed:
     :return:
     """
+    from networkx.algorithms import bipartite
 
     if ig is None:
         raise ModuleNotFoundError(
@@ -92,27 +93,36 @@ def __from_nx_to_igraph(g, directed=None):
 
     gi = ig.Graph(directed=directed)
 
-    ##Two problems to handle:
+    a_r = {}
+    if bipartite.is_bipartite(g):
+        A, B = bipartite.sets(g)
+        for a in A:
+            a_r[a] = 0
+        for b in B:
+            a_r[b] = 1
+
+    ## Two problems to handle:
     # 1)in igraph, names have to be str.
     # 2)since we can ask to compute metrics with found communities and the the original graph, we need to keep
-    #the original nodes types in communities. Therefore we need to handle some transparent conversion for non-str nodes.
-    if type(list(g.nodes)[0]) is str: #if nodes are string, no problem
+    # the original nodes types in communities. Therefore we need to handle some transparent conversion for non-str nodes
+    if type(list(g.nodes)[0]) is str: # if nodes are string, no problem
         gi.add_vertices([n for n in g.nodes()])
         gi.add_edges([(u, v) for (u, v) in g.edges()])
 
     else:
-        if set(range(len(g.nodes)))==set(g.nodes()):#if original names are well formed contiguous ints, keep this for efficiency.
+        if set(range(len(g.nodes)))==set(g.nodes()):# if original names are well formed contiguous ints, keep this for efficiency.
             # Put these int as str with identitiers in the name attribute
             gi.add_vertices(len(g.nodes))
             gi.add_edges([(u, v) for (u, v) in g.edges()])
             gi.vs["name"]=["\\"+str(n) for n in g.nodes()]
-        else: #if names are not well formed ints, convert to string and use the identifier to remember converting back to int
-            #convert = {str(x):x for x in g.nodes()}
+        else: # if names are not well formed ints, convert to string and use the identifier to remember
+            # converting back to int
+            # convert = {str(x):x for x in g.nodes()}
             gi.add_vertices(["\\"+str(n) for n in g.nodes()])
             gi.add_edges([("\\"+str(u), "\\"+str(v)) for (u, v) in g.edges()])
-            #for k,v in convert.items():
-            #    gi.vs["name"][k]=v
 
+    if bipartite.is_bipartite(g):
+        gi.vs['type'] = [a_r[name] if type(name)==int else a_r[int(name.replace("\\", ""))] for name in gi.vs["name"]]
 
     edgelist = nx.to_pandas_edgelist(g)
     for attr in edgelist.columns[2:]:
diff --git a/cdlib/viz/networks.py b/cdlib/viz/networks.py
index 620fc6ee..a7cba198 100644
--- a/cdlib/viz/networks.py
+++ b/cdlib/viz/networks.py
@@ -1,3 +1,6 @@
+import warnings
+import numpy as np
+import matplotlib
 import matplotlib.pyplot as plt
 import networkx as nx
 from cdlib import NodeClustering
@@ -6,22 +9,50 @@
 
 __all__ = ["plot_network_clusters", "plot_community_graph"]
 
-COLOR = ['r', 'b', 'g', 'c', 'm', 'y', 'k',
-         '0.8', '0.2', '0.6', '0.4', '0.7', '0.3', '0.9', '0.1', '0.5']
-
-
-def plot_network_clusters(graph, partition, position, figsize=(8, 8), node_size=200, plot_overlaps=False,
-                          plot_labels=False):
+# [r, b, g, c, m, y, k, 0.8, 0.2, 0.6, 0.4, 0.7, 0.3, 0.9, 0.1, 0.5]
+COLOR = (
+    (1, 0, 0),
+    (0, 0, 1),
+    (0, 0.5, 0),
+    (0, 0.75, 0.75),
+    (0.75, 0, 0.75),
+    (0.75, 0.75, 0),
+    (0, 0, 0),
+    (0.8, 0.8, 0.8),
+    (0.2, 0.2, 0.2),
+    (0.6, 0.6, 0.6),
+    (0.4, 0.4, 0.4),
+    (0.7, 0.7, 0.7),
+    (0.3, 0.3, 0.3),
+    (0.9, 0.9, 0.9),
+    (0.1, 0.1, 0.1),
+    (0.5, 0.5, 0.5)
+)
+
+
+def __filter(partition, top_k, min_size):
+    if isinstance(min_size, int) and min_size > 0:
+        partition = list(filter(lambda nodes: len(nodes) >= min_size, partition))
+    if isinstance(top_k, int) and top_k > 0:
+        partition = partition[:top_k]
+    return partition
+
+
+def plot_network_clusters(graph, partition, position=None, figsize=(8, 8), node_size=200, plot_overlaps=False,
+                          plot_labels=False, cmap=None, top_k=None, min_size=None):
     """
     Plot a graph with node color coding for communities.
 
     :param graph: NetworkX/igraph graph
     :param partition: NodeClustering object
-    :param position: A dictionary with nodes as keys and positions as values. Example: networkx.fruchterman_reingold_layout(G)
+    :param position: A dictionary with nodes as keys and positions as values. Example: networkx.fruchterman_reingold_layout(G). By default, uses nx.spring_layout(g)
     :param figsize: the figure size; it is a pair of float, default (8, 8)
     :param node_size: int, default 200
     :param plot_overlaps: bool, default False. Flag to control if multiple algorithms memberships are plotted.
     :param plot_labels: bool, default False. Flag to control if node labels are plotted.
+    :param cmap: str or Matplotlib colormap, Colormap(Matplotlib colormap) for mapping intensities of nodes. If set to None, original colormap is used.
+    :param top_k: int, Show the top K influential communities. If set to zero or negative value indicates all.
+    :param min_size: int, Exclude communities below the specified minimum size.
 
     Example:
 
@@ -32,16 +63,41 @@ def plot_network_clusters(graph, partition, position, figsize=(8, 8), node_size=
     >>> pos = nx.spring_layout(g)
     >>> viz.plot_network_clusters(g, coms, pos)
     """
-    partition = partition.communities
+    if not isinstance(cmap, (type(None), str, matplotlib.colors.Colormap)):
+        raise TypeError("The 'cmap' argument must be NoneType, str or matplotlib.colors.Colormap, "
+                        "not %s." % (type(cmap).__name__))
+
+    partition = __filter(partition.communities, top_k, min_size)
     graph = convert_graph_formats(graph, nx.Graph)
+    if position is None:
+        position = nx.spring_layout(graph)
+
+    n_communities = len(partition)
+    if n_communities == 0:
+        warnings.warn("There are no communities that match the filter criteria.")
+        return None
+
+    if cmap is None:
+        n_communities = min(n_communities, len(COLOR))
+        cmap = matplotlib.colors.ListedColormap(COLOR[:n_communities])
+    else:
+        cmap = plt.cm.get_cmap(cmap, n_communities)
+    _norm = matplotlib.colors.Normalize(vmin=0, vmax=n_communities - 1)
+    fontcolors = list(map(lambda rgb: ".15" if np.dot(rgb, [0.2126, 0.7152, 0.0722]) > .408 else "w",
+                          [cmap(_norm(i))[:3] for i in range(n_communities)]))
 
-    n_communities = min(len(partition), len(COLOR))
     plt.figure(figsize=figsize)
     plt.axis('off')
 
-    fig = nx.draw_networkx_nodes(graph, position, node_size=node_size, node_color='w')
+    filtered_nodelist = list(np.concatenate(partition))
+    filtered_edgelist = list(filter(lambda edge: len(np.intersect1d(edge, filtered_nodelist)) == 2, graph.edges()))
+    fig = nx.draw_networkx_nodes(graph, position, node_size=node_size, node_color='w',
+                                 nodelist=filtered_nodelist)
     fig.set_edgecolor('k')
-    nx.draw_networkx_edges(graph, position, alpha=.5)
+    nx.draw_networkx_edges(graph, position, alpha=.5, edgelist=filtered_edgelist)
+    if plot_labels:
+        nx.draw_networkx_labels(graph, position, font_color=".2",
+                                labels={node: str(node) for node in filtered_nodelist})
     for i in range(n_communities):
         if len(partition[i]) > 0:
             if plot_overlaps:
@@ -49,35 +105,42 @@ def plot_network_clusters(graph, partition, position, figsize=(8, 8), node_size=
             else:
                 size = node_size
             fig = nx.draw_networkx_nodes(graph, position, node_size=size,
-                                         nodelist=partition[i], node_color=COLOR[i])
+                                         nodelist=partition[i], node_color=[cmap(_norm(i))])
             fig.set_edgecolor('k')
     if plot_labels:
-        nx.draw_networkx_labels(graph, position, labels={node: str(node) for node in graph.nodes()})
+        for i in range(n_communities):
+            if len(partition[i]) > 0:
+                nx.draw_networkx_labels(graph, position, font_color=fontcolors[i],
+                                        labels={node: str(node) for node in partition[i]})
 
     return fig
 
 
-def plot_community_graph(graph, partition, figsize=(8, 8), node_size=200, plot_overlaps=False, plot_labels=False):
+def plot_community_graph(graph, partition, figsize=(8, 8), node_size=200, plot_overlaps=False, plot_labels=False,
+                         cmap=None, top_k=None, min_size=None):
     """
-        Plot a algorithms-graph with node color coding for communities.
+    Plot a algorithms-graph with node color coding for communities.
 
-        :param graph: NetworkX/igraph graph
-        :param partition: NodeClustering object
-        :param figsize: the figure size; it is a pair of float, default (8, 8)
-        :param node_size: int, default 200
-        :param plot_overlaps: bool, default False. Flag to control if multiple algorithms memberships are plotted.
-        :param plot_labels: bool, default False. Flag to control if node labels are plotted.
+    :param graph: NetworkX/igraph graph
+    :param partition: NodeClustering object
+    :param figsize: the figure size; it is a pair of float, default (8, 8)
+    :param node_size: int, default 200
+    :param plot_overlaps: bool, default False. Flag to control if multiple algorithms memberships are plotted.
+    :param plot_labels: bool, default False. Flag to control if node labels are plotted.
+    :param cmap: str or Matplotlib colormap, Colormap(Matplotlib colormap) for mapping intensities of nodes. If set to None, original colormap is used..
+    :param top_k: int, Show the top K influential communities. If set to zero or negative value indicates all.
+    :param min_size: int, Exclude communities below the specified minimum size.
 
-        Example:
+    Example:
 
-        >>> from cdlib import algorithms, viz
-        >>> import networkx as nx
-        >>> g = nx.karate_club_graph()
-        >>> coms = algorithms.louvain(g)
-        >>> viz.plot_community_graph(g, coms)
-        """
+    >>> from cdlib import algorithms, viz
+    >>> import networkx as nx
+    >>> g = nx.karate_club_graph()
+    >>> coms = algorithms.louvain(g)
+    >>> viz.plot_community_graph(g, coms)
+    """
 
-    cms = partition.communities
+    cms = __filter(partition.communities, top_k, min_size)
 
     node_to_com = {}
     for cid, com in enumerate(cms):
@@ -98,8 +161,6 @@ def plot_community_graph(graph, partition, figsize=(8, 8), node_size=200, plot_o
     c_graph = induced_graph(node_to_com, s)
     node_cms = [[node] for node in c_graph.nodes()]
 
-    return plot_network_clusters(c_graph, NodeClustering(node_cms, None, ""), nx.spring_layout(c_graph), figsize=figsize,
-                                 node_size=node_size, plot_overlaps=plot_overlaps, plot_labels=plot_labels)
-
-
-
+    return plot_network_clusters(c_graph, NodeClustering(node_cms, None, ""), nx.spring_layout(c_graph),
+                                 figsize=figsize,
+                                 node_size=node_size, plot_overlaps=plot_overlaps, plot_labels=plot_labels, cmap=cmap)
diff --git a/cdlib/viz/plots.py b/cdlib/viz/plots.py
index b2ec4900..75a284fd 100644
--- a/cdlib/viz/plots.py
+++ b/cdlib/viz/plots.py
@@ -63,7 +63,7 @@ def plot_com_stat(com_clusters, com_fitness):
         allVals += prop
         allNames += [c.get_description()] * len(prop)
 
-    ax = sns.violinplot(allNames, allVals,cut=0,saturation=0.5,palette="Set3")
+    ax = sns.violinplot(allNames, allVals, cut=0, saturation=0.5, palette="Set3")
     for tick in ax.get_xticklabels():
         tick.set_rotation(90)
 
@@ -71,9 +71,6 @@ def plot_com_stat(com_clusters, com_fitness):
     plt.xlabel("Algorithm")
     plt.tight_layout()
 
-
-
-
     return ax
 
 
@@ -108,7 +105,8 @@ def plot_com_properties_relation(com_clusters, com_fitness_x, com_fitness_y, **k
             for_df.append([c.get_description(), vx, y[i]])
 
     df = pd.DataFrame(columns=["Method", com_fitness_x.__name__, com_fitness_y.__name__], data=for_df)
-    ax = sns.lmplot(x=com_fitness_x.__name__, y=com_fitness_y.__name__, data=df, hue="Method", fit_reg=False,legend=False, x_bins=100,**kwargs)
+    ax = sns.lmplot(x=com_fitness_x.__name__, y=com_fitness_y.__name__, data=df, hue="Method", fit_reg=False,
+                    legend=False, x_bins=100, **kwargs)
     plt.legend(loc='best')
 
     # if log_x:
@@ -121,8 +119,8 @@ def plot_com_properties_relation(com_clusters, com_fitness_x, com_fitness_y, **k
 
 
 def plot_scoring(graphs, ref_partitions, graph_names, methods, scoring=cdlib.evaluation.adjusted_mutual_information,
-                nbRuns=5):
-   """
+                 nbRuns=5):
+    """
    Plot the scores obtained by a list of methods on a list of graphs.
 
    :param graphs: list of graphs on which to make computations
@@ -147,19 +145,19 @@ def plot_scoring(graphs, ref_partitions, graph_names, methods, scoring=cdlib.eva
    >>> viz.plot_scoring(graphs, references, names, algos, nbRuns=2)
 
    """
-   forDF = []
-   for i, g in enumerate(graphs):
-       for m in methods:
-           for r in range(nbRuns):
-               partition = m(g)
-
-               score = scoring(partition, ref_partitions[i]).score
-               forDF.append([graph_names[i], score, partition.get_description()])
-   df = pd.DataFrame(columns=["graph", "score", "method"], data=forDF)
-   ax = sns.lineplot(x="graph", y="score", hue="method", data=df, legend="brief")
-   ax.legend(loc='best')
-   for tick in ax.get_xticklabels():
-       tick.set_rotation(90)
-   plt.tight_layout()
-
-   return ax
+    forDF = []
+    for i, g in enumerate(graphs):
+        for m in methods:
+            for r in range(nbRuns):
+                partition = m(g)
+
+                score = scoring(partition, ref_partitions[i]).score
+                forDF.append([graph_names[i], score, partition.get_description()])
+    df = pd.DataFrame(columns=["graph", "score", "method"], data=forDF)
+    ax = sns.lineplot(x="graph", y="score", hue="method", data=df, legend="brief")
+    ax.legend(loc='best')
+    for tick in ax.get_xticklabels():
+        tick.set_rotation(90)
+    plt.tight_layout()
+
+    return ax
diff --git a/docs/conf.py b/docs/conf.py
index 54db719c..49921c59 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -26,7 +26,8 @@ def __getattr__(cls, name):
 
 MOCK_MODULES = ['ASLPAw_package', 'ipaddress', 'ASLPAw', 'graph-tool', 'leidenalg', 'numpy', 'scipy', 'networkx', 'karateclub', 'bimlpa', 'sklearn', 'pquality', 'functools', 'nf1',
                 'ipython', 'pygtk', 'gtk', 'gobject', 'argparse', 'matplotlib', 'matplotlib.pyplot', 'scikit-learn',
-                'python-igraph', 'wurlitzer', 'pulp', 'seaborn', 'pandas', 'infomap', 'angel-cd', 'omega_index_py3']  
+                'python-igraph', 'wurlitzer', 'pulp', 'seaborn', 'pandas', 'infomap', 'angel-cd', 'omega_index_py3', 'markov_clustering', 'chinese_whispers',
+                'scipy.sparse']
 sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
 
 html_theme = "sphinx_rtd_theme"
@@ -89,9 +90,9 @@ def __getattr__(cls, name):
 # built documents.
 #
 # The short X.Y version.
-version = u'0.1.8'
+version = u'0.1.10'
 # The full version, including alpha/beta/rc tags.
-release = u'0.1.8'
+release = u'0.1.10'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/index.rst b/docs/index.rst
index fc3d48f0..b9832b94 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -14,7 +14,7 @@ The implementations of all CD algorithms are inherited from existing projects, e
 
 ================ =================== ==================  ==========  ===============
    **Date**      **Python Versions**   **Main Author**   **GitHub**      **pypl**
-|date|                   3.7         `Giulio Rossetti`_  `Source`_   `Distribution`_
+|date|                 3.7-3.8       `Giulio Rossetti`_  `Source`_   `Distribution`_
 ================ =================== ==================  ==========  ===============
 
 
diff --git a/docs/reference/cd_algorithms/edge_clustering.rst b/docs/reference/cd_algorithms/edge_clustering.rst
index 96da74a9..87095113 100644
--- a/docs/reference/cd_algorithms/edge_clustering.rst
+++ b/docs/reference/cd_algorithms/edge_clustering.rst
@@ -5,6 +5,9 @@ Edge Clustering
 Algorithms falling in this category generates communities composed by edges.
 They return as result a ``EdgeClustering`` object instance.
 
+.. note::
+    The following lists are aligned to CD methods available in the *GitHub main branch* of `CDlib`_.
+
 
 .. automodule:: cdlib.algorithms
 
@@ -13,3 +16,7 @@ They return as result a ``EdgeClustering`` object instance.
     :toctree: algs/
 
     hierarchical_link_community
+
+
+
+.. _`CDlib`: https://github.com/GiulioRossetti/CDlib
\ No newline at end of file
diff --git a/docs/reference/cd_algorithms/node_clustering.rst b/docs/reference/cd_algorithms/node_clustering.rst
index 710d046a..1762ab27 100644
--- a/docs/reference/cd_algorithms/node_clustering.rst
+++ b/docs/reference/cd_algorithms/node_clustering.rst
@@ -2,9 +2,14 @@
 Node Clustering
 ===============
 
-Algorithms falling in this category generates communities composed by nodes.
+Algorithms falling in this category generate communities composed by nodes.
 The communities can represent neat, *crisp*, partition as well as *overlapping* or even *fuzzy* ones.
 
+.. note::
+    The following lists are aligned to CD methods available in the *GitHub main branch* of `CDlib`_.
+
+    In particular, the current version of the library on pypl (that can be installed through pip) does not include the following algorithms: belief, ga.
+
 
 .. automodule:: cdlib.algorithms
 
@@ -13,7 +18,7 @@ Crisp Communities
 ^^^^^^^^^^^^^^^^^
 
 A clustering is said to be a *partition* if each node belongs to one and only one community.
-Methods in this subclass returns as result a ``NodeClustering`` object instance.
+Methods in this subclass return as result a ``NodeClustering`` object instance.
 
 .. autosummary::
     :toctree: algs/
@@ -21,11 +26,14 @@ Methods in this subclass returns as result a ``NodeClustering`` object instance.
     agdl
     aslpaw
     async_fluid
+    belief
     cpm
+    chinesewhispers
     der
     edmot
     eigenvector
     em
+    ga
     gdmp2
     girvan_newman
     greedy_modularity
@@ -50,7 +58,7 @@ Overlapping Communities
 ^^^^^^^^^^^^^^^^^^^^^^^
 
 A clustering is said to be *overlapping* if any generic node can be assigned to more than one community.
-Methods in this subclass returns as result a ``NodeClustering`` object instance.
+Methods in this subclass return as result a ``NodeClustering`` object instance.
 
 .. autosummary::
     :toctree: algs/
@@ -74,6 +82,7 @@ Methods in this subclass returns as result a ``NodeClustering`` object instance.
     overlapping_seed_set_expansion
     percomvc
     slpa
+    wCommunity
 
 
 ^^^^^^^^^^^^^^^^^
@@ -81,7 +90,7 @@ Fuzzy Communities
 ^^^^^^^^^^^^^^^^^
 
 A clustering is said to be a *fuzzy* if each node can belongs (with a different degree of likelihood) to more than one community.
-Methods in this subclass returns as result a ``FuzzyNodeClustering`` object instance.
+Methods in this subclass return as result a ``FuzzyNodeClustering`` object instance.
 
 .. autosummary::
     :toctree: algs/
@@ -93,7 +102,7 @@ Methods in this subclass returns as result a ``FuzzyNodeClustering`` object inst
 Node Attribute
 ^^^^^^^^^^^^^^
 
-Methods in this subclass returns as result a ``AttrNodeClustering`` object instance.
+Methods in this subclass return as result a ``AttrNodeClustering`` object instance.
 
 .. autosummary::
     :toctree: algs/
@@ -102,13 +111,30 @@ Methods in this subclass returns as result a ``AttrNodeClustering`` object insta
     ilouvain
 
 
-^^^^^^^^^^^^^^
-Bipartite Node
-^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Bipartite Graph Communities
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Methods in this subclass returns as result a ``BiNodeClustering`` object instance.
+Methods in this subclass return as result a ``BiNodeClustering`` object instance.
 
 .. autosummary::
     :toctree: algs/
 
-    bimlpa
\ No newline at end of file
+    bimlpa
+    CPM_Bipartite
+    infomap_bipartite
+
+
+^^^^^^^^^^^^^^^^^^^^^
+Antichain Communities
+^^^^^^^^^^^^^^^^^^^^^
+
+Methods in this subclass are designed to extract communities from Directed Acyclic Graphs (DAG) and return as result a ``NodeClustering`` object instance.
+
+.. autosummary::
+    :toctree: algs/
+
+    siblinarity_antichain
+
+
+.. _`CDlib`: https://github.com/GiulioRossetti/CDlib
\ No newline at end of file
diff --git a/docs/reference/datasets.rst b/docs/reference/datasets.rst
new file mode 100644
index 00000000..7a53a384
--- /dev/null
+++ b/docs/reference/datasets.rst
@@ -0,0 +1,24 @@
+***************
+Remote Datasets
+***************
+
+``cdlib`` allows to retrieve existing datasets, along with their ground truth partitions (if available), from an ad-hoc remote `repository`_.
+
+.. note::
+    The following features are still under testing: therefore, they are accessible only on the *GitHub* version of the library.
+
+
+.. automodule:: cdlib.datasets
+
+
+.. autosummary::
+    :toctree: generated/
+
+    available_networks
+    available_ground_truths
+    fetch_network_data
+    fetch_ground_truth_data
+    fetch_network_ground_truth
+
+
+.. _`repository`: https://github.com/GiulioRossetti/cdlib_datasets
\ No newline at end of file
diff --git a/docs/reference/reference.rst b/docs/reference/reference.rst
index 3235f32b..36716983 100644
--- a/docs/reference/reference.rst
+++ b/docs/reference/reference.rst
@@ -14,4 +14,5 @@ Reference
     evaluation.rst
     readwrite.rst
     viz.rst
+    datasets.rst
     utils.rst
\ No newline at end of file
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 93b87a34..c111d632 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -4,6 +4,6 @@ Tutorial
 
 NClib is built upon networkx and is designed to extract, compare and evaluate network partitions.
 
-You can find a few basilar examples to get started with ``cdlib`` in this `notebook <https://colab.research.google.com/github/KDDComplexNetworkAnalysis/CNA_Tutorials/blob/master/CDlib.ipynb>`_
+You can find a few basilar examples to get started with ``cdlib`` in this `notebook <https://colab.research.google.com/github/KDDComplexNetworkAnalysis/CNA_Tutorials/blob/master/CDlib_tutorial.ipynb>`_
 
 
diff --git a/requirements.docs.txt b/requirements.docs.txt
index 5a34396b..01c9d0a9 100644
--- a/requirements.docs.txt
+++ b/requirements.docs.txt
@@ -18,3 +18,4 @@ karateclub>=1.0.0
 bimlpa
 ASLPAw
 sphinx==1.7.5 # pyup: ignore
+pooch
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index d0b23c14..c7984b5b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 numpy>=1.15.*
 future>=0.16.*
 matplotlib>=3.0.*
-scikit-learn>=0.21.*
+scikit-learn>=0.21.*,<0.24
 tqdm>=4.20.*
 networkx>=2.4
 demon>=2.0.5
@@ -16,5 +16,9 @@ pandas>=0.24
 eva_lcd
 karateclub>=1.0.0
 bimlpa
-ASLPAw
-dynetx
\ No newline at end of file
+ASLPAw==2.0.0
+markov_clustering
+chinese_whispers
+shuffle_graph==1.1.1
+pooch
+dynetx
diff --git a/setup.py b/setup.py
index 95c52dfb..4d5054fb 100644
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ def get_requirements(remove_links=True):
 
 
 setup(name='cdlib',
-      version='0.1.8',
+      version='0.1.10',
       license='BSD-Clause-2',
       description='Community Discovery Library',
       url='https://github.com/GiulioRossetti/cdlib',
@@ -70,6 +70,8 @@ def get_requirements(remove_links=True):
       ],
       keywords='community-discovery node-clustering edge-clustering complex-networks',
       install_requires=get_requirements(),
+      long_description=long_description,
+      long_description_content_type='text/markdown',
       extras_require={
         'C':  ["infomap==1.1.3", "python-igraph==0.7.1.post6", "leidenalg==0.7.0",
                "angel-cd==1.0.2", "wurlitzer>=1.0.2"],


Test patch:
diff --git a/cdlib/test/test_community_discovery_models.py b/cdlib/test/test_community_discovery_models.py
index 1fd0252a..4432c5b6 100644
--- a/cdlib/test/test_community_discovery_models.py
+++ b/cdlib/test/test_community_discovery_models.py
@@ -1,6 +1,8 @@
 import unittest
 from cdlib import algorithms
 import networkx as nx
+import itertools
+import random
 import os
 
 
@@ -35,6 +37,20 @@ def get_string_graph():
     return g
 
 
+def random_dag(N, P):
+    nodes = [n for n in range(1, N + 1)]
+    G = nx.DiGraph()
+    G.add_nodes_from(nodes)
+    for n1, n2 in itertools.combinations(nodes, 2):
+        p = random.random()
+        if p <= P:
+            if n1 > n2:
+                G.add_edge(n2, n1)
+            else:
+                G.add_edge(n1, n2)
+    return G
+
+
 class CommunityDiscoveryTests(unittest.TestCase):
     
     def test_ego(self):
@@ -173,6 +189,18 @@ def test_infomap(self):
             if os.path.exists(".tree"):
                 os.remove(".tree")
 
+            gg = ig.Graph(directed=True)
+            gg.add_vertices([v for v in h.nodes()])
+            gg.add_edges([(u, v) for u, v in h.edges()])
+
+            coms = algorithms.infomap(gg)
+            self.assertEqual(type(coms.communities), list)
+            if len(coms.communities) > 0:
+                self.assertEqual(type(coms.communities[0]), list)
+                self.assertEqual(type(coms.communities[0][0]), str)
+            if os.path.exists(".tree"):
+                os.remove(".tree")
+
     def test_lp(self):
         g = get_string_graph()
         coms = algorithms.label_propagation(g)
@@ -390,7 +418,7 @@ def test_Conga(self):
 
     def test_agdl(self):
         g = get_string_graph()
-        coms = algorithms.agdl(g, 3, 2, 2, 0.5)
+        coms = algorithms.agdl(g, 3, 2)
         self.assertEqual(type(coms.communities), list)
         if len(coms.communities) > 0:
             self.assertEqual(type(coms.communities[0]), list)
@@ -489,3 +517,103 @@ def test_percomvc(self):
         if len(coms.communities) > 0:
             self.assertEqual(type(coms.communities[0]), list)
             self.assertEqual(type(coms.communities[0][0]), int)
+
+    def test_chinese_whispers(self):
+        g = get_string_graph()
+
+        communities = algorithms.chinesewhispers(g)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), str)
+
+        g = nx.karate_club_graph()
+
+        communities = algorithms.chinesewhispers(g)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), int)
+
+    def test_wCommunities(self):
+
+        g = get_string_graph()
+        nx.set_edge_attributes(g, values=1, name='weight')
+
+        communities = algorithms.wCommunity(g, min_bel_degree=0.6, threshold_bel_degree=0.6)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), int)
+
+        g = nx.karate_club_graph()
+        nx.set_edge_attributes(g, values=1, name='weight')
+
+        communities = algorithms.wCommunity(g, min_bel_degree=0.6, threshold_bel_degree=0.6)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), int)
+
+    def test_siblinarity_antichain(self):
+
+        g = random_dag(100, 0.1)
+        communities = algorithms.siblinarity_antichain(g, Lambda=1)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), int)
+
+    def test_ga(self):
+
+        g = nx.karate_club_graph()
+
+        communities = algorithms.ga(g)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), int)
+
+    def test_belief(self):
+
+        g = nx.karate_club_graph()
+
+        communities = algorithms.belief(g)
+        self.assertEqual(type(communities.communities), list)
+        if len(communities.communities) > 0:
+            self.assertEqual(type(communities.communities[0]), list)
+            if len(communities.communities[0]) > 0:
+                self.assertEqual(type(communities.communities[0][0]), int)
+
+    def test_CPM_Bipartite(self):
+        g = ig.Graph.Erdos_Renyi(n=80, m=600)
+        g.vs["type"] = 0
+        g.vs[15:]["type"] = 1
+
+        coms = algorithms.CPM_Bipartite(g, 0.3)
+        self.assertEqual(type(coms.communities), list)
+        if len(coms.communities) > 0:
+            self.assertEqual(type(coms.communities[0]), list)
+            self.assertEqual(type(coms.communities[0][0]), int)
+
+        g = nx.algorithms.bipartite.random_graph(50, 50, 0.25)
+        coms = algorithms.CPM_Bipartite(g, 0.3)
+        self.assertEqual(type(coms.communities), list)
+        if len(coms.communities) > 0:
+            self.assertEqual(type(coms.communities[0]), list)
+            self.assertEqual(type(coms.communities[0][0]), int)
+
+    def test_infomap_Bipartite(self):
+        g = nx.algorithms.bipartite.random_graph(300, 100, 0.2)
+        coms = algorithms.infomap_bipartite(g)
+        self.assertEqual(type(coms.communities), list)
+        if len(coms.communities) > 0:
+            self.assertEqual(type(coms.communities[0]), list)
+            self.assertEqual(type(coms.communities[0][0]), int)
+
diff --git a/cdlib/test/test_datasets.py b/cdlib/test/test_datasets.py
new file mode 100644
index 00000000..9fed5d17
--- /dev/null
+++ b/cdlib/test/test_datasets.py
@@ -0,0 +1,41 @@
+import unittest
+from cdlib import datasets, NodeClustering
+import networkx as nx
+
+
+class DatasetCase(unittest.TestCase):
+
+    def test_net_list(self):
+        nets = datasets.available_networks()
+        self.assertIsInstance(nets, list)
+
+    def test_gt_list(self):
+        nets = datasets.available_ground_truths()
+        self.assertIsInstance(nets, list)
+
+    def test_net_remote(self):
+        gi = datasets.fetch_network_data(net_type="igraph", net_name="karate_club")
+        g = datasets.fetch_network_data(net_type="networkx", net_name="karate_club")
+        self.assertEqual(g.number_of_edges(), gi.ecount())
+
+    def test_gt_remote(self):
+        gt = datasets.fetch_ground_truth_data("karate_club")
+        self.assertIsInstance(gt, NodeClustering)
+
+    def test_combined(self):
+        g, gt = datasets.fetch_network_ground_truth("karate_club", net_type="networkx")
+        self.assertIsInstance(gt, NodeClustering)
+        self.assertIsInstance(g, nx.Graph)
+
+    def test_remote_data(self):
+        for net in datasets.available_networks():
+            gi = datasets.fetch_network_data(net_type="igraph", net_name=net)
+            self.assertIsNotNone(gi)
+
+        for net in datasets.available_ground_truths():
+            gi = datasets.fetch_network_ground_truth(net_name=net)
+            self.assertIsNotNone(gi)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/cdlib/test/test_io.py b/cdlib/test/test_io.py
index dd110fa2..2d79340b 100644
--- a/cdlib/test/test_io.py
+++ b/cdlib/test/test_io.py
@@ -16,6 +16,11 @@ def test_read_write(self):
         self.assertListEqual(communities.communities, communities_r.communities)
         os.remove("coms.csv")
 
+        readwrite.write_community_csv(communities, "coms.gzip", zip=True)
+        communities_r = readwrite.read_community_csv("coms.gzip", nodetype=int, zip=True)
+        self.assertListEqual(communities.communities, communities_r.communities)
+        os.remove("coms.gzip")
+
     def test_read_write_json(self):
         g = nx.karate_club_graph()
         communities = algorithms.louvain(g)
@@ -24,6 +29,12 @@ def test_read_write_json(self):
         self.assertListEqual(communities.communities, communities_r.communities)
         os.remove("coms.json")
 
+        communities = algorithms.louvain(g)
+        readwrite.write_community_json(communities, "coms.gzip", zip=True)
+        communities_r = readwrite.read_community_json("coms.gzip", zip=True)
+        self.assertListEqual(communities.communities, communities_r.communities)
+        os.remove("coms.gzip")
+
         communities = algorithms.frc_fgsn(g, 1, 0.5, 3)
         readwrite.write_community_json(communities, "coms.json")
         communities_r = readwrite.read_community_json("coms.json")