From 3646180c78293550e99dff05414f8daac3fea6c0 Mon Sep 17 00:00:00 2001 From: Rick Ratzel Date: Mon, 27 Sep 2021 20:17:05 -0500 Subject: [PATCH 1/4] Fixed example formatting for RMAT docstring, added utility to check for Nx types without requiring Nx. --- .../centrality/betweenness_centrality.py | 5 +- .../cugraph/centrality/katz_centrality.py | 7 +- python/cugraph/cugraph/community/ecg.py | 7 +- python/cugraph/cugraph/community/egonet.py | 9 +- .../cugraph/community/ktruss_subgraph.py | 7 +- python/cugraph/cugraph/community/leiden.py | 7 +- python/cugraph/cugraph/community/louvain.py | 7 +- .../cugraph/community/spectral_clustering.py | 13 +- .../cugraph/community/subgraph_extraction.py | 8 +- .../cugraph/community/triangle_count.py | 4 +- .../cugraph/components/connectivity.py | 27 +-- python/cugraph/cugraph/cores/core_number.py | 7 +- python/cugraph/cugraph/cores/k_core.py | 7 +- python/cugraph/cugraph/generators/rmat.py | 30 ++-- python/cugraph/cugraph/layout/force_atlas2.py | 4 +- python/cugraph/cugraph/link_analysis/hits.py | 7 +- .../cugraph/cugraph/link_analysis/pagerank.py | 11 +- .../cugraph/link_prediction/jaccard.py | 9 +- .../cugraph/link_prediction/overlap.py | 9 +- .../cugraph/link_prediction/sorensen.py | 9 +- .../cugraph/cugraph/sampling/random_walks.py | 4 +- .../cugraph/structure/graph_classes.py | 10 +- python/cugraph/cugraph/traversal/bfs.py | 27 +-- python/cugraph/cugraph/traversal/sssp.py | 27 +-- .../cugraph/tree/minimum_spanning_tree.py | 9 +- .../tree/minimum_spanning_tree_wrapper.pyx | 4 +- python/cugraph/cugraph/utilities/__init__.py | 7 +- .../cugraph/cugraph/utilities/nx_factory.py | 50 +----- python/cugraph/cugraph/utilities/utils.py | 156 +++++++++++++----- 29 files changed, 244 insertions(+), 244 deletions(-) diff --git a/python/cugraph/cugraph/centrality/betweenness_centrality.py b/python/cugraph/cugraph/centrality/betweenness_centrality.py index 3b7cfe6b68f..1dc79e39eb5 100644 --- a/python/cugraph/cugraph/centrality/betweenness_centrality.py +++ b/python/cugraph/cugraph/centrality/betweenness_centrality.py @@ -18,6 +18,7 @@ from cugraph.centrality import edge_betweenness_centrality_wrapper from cugraph.utilities import df_edge_score_to_dictionary from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import ensure_cugraph_obj_for_nx import cugraph @@ -127,7 +128,7 @@ def betweenness_centrality( if result_dtype not in [np.float32, np.float64]: raise TypeError("result type can only be np.float32 or np.float64") - G, isNx = cugraph.utilities.check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) vertices = _initialize_vertices(G, k, seed) @@ -249,7 +250,7 @@ def edge_betweenness_centrality( if result_dtype not in [np.float32, np.float64]: raise TypeError("result type can only be np.float32 or np.float64") - G, isNx = cugraph.utilities.check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) vertices = _initialize_vertices(G, k, seed) df = edge_betweenness_centrality_wrapper.edge_betweenness_centrality( diff --git a/python/cugraph/cugraph/centrality/katz_centrality.py b/python/cugraph/cugraph/centrality/katz_centrality.py index 19432429f1e..af6c73dc366 100644 --- a/python/cugraph/cugraph/centrality/katz_centrality.py +++ b/python/cugraph/cugraph/centrality/katz_centrality.py @@ -12,6 +12,9 @@ # limitations under the License. from cugraph.centrality import katz_centrality_wrapper +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) import cugraph @@ -112,7 +115,7 @@ def katz_centrality( "currently not supported" ) - G, isNx = cugraph.utilities.check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if nstart is not None: if G.renumbered is True: @@ -130,7 +133,7 @@ def katz_centrality( df = G.unrenumber(df, "vertex") if isNx is True: - dict = cugraph.utilities.df_score_to_dictionary(df, 'katz_centrality') + dict = df_score_to_dictionary(df, 'katz_centrality') return dict else: return df diff --git a/python/cugraph/cugraph/community/ecg.py b/python/cugraph/cugraph/community/ecg.py index eb27f5d7ae0..17deacecd6c 100644 --- a/python/cugraph/cugraph/community/ecg.py +++ b/python/cugraph/cugraph/community/ecg.py @@ -12,8 +12,9 @@ # limitations under the License. from cugraph.community import ecg_wrapper -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) def ecg(input_graph, min_weight=0.05, ensemble_size=16, weight=None): @@ -70,7 +71,7 @@ def ecg(input_graph, min_weight=0.05, ensemble_size=16, weight=None): """ - input_graph, isNx = check_nx_graph(input_graph, weight) + input_graph, isNx = ensure_cugraph_obj_for_nx(input_graph, weight) parts = ecg_wrapper.ecg(input_graph, min_weight, ensemble_size) diff --git a/python/cugraph/cugraph/community/egonet.py b/python/cugraph/cugraph/community/egonet.py index 5ae025f1203..2fcafa5795b 100644 --- a/python/cugraph/cugraph/community/egonet.py +++ b/python/cugraph/cugraph/community/egonet.py @@ -15,20 +15,17 @@ import cudf from cugraph.utilities import ( ensure_cugraph_obj, - import_optional, + is_nx_graph_type, ) from cugraph.utilities import cugraph_to_nx -# optional dependencies used for handling different input types -nx = import_optional("networkx") - def _convert_graph_to_output_type(G, input_type): """ Given a cugraph.Graph, convert it to a new type appropriate for the graph algos in this module, based on input_type. """ - if (nx is not None) and (input_type in [nx.Graph, nx.DiGraph]): + if is_nx_graph_type(G): return cugraph_to_nx(G) else: @@ -40,7 +37,7 @@ def _convert_df_series_to_output_type(df, offsets, input_type): Given a cudf.DataFrame df, convert it to a new type appropriate for the graph algos in this module, based on input_type. """ - if (nx is not None) and (input_type in [nx.Graph, nx.DiGraph]): + if is_nx_graph_type(input_type): return df.to_pandas(), offsets.values_host.tolist() else: diff --git a/python/cugraph/cugraph/community/ktruss_subgraph.py b/python/cugraph/cugraph/community/ktruss_subgraph.py index afa7d66d31d..599108ca4cf 100644 --- a/python/cugraph/cugraph/community/ktruss_subgraph.py +++ b/python/cugraph/cugraph/community/ktruss_subgraph.py @@ -13,8 +13,9 @@ from cugraph.community import ktruss_subgraph_wrapper from cugraph.structure.graph_classes import Graph -from cugraph.utilities import check_nx_graph -from cugraph.utilities import cugraph_to_nx +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + cugraph_to_nx, + ) from numba import cuda @@ -67,7 +68,7 @@ def k_truss(G, k): _ensure_compatible_cuda_version() - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if isNx is True: k_sub = ktruss_subgraph(G, k) diff --git a/python/cugraph/cugraph/community/leiden.py b/python/cugraph/cugraph/community/leiden.py index 641cf552192..b2f868a45c0 100644 --- a/python/cugraph/cugraph/community/leiden.py +++ b/python/cugraph/cugraph/community/leiden.py @@ -13,8 +13,9 @@ from cugraph.community import leiden_wrapper from cugraph.structure.graph_classes import Graph -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) def leiden(G, max_iter=100, resolution=1.): @@ -72,7 +73,7 @@ def leiden(G, max_iter=100, resolution=1.): >>> G.from_cudf_edgelist(M, source='0', destination='1') >>> parts, modularity_score = cugraph.leiden(G) """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if type(G) is not Graph: raise Exception(f"input graph must be undirected was {type(G)}") diff --git a/python/cugraph/cugraph/community/louvain.py b/python/cugraph/cugraph/community/louvain.py index a761e060038..2261cc8ae85 100644 --- a/python/cugraph/cugraph/community/louvain.py +++ b/python/cugraph/cugraph/community/louvain.py @@ -13,8 +13,9 @@ from cugraph.community import louvain_wrapper from cugraph.structure.graph_classes import Graph -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) def louvain(G, max_iter=100, resolution=1.): @@ -73,7 +74,7 @@ def louvain(G, max_iter=100, resolution=1.): >>> parts, modularity_score = cugraph.louvain(G) """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if type(G) is not Graph: raise Exception("input graph must be undirected") diff --git a/python/cugraph/cugraph/community/spectral_clustering.py b/python/cugraph/cugraph/community/spectral_clustering.py index 06294af00c9..0e9579b6b44 100644 --- a/python/cugraph/cugraph/community/spectral_clustering.py +++ b/python/cugraph/cugraph/community/spectral_clustering.py @@ -12,8 +12,9 @@ # limitations under the License. from cugraph.community import spectral_clustering_wrapper -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) def spectralBalancedCutClustering( @@ -75,7 +76,7 @@ def spectralBalancedCutClustering( # Error checking in C++ code - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) df = spectral_clustering_wrapper.spectralBalancedCutClustering( G, @@ -152,7 +153,7 @@ def spectralModularityMaximizationClustering( # Error checking in C++ code - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) df = spectral_clustering_wrapper.spectralModularityMaximizationClustering( G, @@ -222,7 +223,7 @@ def analyzeClustering_modularity(G, n_clusters, clustering, if type(cluster_col_name) is not str: raise Exception("cluster_col_name must be a string") - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if G.renumbered: clustering = G.add_internal_vertex_id(clustering, @@ -288,7 +289,7 @@ def analyzeClustering_edge_cut(G, n_clusters, clustering, if type(cluster_col_name) is not str: raise Exception("cluster_col_name must be a string") - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if G.renumbered: clustering = G.add_internal_vertex_id(clustering, diff --git a/python/cugraph/cugraph/community/subgraph_extraction.py b/python/cugraph/cugraph/community/subgraph_extraction.py index 2df6e037d71..14173311a94 100644 --- a/python/cugraph/cugraph/community/subgraph_extraction.py +++ b/python/cugraph/cugraph/community/subgraph_extraction.py @@ -12,9 +12,11 @@ # limitations under the License. from cugraph.community import subgraph_extraction_wrapper -from cugraph.utilities import check_nx_graph +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + cugraph_to_nx, + ) + import cudf -from cugraph.utilities import cugraph_to_nx def subgraph(G, vertices): @@ -53,7 +55,7 @@ def subgraph(G, vertices): >>> Sg = cugraph.subgraph(G, sverts) """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if G.renumbered: if isinstance(vertices, cudf.DataFrame): diff --git a/python/cugraph/cugraph/community/triangle_count.py b/python/cugraph/cugraph/community/triangle_count.py index d28424a513e..fce339a1b7c 100644 --- a/python/cugraph/cugraph/community/triangle_count.py +++ b/python/cugraph/cugraph/community/triangle_count.py @@ -13,7 +13,7 @@ from cugraph.community import triangle_count_wrapper from cugraph.structure.graph_classes import Graph -from cugraph.utilities import check_nx_graph +from cugraph.utilities import ensure_cugraph_obj_for_nx def triangles(G): @@ -47,7 +47,7 @@ def triangles(G): >>> count = cugraph.triangles(G) """ - G, _ = check_nx_graph(G) + G, _ = ensure_cugraph_obj_for_nx(G) if type(G) is not Graph: raise Exception("input graph must be undirected") diff --git a/python/cugraph/cugraph/components/connectivity.py b/python/cugraph/cugraph/components/connectivity.py index 94eea312fb9..c1fdc6ac135 100644 --- a/python/cugraph/cugraph/components/connectivity.py +++ b/python/cugraph/cugraph/components/connectivity.py @@ -16,30 +16,12 @@ ensure_cugraph_obj, is_matrix_type, is_cp_matrix_type, - import_optional, + is_nx_graph_type, + cupy_package as cp, ) from cugraph.structure import Graph, DiGraph from cugraph.components import connectivity_wrapper -# optional dependencies used for handling different input types -nx = import_optional("networkx") - -cp = import_optional("cupy") -cp_coo_matrix = import_optional("coo_matrix", - import_from="cupyx.scipy.sparse.coo") -cp_csr_matrix = import_optional("csr_matrix", - import_from="cupyx.scipy.sparse.csr") -cp_csc_matrix = import_optional("csc_matrix", - import_from="cupyx.scipy.sparse.csc") - -sp = import_optional("scipy") -sp_coo_matrix = import_optional("coo_matrix", - import_from="scipy.sparse.coo") -sp_csr_matrix = import_optional("csr_matrix", - import_from="scipy.sparse.csr") -sp_csc_matrix = import_optional("csc_matrix", - import_from="scipy.sparse.csc") - def _ensure_args(api_name, G, directed, connection, return_labels): """ @@ -49,8 +31,7 @@ def _ensure_args(api_name, G, directed, connection, return_labels): """ G_type = type(G) # Check for Graph-type inputs and set defaults if unset - if (G_type in [Graph, DiGraph]) or \ - ((nx is not None) and (G_type in [nx.Graph, nx.DiGraph])): + if (G_type in [Graph, DiGraph]) or is_nx_graph_type(G_type): exc_value = "'%s' cannot be specified for a Graph-type input" if directed is not None: raise TypeError(exc_value % "directed") @@ -92,7 +73,7 @@ def _convert_df_to_output_type(df, input_type, return_labels): if input_type in [Graph, DiGraph]: return df - elif (nx is not None) and (input_type in [nx.Graph, nx.DiGraph]): + elif is_nx_graph_type(input_type): return df_score_to_dictionary(df, "labels", "vertex") elif is_matrix_type(input_type): diff --git a/python/cugraph/cugraph/cores/core_number.py b/python/cugraph/cugraph/cores/core_number.py index f1f6de3f0de..0d0fc0ea369 100644 --- a/python/cugraph/cugraph/cores/core_number.py +++ b/python/cugraph/cugraph/cores/core_number.py @@ -12,8 +12,9 @@ # limitations under the License. from cugraph.cores import core_number_wrapper -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) def core_number(G): @@ -52,7 +53,7 @@ def core_number(G): >>> cn = cugraph.core_number(G) """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) df = core_number_wrapper.core_number(G) diff --git a/python/cugraph/cugraph/cores/k_core.py b/python/cugraph/cugraph/cores/k_core.py index 17a3baf9c4c..be720cab30c 100644 --- a/python/cugraph/cugraph/cores/k_core.py +++ b/python/cugraph/cugraph/cores/k_core.py @@ -12,8 +12,9 @@ # limitations under the License. from cugraph.cores import k_core_wrapper, core_number_wrapper -from cugraph.utilities import cugraph_to_nx -from cugraph.utilities import check_nx_graph +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + cugraph_to_nx, + ) from cugraph.structure.graph_classes import Graph @@ -59,7 +60,7 @@ def k_core(G, k=None, core_number=None): >>> KCoreGraph = cugraph.k_core(G) """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) mytype = type(G) KCoreGraph = mytype() diff --git a/python/cugraph/cugraph/generators/rmat.py b/python/cugraph/cugraph/generators/rmat.py index 8077a6fbacd..859f99b87c4 100644 --- a/python/cugraph/cugraph/generators/rmat.py +++ b/python/cugraph/cugraph/generators/rmat.py @@ -290,21 +290,21 @@ def rmat( Examples -------- - import cugraph - from cugraph.generators import rmat - - df = rmat( - scale, - (2**scale)*edgefactor, - 0.1, - 0.2, - 0.3, - seed or 42, - clip_and_flip=False, - scramble_vertex_ids=True, - create_using=None, # return edgelist instead of Graph instance - mg=False - ) + >>> import cugraph + >>> from cugraph.generators import rmat + >>> + >>> df = rmat( + ... scale, + ... (2**scale)*edgefactor, + ... 0.1, + ... 0.2, + ... 0.3, + ... seed or 42, + ... clip_and_flip=False, + ... scramble_vertex_ids=True, + ... create_using=None, # return edgelist instead of Graph instance + ... mg=False + ... ) """ _ensure_args_rmat(scale, num_edges, a, b, c, seed, clip_and_flip, diff --git a/python/cugraph/cugraph/layout/force_atlas2.py b/python/cugraph/cugraph/layout/force_atlas2.py index 00d920e1921..623879735d1 100644 --- a/python/cugraph/cugraph/layout/force_atlas2.py +++ b/python/cugraph/cugraph/layout/force_atlas2.py @@ -12,7 +12,7 @@ # limitations under the License. from cugraph.layout import force_atlas2_wrapper -import cugraph +from cugraph.utilities import ensure_cugraph_obj_for_nx def force_atlas2( @@ -107,7 +107,7 @@ def on_train_end(self, positions): GPU data frame of size V containing three columns: the vertex identifiers and the x and y positions. """ - input_graph, isNx = cugraph.utilities.check_nx_graph(input_graph) + input_graph, isNx = ensure_cugraph_obj_for_nx(input_graph) if pos_list is not None: if input_graph.renumbered is True: diff --git a/python/cugraph/cugraph/link_analysis/hits.py b/python/cugraph/cugraph/link_analysis/hits.py index f7510a6a0e9..7176d1c66a1 100644 --- a/python/cugraph/cugraph/link_analysis/hits.py +++ b/python/cugraph/cugraph/link_analysis/hits.py @@ -12,8 +12,9 @@ # limitations under the License. from cugraph.link_analysis import hits_wrapper -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_score_to_dictionary +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) def hits(G, max_iter=100, tol=1.0e-5, nstart=None, normalized=True): @@ -74,7 +75,7 @@ def hits(G, max_iter=100, tol=1.0e-5, nstart=None, normalized=True): >>> hits = cugraph.hits(G, max_iter = 50) """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) df = hits_wrapper.hits(G, max_iter, tol) diff --git a/python/cugraph/cugraph/link_analysis/pagerank.py b/python/cugraph/cugraph/link_analysis/pagerank.py index b875246fb03..6bb2fcff214 100644 --- a/python/cugraph/cugraph/link_analysis/pagerank.py +++ b/python/cugraph/cugraph/link_analysis/pagerank.py @@ -11,10 +11,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from cugraph.link_analysis import pagerank_wrapper -import cugraph import cudf +from cugraph.link_analysis import pagerank_wrapper +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_score_to_dictionary, + ) + def pagerank( G, alpha=0.85, personalization=None, max_iter=100, tol=1.0e-5, nstart=None, @@ -95,7 +98,7 @@ def pagerank( >>> pr = cugraph.pagerank(G, alpha = 0.85, max_iter = 500, tol = 1.0e-05) """ - G, isNx = cugraph.utilities.check_nx_graph(G, weight) + G, isNx = ensure_cugraph_obj_for_nx(G, weight) if personalization is not None: if not isinstance(personalization, cudf.DataFrame): @@ -130,6 +133,6 @@ def pagerank( df = G.unrenumber(df, "vertex") if isNx is True: - return cugraph.utilities.df_score_to_dictionary(df, 'pagerank') + return df_score_to_dictionary(df, 'pagerank') else: return df diff --git a/python/cugraph/cugraph/link_prediction/jaccard.py b/python/cugraph/cugraph/link_prediction/jaccard.py index 689f5d2e768..e1e4b9c875e 100644 --- a/python/cugraph/cugraph/link_prediction/jaccard.py +++ b/python/cugraph/cugraph/link_prediction/jaccard.py @@ -14,9 +14,10 @@ import cudf from cugraph.structure.graph_classes import Graph from cugraph.link_prediction import jaccard_wrapper -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_edge_score_to_dictionary -from cugraph.utilities import renumber_vertex_pair +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_edge_score_to_dictionary, + renumber_vertex_pair, + ) def jaccard(input_graph, vertex_pair=None): @@ -168,7 +169,7 @@ def jaccard_coefficient(G, ebunch=None): """ vertex_pair = None - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if isNx is True and ebunch is not None: vertex_pair = cudf.DataFrame(ebunch) diff --git a/python/cugraph/cugraph/link_prediction/overlap.py b/python/cugraph/cugraph/link_prediction/overlap.py index 52f39a7733c..f37a9f94d92 100644 --- a/python/cugraph/cugraph/link_prediction/overlap.py +++ b/python/cugraph/cugraph/link_prediction/overlap.py @@ -13,9 +13,10 @@ from cugraph.link_prediction import overlap_wrapper import cudf -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_edge_score_to_dictionary -from cugraph.utilities import renumber_vertex_pair +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_edge_score_to_dictionary, + renumber_vertex_pair, + ) def overlap_coefficient(G, ebunch=None): @@ -25,7 +26,7 @@ def overlap_coefficient(G, ebunch=None): """ vertex_pair = None - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if isNx is True and ebunch is not None: vertex_pair = cudf.DataFrame(ebunch) diff --git a/python/cugraph/cugraph/link_prediction/sorensen.py b/python/cugraph/cugraph/link_prediction/sorensen.py index 2499f11d6b6..376eaaeef1b 100644 --- a/python/cugraph/cugraph/link_prediction/sorensen.py +++ b/python/cugraph/cugraph/link_prediction/sorensen.py @@ -14,9 +14,10 @@ import cudf from cugraph.structure.graph_classes import Graph from cugraph.link_prediction import jaccard_wrapper -from cugraph.utilities import check_nx_graph -from cugraph.utilities import df_edge_score_to_dictionary -from cugraph.utilities import renumber_vertex_pair +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + df_edge_score_to_dictionary, + renumber_vertex_pair, + ) def sorensen(input_graph, vertex_pair=None): @@ -136,7 +137,7 @@ def sorensen_coefficient(G, ebunch=None): """ vertex_pair = None - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if isNx is True and ebunch is not None: vertex_pair = cudf.DataFrame(ebunch) diff --git a/python/cugraph/cugraph/sampling/random_walks.py b/python/cugraph/cugraph/sampling/random_walks.py index fc21abd3bc4..69ea6733f6a 100644 --- a/python/cugraph/cugraph/sampling/random_walks.py +++ b/python/cugraph/cugraph/sampling/random_walks.py @@ -13,7 +13,7 @@ import cudf from cugraph.sampling import random_walks_wrapper -import cugraph +from cugraph.utilities import ensure_cugraph_obj_for_nx def random_walks(G, @@ -57,7 +57,7 @@ def random_walks(G, if max_depth is None: raise TypeError("must specify a 'max_depth'") - G, _ = cugraph.utilities.check_nx_graph(G) + G, _ = ensure_cugraph_obj_for_nx(G) if start_vertices is int: start_vertices = [start_vertices] diff --git a/python/cugraph/cugraph/structure/graph_classes.py b/python/cugraph/cugraph/structure/graph_classes.py index f8a944442fb..744e19966ab 100644 --- a/python/cugraph/cugraph/structure/graph_classes.py +++ b/python/cugraph/cugraph/structure/graph_classes.py @@ -298,12 +298,8 @@ def from_pandas_edgelist( >>> G.from_pandas_edgelist(df, source='0', destination='1', edge_attr='2', renumber=False) """ - if pd is None: - raise RuntimeError("Pandas could not be imported, " - "cannot convert from pandas") - if not isinstance(pdf, pd.core.frame.DataFrame): - raise Exception("pdf input is not a Pandas DataFrame") + raise TypeError("pdf input is not a Pandas DataFrame") gdf = cudf.DataFrame.from_pandas(pdf) self.from_cudf_edgelist(gdf, source=source, destination=destination, @@ -318,10 +314,6 @@ def from_pandas_adjacency(self, pdf): pdf : pandas.DataFrame A DataFrame that contains adjacency information """ - if pd is None: - raise RuntimeError("Pandas could not be imported, " - "cannot convert from pandas") - if not isinstance(pdf, pd.core.frame.DataFrame): raise TypeError("pdf input is not a Pandas DataFrame") diff --git a/python/cugraph/cugraph/traversal/bfs.py b/python/cugraph/cugraph/traversal/bfs.py index 0fb415de0c4..127ab3d0ba3 100644 --- a/python/cugraph/cugraph/traversal/bfs.py +++ b/python/cugraph/cugraph/traversal/bfs.py @@ -18,28 +18,10 @@ from cugraph.utilities import (ensure_cugraph_obj, is_matrix_type, is_cp_matrix_type, - import_optional, + is_nx_graph_type, + cupy_package as cp, ) -# optional dependencies used for handling different input types -nx = import_optional("networkx") - -cp = import_optional("cupy") -cp_coo_matrix = import_optional("coo_matrix", - import_from="cupyx.scipy.sparse.coo") -cp_csr_matrix = import_optional("csr_matrix", - import_from="cupyx.scipy.sparse.csr") -cp_csc_matrix = import_optional("csc_matrix", - import_from="cupyx.scipy.sparse.csc") - -sp = import_optional("scipy") -sp_coo_matrix = import_optional("coo_matrix", - import_from="scipy.sparse.coo") -sp_csr_matrix = import_optional("csr_matrix", - import_from="scipy.sparse.csr") -sp_csc_matrix = import_optional("csc_matrix", - import_from="scipy.sparse.csc") - def _ensure_args(G, start, i_start, directed): """ @@ -55,8 +37,7 @@ def _ensure_args(G, start, i_start, directed): G_type = type(G) # Check for Graph-type inputs - if (G_type in [Graph, DiGraph]) or \ - ((nx is not None) and (G_type in [nx.Graph, nx.DiGraph])): + if (G_type in [Graph, DiGraph]) or is_nx_graph_type(G_type): if directed is not None: raise TypeError("'directed' cannot be specified for a " "Graph-type input") @@ -76,7 +57,7 @@ def _convert_df_to_output_type(df, input_type): if input_type in [Graph, DiGraph]: return df - elif (nx is not None) and (input_type in [nx.Graph, nx.DiGraph]): + elif is_nx_graph_type(input_type): return df.to_pandas() elif is_matrix_type(input_type): diff --git a/python/cugraph/cugraph/traversal/sssp.py b/python/cugraph/cugraph/traversal/sssp.py index f3aebaf43bf..5d7041a4287 100644 --- a/python/cugraph/cugraph/traversal/sssp.py +++ b/python/cugraph/cugraph/traversal/sssp.py @@ -19,28 +19,10 @@ from cugraph.utilities import (ensure_cugraph_obj, is_matrix_type, is_cp_matrix_type, - import_optional, + is_nx_graph_type, + cupy_package as cp, ) -# optional dependencies used for handling different input types -nx = import_optional("networkx") - -cp = import_optional("cupy") -cp_coo_matrix = import_optional("coo_matrix", - import_from="cupyx.scipy.sparse.coo") -cp_csr_matrix = import_optional("csr_matrix", - import_from="cupyx.scipy.sparse.csr") -cp_csc_matrix = import_optional("csc_matrix", - import_from="cupyx.scipy.sparse.csc") - -sp = import_optional("scipy") -sp_coo_matrix = import_optional("coo_matrix", - import_from="scipy.sparse.coo") -sp_csr_matrix = import_optional("csr_matrix", - import_from="scipy.sparse.csr") -sp_csc_matrix = import_optional("csc_matrix", - import_from="scipy.sparse.csc") - def _ensure_args(G, source, method, directed, return_predecessors, unweighted, overwrite, indices): @@ -61,8 +43,7 @@ def _ensure_args(G, source, method, directed, G_type = type(G) # Check for Graph-type inputs - if (G_type in [Graph, DiGraph]) or \ - ((nx is not None) and (G_type in [nx.Graph, nx.DiGraph])): + if (G_type in [Graph, DiGraph]) or is_nx_graph_type(G_type): exc_value = "'%s' cannot be specified for a Graph-type input" if directed is not None: raise TypeError(exc_value % "directed") @@ -107,7 +88,7 @@ def _convert_df_to_output_type(df, input_type, return_predecessors): if input_type in [Graph, DiGraph, MultiGraph, MultiDiGraph]: return df - elif (nx is not None) and (input_type in [nx.Graph, nx.DiGraph]): + elif is_nx_graph_type(input_type): return df.to_pandas() elif is_matrix_type(input_type): diff --git a/python/cugraph/cugraph/tree/minimum_spanning_tree.py b/python/cugraph/cugraph/tree/minimum_spanning_tree.py index 6a5f7b5bf38..eebc4f10b56 100644 --- a/python/cugraph/cugraph/tree/minimum_spanning_tree.py +++ b/python/cugraph/cugraph/tree/minimum_spanning_tree.py @@ -13,8 +13,9 @@ from cugraph.tree import minimum_spanning_tree_wrapper from cugraph.structure.graph_classes import Graph -from cugraph.utilities import check_nx_graph -from cugraph.utilities import cugraph_to_nx +from cugraph.utilities import (ensure_cugraph_obj_for_nx, + cugraph_to_nx, + ) def _minimum_spanning_tree_subgraph(G): @@ -88,7 +89,7 @@ def minimum_spanning_tree( """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if isNx is True: mst = _minimum_spanning_tree_subgraph(G) @@ -128,7 +129,7 @@ def maximum_spanning_tree( """ - G, isNx = check_nx_graph(G) + G, isNx = ensure_cugraph_obj_for_nx(G) if isNx is True: mst = _maximum_spanning_tree_subgraph(G) diff --git a/python/cugraph/cugraph/tree/minimum_spanning_tree_wrapper.pyx b/python/cugraph/cugraph/tree/minimum_spanning_tree_wrapper.pyx index 3068b45418d..7e51a07a5c2 100644 --- a/python/cugraph/cugraph/tree/minimum_spanning_tree_wrapper.pyx +++ b/python/cugraph/cugraph/tree/minimum_spanning_tree_wrapper.pyx @@ -22,7 +22,9 @@ from cugraph.structure import graph_primtypes_wrapper from libc.stdint cimport uintptr_t import cudf -import rmm + +# FIXME: these are transitive dependencies and are not currently listed in the +# conda recipe. Either make them optional or add them to the conda recipe. import numpy as np import cupy as cp diff --git a/python/cugraph/cugraph/utilities/__init__.py b/python/cugraph/cugraph/utilities/__init__.py index 6dc23ff53b7..f868bef0e0d 100644 --- a/python/cugraph/cugraph/utilities/__init__.py +++ b/python/cugraph/cugraph/utilities/__init__.py @@ -14,16 +14,17 @@ # from cugraph.utilities.grmat import grmat_gen # from cugraph.utilities.pointer_utils import device_of_gpu_pointer from cugraph.utilities.nx_factory import convert_from_nx -from cugraph.utilities.nx_factory import check_nx_graph from cugraph.utilities.nx_factory import df_score_to_dictionary from cugraph.utilities.nx_factory import df_edge_score_to_dictionary from cugraph.utilities.nx_factory import cugraph_to_nx -from cugraph.utilities.nx_factory import is_networkx_graph from cugraph.utilities.utils import (import_optional, ensure_cugraph_obj, + ensure_cugraph_obj_for_nx, is_matrix_type, is_cp_matrix_type, is_sp_matrix_type, - renumber_vertex_pair + is_nx_graph_type, + renumber_vertex_pair, + cupy_package, ) from cugraph.utilities.path_retrieval import get_traversed_cost diff --git a/python/cugraph/cugraph/utilities/nx_factory.py b/python/cugraph/cugraph/utilities/nx_factory.py index 7eb97b30582..b3e4bae6010 100644 --- a/python/cugraph/cugraph/utilities/nx_factory.py +++ b/python/cugraph/cugraph/utilities/nx_factory.py @@ -11,23 +11,28 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Utilities specific to NetworkX. + +NetworkX is required at runtime in order to call any of these functions, so +ensure code using these utilities has done the proper checks prior to calling. +""" + import cugraph from .utils import import_optional from cudf import from_pandas import numpy as np +# nx will be a MissingModule instance if NetworkX is not installed (any +# attribute access on a MissingModule instance results in a RuntimeError). nx = import_optional("networkx") - def convert_from_nx(nxG, weight=None): """ weight, if given, is the string/name of the edge attr in nxG to use for weights in the resulting cugraph obj. If nxG has no edge attributes, weight is ignored even if specified. """ - if nx is None: - raise RuntimeError("NetworkX could not be imported, " - "cannot convert nxG") if type(nxG) == nx.classes.graph.Graph: G = cugraph.Graph() elif type(nxG) == nx.classes.digraph.DiGraph: @@ -69,40 +74,6 @@ def convert_from_nx(nxG, weight=None): return G -def is_networkx_graph(G): - if nx is None: - raise RuntimeError("NetworkX could not be imported, cannot check G") - return isinstance(G, nx.classes.graph.Graph) - - -def check_nx_graph(G, weight=None): - """ - This is a convenience function that will ensure the proper graph type - - Parameters - ---------- - G : cudf.Graph or networkx.Graph - weight : str or None - which column to use for weight. Default is None - - Returns - ------- - G : cudf.Graph - returns a cugraph.Graph that is either the orginal input or - a conversion from NetworkX - - is_nx : Boolean - indicates rather or not the Graph was converted - """ - - if nx is None: - raise RuntimeError("NetworkX could not be imported, cannot check G") - if isinstance(G, nx.classes.graph.Graph): - return convert_from_nx(G, weight), True - else: - return G, False - - def df_score_to_dictionary(df, k, v="vertex"): """ Convert a dataframe to a dictionary @@ -175,9 +146,6 @@ def df_edge_score_to_dictionary(df, k, src="src", dst="dst"): def cugraph_to_nx(G): - if nx is None: - raise RuntimeError("NetworkX could not be imported, cannot convert G") - pdf = G.view_edge_list().to_pandas() num_col = len(pdf.columns) diff --git a/python/cugraph/cugraph/utilities/utils.py b/python/cugraph/cugraph/utilities/utils.py index e4e93513630..c9002bd4ae3 100644 --- a/python/cugraph/cugraph/utilities/utils.py +++ b/python/cugraph/cugraph/utilities/utils.py @@ -11,6 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import importlib + from numba import cuda import cudf @@ -27,12 +29,14 @@ from cupyx.scipy.sparse.csr import csr_matrix as cp_csr_matrix from cupyx.scipy.sparse.csc import csc_matrix as cp_csc_matrix - CP_MATRIX_TYPES = [cp_coo_matrix, cp_csr_matrix, cp_csc_matrix] - CP_COMPRESSED_MATRIX_TYPES = [cp_csr_matrix, cp_csc_matrix] + __cp_matrix_types = [cp_coo_matrix, cp_csr_matrix, cp_csc_matrix] + __cp_compressed_matrix_types = [cp_csr_matrix, cp_csc_matrix] except ModuleNotFoundError: cp = None - CP_MATRIX_TYPES = [] - CP_COMPRESSED_MATRIX_TYPES = [] + __cp_matrix_types = [] + __cp_compressed_matrix_types = [] + +cupy_package = cp try: import scipy as sp @@ -40,17 +44,23 @@ from scipy.sparse.csr import csr_matrix as sp_csr_matrix from scipy.sparse.csc import csc_matrix as sp_csc_matrix - SP_MATRIX_TYPES = [sp_coo_matrix, sp_csr_matrix, sp_csc_matrix] - SP_COMPRESSED_MATRIX_TYPES = [sp_csr_matrix, sp_csc_matrix] + __sp_matrix_types = [sp_coo_matrix, sp_csr_matrix, sp_csc_matrix] + __sp_compressed_matrix_types = [sp_csr_matrix, sp_csc_matrix] except ModuleNotFoundError: sp = None - SP_MATRIX_TYPES = [] - SP_COMPRESSED_MATRIX_TYPES = [] + __sp_matrix_types = [] + __sp_compressed_matrix_types = [] + +scipy_package = sp try: import networkx as nx + __nx_graph_types = [nx.Graph, nx.DiGraph] except ModuleNotFoundError: nx = None + __nx_graph_types = [] + +nx_package = nx def get_traversed_path(df, id): @@ -236,6 +246,7 @@ def get_device_memory_info(): # | Many NetworkX algorithms designed for weighted graphs use # | an edge attribute (by default `weight`) to hold a numerical value. def ensure_cugraph_obj(obj, nx_weight_attr=None, matrix_graph_type=None): + """ Convert the input obj - if possible - to a cuGraph Graph-type obj (Graph, DiGraph, etc.) and return a tuple of (cugraph Graph-type obj, original @@ -247,14 +258,13 @@ def ensure_cugraph_obj(obj, nx_weight_attr=None, matrix_graph_type=None): from cugraph.utilities.nx_factory import convert_from_nx input_type = type(obj) - if input_type in [Graph, DiGraph, MultiGraph, MultiDiGraph]: + if is_cugraph_graph_type(input_type): return (obj, input_type) - elif (nx is not None) and (input_type in [nx.Graph, nx.DiGraph]): + elif is_nx_graph_type(input_type): return (convert_from_nx(obj, weight=nx_weight_attr), input_type) - elif (input_type in CP_MATRIX_TYPES) or (input_type in SP_MATRIX_TYPES): - + elif (input_type in __cp_matrix_types) or (input_type in __sp_matrix_types): if matrix_graph_type is None: matrix_graph_type = Graph elif matrix_graph_type not in [Graph, DiGraph]: @@ -264,13 +274,13 @@ def ensure_cugraph_obj(obj, nx_weight_attr=None, matrix_graph_type=None): ) if input_type in ( - CP_COMPRESSED_MATRIX_TYPES + SP_COMPRESSED_MATRIX_TYPES + __cp_compressed_matrix_types + __sp_compressed_matrix_types ): coo = obj.tocoo(copy=False) else: coo = obj - if input_type in CP_MATRIX_TYPES: + if input_type in __cp_matrix_types: df = cudf.DataFrame( { "source": cp.ascontiguousarray(coo.row), @@ -296,45 +306,50 @@ def ensure_cugraph_obj(obj, nx_weight_attr=None, matrix_graph_type=None): raise TypeError(f"obj of type {input_type} is not supported.") +# FIXME: if G is a Nx type, the weight attribute is assumed to be "weight", if +# set. An additional optional parameter for the weight attr name when accepting +# Nx graphs may be needed. From the Nx docs: +# | Many NetworkX algorithms designed for weighted graphs use +# | an edge attribute (by default `weight`) to hold a numerical value. +def ensure_cugraph_obj_for_nx(obj, nx_weight_attr=None): + """ + Ensures a cuGraph Graph-type obj is returned for either cuGraph or Nx + Graph-type objs. If obj is a Nx type, + """ + # FIXME: importing here to avoid circular import + from cugraph.utilities.nx_factory import convert_from_nx + + input_type = type(obj) + if is_nx_graph_type(input_type): + return (convert_from_nx(obj, weight=nx_weight_attr), True) + elif is_cugraph_graph_type(input_type): + return (obj, False) + else: + raise TypeError("input must be either a cuGraph or NetworkX graph " + f"type, got {input_type}") + + def is_cp_matrix_type(m): - return m in CP_MATRIX_TYPES + return m in __cp_matrix_types def is_sp_matrix_type(m): - return m in SP_MATRIX_TYPES + return m in __sp_matrix_types def is_matrix_type(m): return is_cp_matrix_type(m) or is_sp_matrix_type(m) -def import_optional(mod, import_from=None): - """ - import module or object 'mod', possibly from module 'import_from', and - return the module object or object. If the import raises - ModuleNotFoundError, returns None. +def is_nx_graph_type(g): + return g in __nx_graph_types - This method was written to support importing "optional" dependencies so - code can be written to run even if the dependency is not installed. - >>> nx = import_optional("networkx") # networkx is not installed - >>> if nx: - ... G = nx.Graph() - ... else: - ... print("Warning: NetworkX is not installed, using CPUGraph") - ... G = cpu_graph.Graph() - >>> - """ - namespace = {} - try: - if import_from: - exec(f"from {import_from} import {mod}", namespace) - else: - exec(f"import {mod}", namespace) - except ModuleNotFoundError: - pass +def is_cugraph_graph_type(g): + # FIXME: importing here to avoid circular import + from cugraph.structure import Graph, DiGraph, MultiGraph, MultiDiGraph - return namespace.get(mod) + return g in [Graph, DiGraph, MultiGraph, MultiDiGraph] def renumber_vertex_pair(input_graph, vertex_pair): @@ -355,3 +370,64 @@ def renumber_vertex_pair(input_graph, vertex_pair): vertex_pair, "dst", columns[vertex_size:] ) return vertex_pair + + +class MissingModule: + """ + Raises RuntimeError when any attribute is accessed on instances of this + class. + + Instances of this class are returned by import_optional() when a module + cannot be found, which allows for code to import optional dependencies, and + have only the code paths that use the module affected. + """ + def __init__(self, mod_name): + self.name = mod_name + + def __getattr__(self, attr): + raise RuntimeError(f"This feature requires the {self.name} package/module") + + +def import_optional(mod, default_mod_class=MissingModule): + """ + import the "optional" module 'mod' and return the module object or object. + If the import raises ModuleNotFoundError, returns an instance of + default_mod_class. + + This method was written to support importing "optional" dependencies so + code can be written to run even if the dependency is not installed. + + Example + ------- + >>> nx = import_optional("networkx") # networkx is not installed + >>> G = nx.Graph() + Traceback (most recent call last): + File "", line 1, in + ... + RuntimeError: This feature requires the networkx package/module + + Example + ------- + >>> class CuDFFallback: + ... def __init__(self, mod_name): + ... assert mod_name == "cudf" + ... warnings.warn("cudf could not be imported, using pandas instead!") + ... def __getattr__(self, attr): + ... import pandas + ... return getattr(pandas, attr) + ... + >>> df_mod = import_optional("cudf", default_mod_class=CuDFFallback) + :4: UserWarning: cudf could not be imported, using pandas instead! + >>> df = df_mod.DataFrame() + >>> df + Empty DataFrame + Columns: [] + Index: [] + >>> type(df) + + >>> + """ + try: + return importlib.import_module(mod) + except ModuleNotFoundError: + return default_mod_class(mod_name=mod) From 52a9465b89c466492023c0d870533473ddd92a53 Mon Sep 17 00:00:00 2001 From: Rick Ratzel Date: Mon, 27 Sep 2021 22:08:26 -0500 Subject: [PATCH 2/4] Fixed error with egonet where obj instead of type was being passed to type checking code, added docstrings for pylibcugraph.CC functions. --- python/cugraph/cugraph/community/egonet.py | 2 +- .../pylibcugraph/components/_connectivity.pyx | 140 +++++++++++++++++- 2 files changed, 137 insertions(+), 5 deletions(-) diff --git a/python/cugraph/cugraph/community/egonet.py b/python/cugraph/cugraph/community/egonet.py index 2fcafa5795b..09b7b419a15 100644 --- a/python/cugraph/cugraph/community/egonet.py +++ b/python/cugraph/cugraph/community/egonet.py @@ -25,7 +25,7 @@ def _convert_graph_to_output_type(G, input_type): Given a cugraph.Graph, convert it to a new type appropriate for the graph algos in this module, based on input_type. """ - if is_nx_graph_type(G): + if is_nx_graph_type(input_type): return cugraph_to_nx(G) else: diff --git a/python/pylibcugraph/pylibcugraph/components/_connectivity.pyx b/python/pylibcugraph/pylibcugraph/components/_connectivity.pyx index 969feb199d7..89c1cb6c0d5 100644 --- a/python/pylibcugraph/pylibcugraph/components/_connectivity.pyx +++ b/python/pylibcugraph/pylibcugraph/components/_connectivity.pyx @@ -40,8 +40,74 @@ def _ensure_arg_types(**kwargs): def strongly_connected_components(offsets, indices, weights, num_verts, num_edges, labels): """ - This is the docstring for strongly_connected_components - FIXME: write this docstring + Generate the Strongly Connected Components and attach a component label to + each vertex. + + Parameters + ---------- + offsets : object supporting a __cuda_array_interface__ interface + Array containing the offsets values of a Compressed Sparse Row matrix + that represents the graph. + + indices : object supporting a __cuda_array_interface__ interface + Array containing the indices values of a Compressed Sparse Row matrix + that represents the graph. + + weights : object supporting a __cuda_array_interface__ interface + Array containing the weights values of a Compressed Sparse Row matrix + that represents the graph. + + NOTE: weighted graphs are currently unsupported, and because of this the + weights parameter can only be set to None. + + num_verts : int + The number of vertices present in the graph represented by the CSR + arrays above. + + num_edges : int + The number of edges present in the graph represented by the CSR arrays + above. + + labels : object supporting a __cuda_array_interface__ interface + Array of size num_verts that will be populated with component label + values. The component lables in the array are ordered based on the + sorted vertex ID values of the graph. For example, labels [9, 9, 7] + mean vertex 0 is labelled 9, vertex 1 is labelled 9, and vertex 2 is + labelled 7. + + Returns + ------- + None + + Examples + -------- + >>> import cupy as cp + >>> import numpy as np + >>> from scipy.sparse import csr_matrix + >>> + >>> graph = [ + ... [0, 1, 1, 0, 0], + ... [0, 0, 1, 0, 0], + ... [0, 0, 0, 0, 0], + ... [0, 0, 0, 0, 1], + ... [0, 0, 0, 0, 0], + ... ] + >>> scipy_csr = csr_matrix(graph) + >>> num_verts = scipy_csr.get_shape()[0] + >>> num_edges = scipy_csr.nnz + >>> + >>> cp_offsets = cp.asarray(scipy_csr.indptr) + >>> cp_indices = cp.asarray(scipy_csr.indices, dtype=np.int32) + >>> cp_labels = cp.asarray(np.zeros(num_verts, dtype=np.int32)) + >>> + >>> strongly_connected_components(offsets=cp_offsets, + ... indices=cp_indices, + ... weights=None, + ... num_verts=num_verts, + ... num_edges=num_edges, + ... labels=cp_labels) + >>> print(f"{len(set(cp_labels.tolist()))} - {cp_labels}") + 5 - [0 1 2 3 4] """ _ensure_arg_types(offsets=offsets, indices=indices, weights=weights, labels=labels) @@ -65,8 +131,74 @@ def strongly_connected_components(offsets, indices, weights, num_verts, num_edge def weakly_connected_components(offsets, indices, weights, num_verts, num_edges, labels): """ - This is the docstring for weakly_connected_components - FIXME: write this docstring + Generate the Weakly Connected Components and attach a component label to + each vertex. + + Parameters + ---------- + offsets : object supporting a __cuda_array_interface__ interface + Array containing the offsets values of a Compressed Sparse Row matrix + that represents the graph. + + indices : object supporting a __cuda_array_interface__ interface + Array containing the indices values of a Compressed Sparse Row matrix + that represents the graph. + + weights : object supporting a __cuda_array_interface__ interface + Array containing the weights values of a Compressed Sparse Row matrix + that represents the graph. + + NOTE: weighted graphs are currently unsupported, and because of this the + weights parameter can only be set to None. + + num_verts : int + The number of vertices present in the graph represented by the CSR + arrays above. + + num_edges : int + The number of edges present in the graph represented by the CSR arrays + above. + + labels : object supporting a __cuda_array_interface__ interface + Array of size num_verts that will be populated with component label + values. The component lables in the array are ordered based on the + sorted vertex ID values of the graph. For example, labels [9, 9, 7] + mean vertex 0 is labelled 9, vertex 1 is labelled 9, and vertex 2 is + labelled 7. + + Returns + ------- + None + + Examples + -------- + >>> import cupy as cp + >>> import numpy as np + >>> from scipy.sparse import csr_matrix + >>> + >>> graph = [ + ... [0, 1, 1, 0, 0], + ... [0, 0, 1, 0, 0], + ... [0, 0, 0, 0, 0], + ... [0, 0, 0, 0, 1], + ... [0, 0, 0, 0, 0], + ... ] + >>> scipy_csr = csr_matrix(graph) + >>> num_verts = scipy_csr.get_shape()[0] + >>> num_edges = scipy_csr.nnz + >>> + >>> cp_offsets = cp.asarray(scipy_csr.indptr) + >>> cp_indices = cp.asarray(scipy_csr.indices, dtype=np.int32) + >>> cp_labels = cp.asarray(np.zeros(num_verts, dtype=np.int32)) + >>> + >>> weakly_connected_components(offsets=cp_offsets, + ... indices=cp_indices, + ... weights=None, + ... num_verts=num_verts, + ... num_edges=num_edges, + ... labels=cp_labels) + >>> print(f"{len(set(cp_labels.tolist()))} - {cp_labels}") + 2 - [1 1 1 4 4] """ _ensure_arg_types(offsets=offsets, indices=indices, weights=weights, labels=labels) From 42c536d2e9312e32c4092ab1bb21e279edf120fc Mon Sep 17 00:00:00 2001 From: Rick Ratzel Date: Mon, 27 Sep 2021 22:28:25 -0500 Subject: [PATCH 3/4] flake8 fixes. --- python/cugraph/cugraph/centrality/katz_centrality.py | 1 - python/cugraph/cugraph/link_analysis/pagerank.py | 2 +- python/cugraph/cugraph/utilities/nx_factory.py | 1 + python/cugraph/cugraph/utilities/utils.py | 8 +++++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/python/cugraph/cugraph/centrality/katz_centrality.py b/python/cugraph/cugraph/centrality/katz_centrality.py index af6c73dc366..de8a9339a55 100644 --- a/python/cugraph/cugraph/centrality/katz_centrality.py +++ b/python/cugraph/cugraph/centrality/katz_centrality.py @@ -15,7 +15,6 @@ from cugraph.utilities import (ensure_cugraph_obj_for_nx, df_score_to_dictionary, ) -import cugraph def katz_centrality( diff --git a/python/cugraph/cugraph/link_analysis/pagerank.py b/python/cugraph/cugraph/link_analysis/pagerank.py index 6bb2fcff214..d6b0b65802c 100644 --- a/python/cugraph/cugraph/link_analysis/pagerank.py +++ b/python/cugraph/cugraph/link_analysis/pagerank.py @@ -16,7 +16,7 @@ from cugraph.link_analysis import pagerank_wrapper from cugraph.utilities import (ensure_cugraph_obj_for_nx, df_score_to_dictionary, - ) + ) def pagerank( diff --git a/python/cugraph/cugraph/utilities/nx_factory.py b/python/cugraph/cugraph/utilities/nx_factory.py index b3e4bae6010..c5d8ee22f83 100644 --- a/python/cugraph/cugraph/utilities/nx_factory.py +++ b/python/cugraph/cugraph/utilities/nx_factory.py @@ -27,6 +27,7 @@ # attribute access on a MissingModule instance results in a RuntimeError). nx = import_optional("networkx") + def convert_from_nx(nxG, weight=None): """ weight, if given, is the string/name of the edge attr in nxG to use for diff --git a/python/cugraph/cugraph/utilities/utils.py b/python/cugraph/cugraph/utilities/utils.py index c9002bd4ae3..42db0a6b370 100644 --- a/python/cugraph/cugraph/utilities/utils.py +++ b/python/cugraph/cugraph/utilities/utils.py @@ -254,7 +254,7 @@ def ensure_cugraph_obj(obj, nx_weight_attr=None, matrix_graph_type=None): cugraph Graph-type obj to create when converting from a matrix type. """ # FIXME: importing here to avoid circular import - from cugraph.structure import Graph, DiGraph, MultiGraph, MultiDiGraph + from cugraph.structure import Graph, DiGraph from cugraph.utilities.nx_factory import convert_from_nx input_type = type(obj) @@ -264,7 +264,8 @@ def ensure_cugraph_obj(obj, nx_weight_attr=None, matrix_graph_type=None): elif is_nx_graph_type(input_type): return (convert_from_nx(obj, weight=nx_weight_attr), input_type) - elif (input_type in __cp_matrix_types) or (input_type in __sp_matrix_types): + elif (input_type in __cp_matrix_types) or \ + (input_type in __sp_matrix_types): if matrix_graph_type is None: matrix_graph_type = Graph elif matrix_graph_type not in [Graph, DiGraph]: @@ -385,7 +386,8 @@ def __init__(self, mod_name): self.name = mod_name def __getattr__(self, attr): - raise RuntimeError(f"This feature requires the {self.name} package/module") + raise RuntimeError(f"This feature requires the {self.name} " + "package/module") def import_optional(mod, default_mod_class=MissingModule): From db93b6322c971b431b541211022fd1712ee5f267 Mon Sep 17 00:00:00 2001 From: Rick Ratzel Date: Tue, 28 Sep 2021 08:54:30 -0500 Subject: [PATCH 4/4] Added FIXME for RW return type conversion, changed formatting of import in BC for consistency. --- .../cugraph/cugraph/centrality/betweenness_centrality.py | 7 ++++--- python/cugraph/cugraph/sampling/random_walks.py | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/python/cugraph/cugraph/centrality/betweenness_centrality.py b/python/cugraph/cugraph/centrality/betweenness_centrality.py index 1dc79e39eb5..d86349504da 100644 --- a/python/cugraph/cugraph/centrality/betweenness_centrality.py +++ b/python/cugraph/cugraph/centrality/betweenness_centrality.py @@ -16,9 +16,10 @@ import cudf from cugraph.centrality import betweenness_centrality_wrapper from cugraph.centrality import edge_betweenness_centrality_wrapper -from cugraph.utilities import df_edge_score_to_dictionary -from cugraph.utilities import df_score_to_dictionary -from cugraph.utilities import ensure_cugraph_obj_for_nx +from cugraph.utilities import (df_edge_score_to_dictionary, + df_score_to_dictionary, + ensure_cugraph_obj_for_nx, + ) import cugraph diff --git a/python/cugraph/cugraph/sampling/random_walks.py b/python/cugraph/cugraph/sampling/random_walks.py index 69ea6733f6a..8f7ad3fc3a8 100644 --- a/python/cugraph/cugraph/sampling/random_walks.py +++ b/python/cugraph/cugraph/sampling/random_walks.py @@ -57,6 +57,11 @@ def random_walks(G, if max_depth is None: raise TypeError("must specify a 'max_depth'") + # FIXME: supporting Nx types should mean having a return type that better + # matches Nx expectations (eg. data on the CPU, possibly using a different + # data struct like a dictionary, etc.). The 2nd value is ignored here, + # which is typically named isNx and used to convert the return type. + # Consider a different return type if Nx types are passed in. G, _ = ensure_cugraph_obj_for_nx(G) if start_vertices is int: