diff --git a/cdlib/algorithms/bipartite_clustering.py b/cdlib/algorithms/bipartite_clustering.py index 151744fc..f608f8ed 100644 --- a/cdlib/algorithms/bipartite_clustering.py +++ b/cdlib/algorithms/bipartite_clustering.py @@ -1,15 +1,21 @@ from cdlib import BiNodeClustering - import networkx as nx +from cdlib.utils import convert_graph_formats +from collections import defaultdict +from cdlib.algorithms.internal.pycondor import condor_object, initial_community, brim + +missing_packages = set() try: import infomap as imp except ModuleNotFoundError: + missing_packages.add("infomap") imp = None try: from wurlitzer import pipes except ModuleNotFoundError: + missing_packages.add("wurlitzer") pipes = None try: @@ -20,11 +26,14 @@ try: import leidenalg except ModuleNotFoundError: + missing_packages.add("leidenalg") leidenalg = None -from cdlib.utils import convert_graph_formats -from collections import defaultdict -from cdlib.algorithms.internal.pycondor import condor_object, initial_community, brim +if len(missing_packages) > 0: + print( + "Note: to be able to use all bipartite methods, you need to install some additional packages: ", + missing_packages, + ) __all__ = ["bimlpa", "CPM_Bipartite", "infomap_bipartite", "condor"] @@ -123,11 +132,15 @@ def CPM_Bipartite( .. note:: Reference implementation: https://leidenalg.readthedocs.io/en/stable/multiplex.html?highlight=bipartite#bipartite """ + global leidenalg if ig is None or leidenalg is None: - raise ModuleNotFoundError( - "Optional dependency not satisfied: install igraph and leidenalg to use the " - "selected feature." - ) + try: + import leidenalg + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install igraph and leidenalg to use the " + "selected feature." + ) g = convert_graph_formats(g_original, ig.Graph) @@ -201,14 +214,21 @@ def infomap_bipartite(g_original: object, flags: str = "") -> BiNodeClustering: .. note:: Infomap Python API documentation: https://mapequation.github.io/infomap/python/ """ + global imp, pipes if imp is None: - raise ModuleNotFoundError( - "Optional dependency not satisfied: install infomap to use the selected feature." - ) + try: + import infomap as imp + except ModuleNotFoundError: + 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." - ) + try: + from wurlitzer import pipes + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install package wurlitzer to use infomap." + ) g = convert_graph_formats(g_original, nx.Graph) diff --git a/cdlib/algorithms/crisp_partition.py b/cdlib/algorithms/crisp_partition.py index 075a2b11..0be2dcba 100644 --- a/cdlib/algorithms/crisp_partition.py +++ b/cdlib/algorithms/crisp_partition.py @@ -1,35 +1,11 @@ -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 - -try: - import graph_tool.all as gt -except ModuleNotFoundError: - gt = None - -import warnings +import sys import numpy as np from typing import Callable from copy import deepcopy from cdlib.algorithms.internal import DER -import community as louvain_modularity -import warnings + +# import community as louvain_modularity +from community import community_louvain from collections import defaultdict from cdlib import NodeClustering, FuzzyNodeClustering from cdlib.algorithms.internal.belief_prop import detect_belief_communities @@ -57,23 +33,12 @@ from cdlib.algorithms.internal.principled import principled from cdlib.algorithms.internal.MCODE import m_code from cdlib.algorithms.internal.RSC import rsc_evaluate_graph +import warnings -try: - from GraphRicciCurvature.OllivierRicci import OllivierRicci -except ModuleNotFoundError: - OllivierRicci = None - -try: - import pycombo as pycombo_part -except ModuleNotFoundError: - pycombo_part = None - -from karateclub import EdMot, GEMSEC, SCD import markov_clustering as mc from chinese_whispers import chinese_whispers as cw from chinese_whispers import aggregate_clusters from thresholdclustering.thresholdclustering import best_partition as th_best_partition - import networkx as nx from cdlib.utils import ( @@ -83,6 +48,59 @@ nx_node_integer_mapping, ) +missing_packages = set() + +try: + import infomap as imp +except ModuleNotFoundError: + missing_packages.add("infomap") + imp = None + +try: + from wurlitzer import pipes +except ModuleNotFoundError: + missing_packages.add("wurlitzer") + pipes = None + +try: + import igraph as ig +except ModuleNotFoundError: + ig = None + +try: + import leidenalg +except ModuleNotFoundError: + missing_packages.add("leidenalg") + leidenalg = None + +try: + import graph_tool.all as gt +except ModuleNotFoundError: + missing_packages.add("graph_tool") + gt = None + +try: + import karateclub +except ModuleNotFoundError: + missing_packages.add("karateclub") + + +if len(missing_packages) > 0: + print( + "Note: to be able to use all crisp methods, you need to install some additional packages: ", + missing_packages, + ) + +try: + from GraphRicciCurvature.OllivierRicci import OllivierRicci +except ModuleNotFoundError: + OllivierRicci = None + +try: + import pycombo as pycombo_part +except ModuleNotFoundError: + pycombo_part = None + __all__ = [ "louvain", "leiden", @@ -509,7 +527,7 @@ def louvain( g = convert_graph_formats(g_original, nx.Graph) - coms = louvain_modularity.best_partition( + coms = community_louvain.best_partition( g, weight=weight, resolution=resolution, randomize=randomize ) @@ -568,12 +586,15 @@ def leiden( .. note:: Reference implementation: https://github.com/vtraag/leidenalg """ - + global leidenalg if ig is None or leidenalg is None: - raise ModuleNotFoundError( - "Optional dependency not satisfied: install igraph and leidenalg to use the " - "selected feature." - ) + try: + import leidenalg + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install igraph and leidenalg to use the " + "selected feature." + ) g = convert_graph_formats(g_original, ig.Graph) @@ -1049,15 +1070,21 @@ def infomap(g_original: object, flags: str = "") -> NodeClustering: .. note:: Infomap Python API documentation: https://mapequation.github.io/infomap/python/ """ - + global imp, pipes if imp is None: - raise ModuleNotFoundError( - "Optional dependency not satisfied: install infomap to use the selected feature." - ) + try: + import infomap as imp + except ModuleNotFoundError: + 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." - ) + try: + from wurlitzer import pipes + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install package wurlitzer to use infomap." + ) g = convert_graph_formats(g_original, nx.Graph, directed=g_original.is_directed()) @@ -1445,12 +1472,15 @@ def sbm_dl( .. note:: Implementation from graph-tool library, please report to https://graph-tool.skewed.de for details """ + if gt is None: raise Exception( "===================================================== \n" "The graph-tool library seems not to be installed (or incorrectly installed). \n" - "Please check installation procedure there https://git.skewed.de/count0/graph-tool/wikis/installation-instructions#native-installation \n" - "on linux/mac, you can use package managers to do so(apt-get install python3-graph-tool, brew install graph-tool, etc.)" + "Please check installation procedure there " + "https://git.skewed.de/count0/graph-tool/wikis/installation-instructions#native-installation \n" + "on linux/mac, you can use package managers to do so " + "(apt-get install python3-graph-tool, brew install graph-tool, etc.)" ) gt_g = convert_graph_formats(g_original, nx.Graph) gt_g, label_map = __from_nx_to_graph_tool(gt_g) @@ -1498,13 +1528,21 @@ def sbm_dl_nested( .. note:: Implementation from graph-tool library, please report to https://graph-tool.skewed.de for details """ + global gt + if gt is None: - raise Exception( - "===================================================== \n" - "The graph-tool library seems not to be installed (or incorrectly installed). \n" - "Please check installation procedure there https://git.skewed.de/count0/graph-tool/wikis/installation-instructions#native-installation \n" - "on linux/mac, you can use package managers to do so(apt-get install python3-graph-tool, brew install graph-tool, etc.)" - ) + try: + import graph_tool.all as gt + except ModuleNotFoundError: + raise Exception( + "===================================================== \n" + "The graph-tool library seems not to be installed (or incorrectly installed). \n" + "Please check installation procedure " + "there https://git.skewed.de/count0/graph-tool/wikis/installation-instructions#native-installation \n" + "on linux/mac, you can use package managers to do so(apt-get install python3-graph-tool, " + "brew install graph-tool, etc.)" + ) + gt_g = convert_graph_formats(g_original, nx.Graph) gt_g, label_map = __from_nx_to_graph_tool(gt_g) state = gt.minimize_nested_blockmodel_dl(gt_g) @@ -1716,9 +1754,18 @@ def edmot( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + global karateclub + + if "karateclub" not in sys.modules: + try: + import karateclub + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install karateclub to use the selected feature." + ) g = convert_graph_formats(g_original, nx.Graph) - model = EdMot(component_count=2, cutoff=10) + model = karateclub.EdMot(component_count=2, cutoff=10) model.fit(g) members = model.get_memberships() @@ -2360,8 +2407,17 @@ def gemsec( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + global karateclub + if "karateclub" not in sys.modules: + try: + import karateclub + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install karateclub to use the selected feature." + ) + g = convert_graph_formats(g_original, nx.Graph) - model = GEMSEC( + model = karateclub.GEMSEC( walk_number=walk_number, walk_length=walk_length, dimensions=dimensions, @@ -2438,8 +2494,18 @@ def scd( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + global karateclub + + if "karateclub" not in sys.modules: + try: + import karateclub + except ModuleNotFoundError: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install karateclub to use the selected feature." + ) + g = convert_graph_formats(g_original, nx.Graph) - model = SCD(iterations=iterations, eps=eps, seed=seed) + model = karateclub.SCD(iterations=iterations, eps=eps, seed=seed) model.fit(g) members = model.get_memberships() diff --git a/cdlib/algorithms/overlapping_partition.py b/cdlib/algorithms/overlapping_partition.py index e8734377..16db1cea 100644 --- a/cdlib/algorithms/overlapping_partition.py +++ b/cdlib/algorithms/overlapping_partition.py @@ -1,17 +1,4 @@ -try: - import igraph as ig -except ModuleNotFoundError: - ig = None -try: - from angel import Angel -except ModuleNotFoundError: - Angel = None - -try: - from ASLPAw_package import ASLPAw -except ModuleNotFoundError: - ASLPAw = None - +import sys from random import sample from demon import Demon from cdlib.algorithms.internal.NodePerception import NodePerception @@ -31,7 +18,6 @@ from cdlib.algorithms.internal.PercoMCV import percoMVC from cdlib.algorithms.internal.LPAM import LPAM from cdlib.algorithms.internal.core_exp import findCommunities as core_exp_find -from karateclub import DANMF, EgoNetSplitter, NNSED, MNMF, BigClam, SymmNMF from cdlib.algorithms.internal.weightedCommunity import weightedCommunity from cdlib.algorithms.internal.LPANNI import LPANNI, GraphGenerator from cdlib.algorithms.internal.DCS import main_dcs @@ -46,6 +32,50 @@ endntm_find_overlap_cluster, endntm_evalFuction, ) +import warnings + +missing_packages = set() + + +def __try_load_karate(init=False): + global karateclub + if init == True or "karateclub" not in sys.modules: + try: + import karateclub + + except ModuleNotFoundError: + if not init: + raise ModuleNotFoundError( + "Optional dependency not satisfied: install karateclub to use the selected feature." + ) + + +__try_load_karate(init=True) +if "karateclub" not in sys.modules: + karateclub = None + missing_packages.add("karateclub") + + +try: + import igraph as ig +except ModuleNotFoundError: + ig = None +try: + from angel import Angel +except ModuleNotFoundError: + Angel = None + +try: + from ASLPAw_package import ASLPAw +except ModuleNotFoundError: + ASLPAw = None + missing_packages.add("ASLPAw") + +if len(missing_packages) > 0: + print( + "Note: to be able to use all overlapping methods, you need to install some additional packages: ", + missing_packages, + ) __all__ = [ "ego_networks", @@ -878,10 +908,10 @@ def big_clam( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ - + __try_load_karate() g = convert_graph_formats(g_original, nx.Graph) - model = BigClam( + model = karateclub.BigClam( dimensions=dimensions, iterations=iterations, learning_rate=learning_rate ) model.fit(g) @@ -949,8 +979,11 @@ def danmf( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + + __try_load_karate() + g = convert_graph_formats(g_original, nx.Graph) - model = DANMF(layers, pre_iterations, iterations, seed, lamb) + model = karateclub.DANMF(layers, pre_iterations, iterations, seed, lamb) mapping = {node: i for i, node in enumerate(g.nodes())} rev = {i: node for node, i in mapping.items()} @@ -1012,8 +1045,10 @@ def egonet_splitter(g_original: object, resolution: float = 1.0) -> NodeClusteri .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + __try_load_karate() + g = convert_graph_formats(g_original, nx.Graph) - model = EgoNetSplitter(resolution=resolution) + model = karateclub.EgoNetSplitter(resolution=resolution) mapping = {node: i for i, node in enumerate(g.nodes())} rev = {i: node for node, i in mapping.items()} @@ -1074,8 +1109,11 @@ def nnsed( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + + __try_load_karate() + g = convert_graph_formats(g_original, nx.Graph) - model = NNSED(dimensions=dimensions, iterations=iterations, seed=seed) + model = karateclub.NNSED(dimensions=dimensions, iterations=iterations, seed=seed) model.fit(g) members = model.get_memberships() @@ -1148,8 +1186,9 @@ def mnmf( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + __try_load_karate() g = convert_graph_formats(g_original, nx.Graph) - model = MNMF( + model = karateclub.MNMF( dimensions=dimensions, clusters=clusters, lambd=lambd, @@ -1601,8 +1640,11 @@ def symmnmf( .. note:: Reference implementation: https://karateclub.readthedocs.io/ """ + __try_load_karate() g = convert_graph_formats(g_original, nx.Graph) - model = SymmNMF(dimensions=dimensions, iterations=iterations, rho=rho, seed=seed) + model = karateclub.SymmNMF( + dimensions=dimensions, iterations=iterations, rho=rho, seed=seed + ) model.fit(g) members = model.get_memberships() diff --git a/cdlib/test/test_community_discovery_models.py b/cdlib/test/test_community_discovery_models.py index 2e52c8e6..232b87e4 100644 --- a/cdlib/test/test_community_discovery_models.py +++ b/cdlib/test/test_community_discovery_models.py @@ -5,6 +5,11 @@ import random import os +try: + import karateclub +except ModuleNotFoundError: + karateclub = None + try: import pycombo as pycombo_part except ModuleNotFoundError: @@ -348,6 +353,8 @@ def test_markov_clustering(self): self.assertEqual(type(communities.communities[0][0]), int) def test_bigClam(self): + if karateclub is None: + return g = nx.karate_club_graph() coms = algorithms.big_clam(g) self.assertEqual(type(coms.communities), list) @@ -478,6 +485,8 @@ def test_sbm_nested_dl(self): self.assertEqual(type(coms.communities[0][0]), str) def test_danmf(self): + if karateclub is None: + return g = get_string_graph() coms = algorithms.danmf(g) self.assertEqual(type(coms.communities), list) @@ -486,6 +495,8 @@ def test_danmf(self): self.assertEqual(type(coms.communities[0][0]), str) def test_egonet_splitter(self): + if karateclub is None: + return g = get_string_graph() coms = algorithms.egonet_splitter(g) self.assertEqual(type(coms.communities), list) @@ -494,6 +505,8 @@ def test_egonet_splitter(self): self.assertEqual(type(coms.communities[0][0]), str) def test_nnsed(self): + if karateclub is None: + return g = nx.karate_club_graph() coms = algorithms.nnsed(g) self.assertEqual(type(coms.communities), list) @@ -502,6 +515,8 @@ def test_nnsed(self): self.assertEqual(type(coms.communities[0][0]), int) def test_mnmf(self): + if karateclub is None: + return g = nx.karate_club_graph() coms = algorithms.mnmf(g) self.assertEqual(type(coms.communities), list) @@ -510,6 +525,8 @@ def test_mnmf(self): self.assertEqual(type(coms.communities[0][0]), int) def test_edmot(self): + if karateclub is None: + return g = nx.karate_club_graph() coms = algorithms.edmot(g) self.assertEqual(type(coms.communities), list) @@ -625,7 +642,8 @@ def test_CPM_Bipartite(self): g = ig.Graph.Erdos_Renyi(n=80, m=600) g.vs["type"] = 0 g.vs[15:]["type"] = 1 - + if leidenalg is None: + return coms = algorithms.CPM_Bipartite(g, 0.3) self.assertEqual(type(coms.communities), list) if len(coms.communities) > 0: @@ -640,6 +658,8 @@ def test_CPM_Bipartite(self): self.assertEqual(type(coms.communities[0][0]), int) def test_infomap_Bipartite(self): + if infomap is None: + return g = nx.algorithms.bipartite.random_graph(300, 100, 0.2) coms = algorithms.infomap_bipartite(g) self.assertEqual(type(coms.communities), list) @@ -788,6 +808,8 @@ def test_kcut(self): print("Kcut error to be checked (conda packaging)") def test_symmnmf(self): + if karateclub is None: + return G = nx.karate_club_graph() coms = algorithms.symmnmf(G) @@ -806,6 +828,8 @@ def test_scd(self): self.assertEqual(type(coms.communities[0][0]), int) def test_gemsec(self): + if karateclub is None: + return G = nx.karate_club_graph() coms = algorithms.gemsec(G) diff --git a/cdlib/viz/networks.py b/cdlib/viz/networks.py index 0f9cffc9..ec8eb8bb 100644 --- a/cdlib/viz/networks.py +++ b/cdlib/viz/networks.py @@ -5,7 +5,7 @@ import networkx as nx from cdlib import NodeClustering from cdlib.utils import convert_graph_formats -from community import induced_graph +from community import community_louvain __all__ = ["plot_network_clusters", "plot_community_graph"] @@ -202,7 +202,7 @@ def plot_community_graph( s = nx.subgraph(graph, node_to_com.keys()) # algorithms graph construction - c_graph = induced_graph(node_to_com, s) + c_graph = community_louvain.induced_graph(node_to_com, s) node_cms = [[node] for node in c_graph.nodes()] return plot_network_clusters( diff --git a/conda/meta.yaml b/conda/meta.yaml index 33400fe4..7d3780ab 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,5 +1,5 @@ {% set name = "cdlib" %} -{% set version = "0.2.5" %} +{% set version = "0.2.6" %} package: name: "{{ name|lower }}" @@ -45,8 +45,6 @@ requirements: - demon - dynetx - eva_lcd - - karateclub - - leidenalg - markov_clustering - matplotlib - networkx>=2.4 @@ -65,7 +63,6 @@ requirements: - seaborn - thresholdclustering - tqdm - - wurlitzer test: imports: diff --git a/docs/conf.py b/docs/conf.py index da37cc17..0c48f392 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -134,9 +134,9 @@ def __getattr__(cls, name): # built documents. # # The short X.Y version. -version = u"0.2.5" +version = u"0.2.6" # The full version, including alpha/beta/rc tags. -release = u"0.2.5" +release = u"0.2.6" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/environment.yml b/environment.yml index 3b737b64..10eae4d6 100644 --- a/environment.yml +++ b/environment.yml @@ -14,17 +14,13 @@ dependencies: - seaborn - pandas - eva_lcd -- karateclub - bimlpa - markov_clustering - chinese_whispers - python-igraph -- leidenalg - angel_cd - pooch - dynetx -- python-infomap -- wurlitzer - thresholdclustering - pyclustering - python-Levenshtein diff --git a/requirements.txt b/requirements.txt index ee42ae70..229b478c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,24 +5,20 @@ scikit-learn tqdm networkx>=2.4 demon -python-louvain +python-louvain>=0.16 nf1 scipy pulp seaborn pandas eva_lcd -karateclub bimlpa markov_clustering chinese_whispers python-igraph -leidenalg angel-cd pooch dynetx -infomap -wurlitzer thresholdclustering pyclustering cython diff --git a/requirements_optional.txt b/requirements_optional.txt index d90da8d1..c328df25 100644 --- a/requirements_optional.txt +++ b/requirements_optional.txt @@ -1,5 +1,7 @@ -infomap>=1.3.0 -wurlitzer>=1.0.2 GraphRicciCurvature==0.5.2.1 networkit pycombo +karateclub +leidenalg +infomap>=1.3.0 +wurlitzer>=1.0.2 \ No newline at end of file diff --git a/setup.py b/setup.py index 786f365c..7ac88ec6 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup(name='cdlib', - version='0.2.5', + version='0.2.6', license='BSD-Clause-2', description='Community Discovery Library', url='https://github.com/GiulioRossetti/cdlib', @@ -51,7 +51,7 @@ long_description=long_description, long_description_content_type='text/markdown', extras_require={ - 'C': ["infomap>=1.3.0", "wurlitzer>=1.0.2", "GraphRicciCurvature", "networkit", "pycombo"], + 'C': ["infomap>=1.3.0", "wurlitzer>=1.0.2", "GraphRicciCurvature", "networkit", "pycombo","leidenalg","karateclub"], }, packages=find_packages(exclude=["*.test", "*.test.*", "test.*", "test", "cdlib.test", "cdlib.test.*"]), )