From 277b3531b40fb63fae304bee264aac508412fb33 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Sun, 26 Jan 2025 22:08:48 -0600 Subject: [PATCH] Add experimental version of `leiden_communities` (#50) This function is not yet defined or implemented in NetworkX, and there is high probability the API may change once added to NetworkX. For now, this is "best effort" and simply mimics PLC leiden. Authors: - Erik Welch (https://github.com/eriknw) - Ralph Liu (https://github.com/nv-rliu) Approvers: - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/nx-cugraph/pull/50 --- _nx_cugraph/__init__.py | 6 ++- nx_cugraph/algorithms/community/__init__.py | 3 +- nx_cugraph/algorithms/community/leiden.py | 52 +++++++++++++++++++++ nx_cugraph/tests/test_leiden.py | 22 +++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 nx_cugraph/algorithms/community/leiden.py create mode 100644 nx_cugraph/tests/test_leiden.py diff --git a/_nx_cugraph/__init__.py b/_nx_cugraph/__init__.py index a52583d4d..07bfde667 100644 --- a/_nx_cugraph/__init__.py +++ b/_nx_cugraph/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023-2024, NVIDIA CORPORATION. +# Copyright (c) 2023-2025, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -115,6 +115,7 @@ "katz_centrality", "krackhardt_kite_graph", "ladder_graph", + "leiden_communities", "les_miserables_graph", "lollipop_graph", "louvain_communities", @@ -235,6 +236,9 @@ "katz_centrality": { "dtype : dtype or None, optional": "The data type (np.float32, np.float64, or None) to use for the edge weights in the algorithm. If None, then dtype is determined by the edge values.", }, + "leiden_communities": { + "dtype : dtype or None, optional": "The data type (np.float32, np.float64, or None) to use for the edge weights in the algorithm. If None, then dtype is determined by the edge values.", + }, "louvain_communities": { "dtype : dtype or None, optional": "The data type (np.float32, np.float64, or None) to use for the edge weights in the algorithm. If None, then dtype is determined by the edge values.", }, diff --git a/nx_cugraph/algorithms/community/__init__.py b/nx_cugraph/algorithms/community/__init__.py index 51a4f5c19..9515769a6 100644 --- a/nx_cugraph/algorithms/community/__init__.py +++ b/nx_cugraph/algorithms/community/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. +# Copyright (c) 2023-2025, NVIDIA CORPORATION. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,4 +10,5 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from .leiden import * from .louvain import * diff --git a/nx_cugraph/algorithms/community/leiden.py b/nx_cugraph/algorithms/community/leiden.py new file mode 100644 index 000000000..4f143754f --- /dev/null +++ b/nx_cugraph/algorithms/community/leiden.py @@ -0,0 +1,52 @@ +# Copyright (c) 2024-2025, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import pylibcugraph as plc + +from nx_cugraph.convert import _to_undirected_graph +from nx_cugraph.utils import ( + _dtype_param, + _get_float_dtype, + _groupby, + _seed_to_int, + networkx_algorithm, + not_implemented_for, +) + +__all__ = ["leiden_communities"] + + +@not_implemented_for("directed") +@networkx_algorithm(extra_params=_dtype_param, version_added="25.02", _plc="leiden") +def leiden_communities( + G, weight="weight", resolution=1, max_level=None, seed=None, *, dtype=None +): + # Warning: this API is experimental and may change. It is not yet in NetworkX. + # See: https://github.com/networkx/networkx/pull/7743 + seed = _seed_to_int(seed) + G = _to_undirected_graph(G, weight, 1, np.float32) + dtype = _get_float_dtype(dtype, graph=G, weight=weight) + if max_level is None or max_level < 0: + max_level = 500 + node_ids, clusters, modularity = plc.leiden( + resource_handle=plc.ResourceHandle(), + random_state=seed, + graph=G._get_plc_graph(weight, 1, dtype), + max_level=max_level, + resolution=resolution, + theta=1, # TODO: expose theta as a backend-only parameter once it's used + do_expensive_check=False, + ) + groups = _groupby(clusters, node_ids, groups_are_canonical=True) + return [set(G._nodearray_to_list(ids)) for ids in groups.values()] diff --git a/nx_cugraph/tests/test_leiden.py b/nx_cugraph/tests/test_leiden.py new file mode 100644 index 000000000..beee13f16 --- /dev/null +++ b/nx_cugraph/tests/test_leiden.py @@ -0,0 +1,22 @@ +# Copyright (c) 2024-2025, NVIDIA CORPORATION. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import nx_cugraph as nxcg + + +def test_leiden_karate(): + # Basic smoke test; if something here changes, we want to know! + G = nxcg.karate_club_graph() + leiden = nxcg.community.leiden_communities(G, seed=123) + louvain = nxcg.community.louvain_communities(G, seed=123) + assert leiden == louvain