diff --git a/README.md b/README.md index b6ae0756f..0ee2f7264 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ Below is the list of algorithms that are currently supported in nx-cugraph.
 bipartite
+ ├─ centrality
+ │   └─ betweenness_centrality
  └─ generators
      └─ complete_bipartite_graph
 centrality
diff --git a/_nx_cugraph/__init__.py b/_nx_cugraph/__init__.py
index 6a60fdce7..bcedc13b4 100644
--- a/_nx_cugraph/__init__.py
+++ b/_nx_cugraph/__init__.py
@@ -60,6 +60,7 @@
         "bfs_successors",
         "bfs_tree",
         "bidirectional_shortest_path",
+        "bipartite_betweenness_centrality",
         "bull_graph",
         "caveman_graph",
         "chvatal_graph",
diff --git a/benchmarks/pytest-based/bench_algos.py b/benchmarks/pytest-based/bench_algos.py
index 6d3a06ba8..34e81d2b2 100644
--- a/benchmarks/pytest-based/bench_algos.py
+++ b/benchmarks/pytest-based/bench_algos.py
@@ -884,6 +884,27 @@ def bench_lowest_common_ancestor(benchmark, graph_obj, backend_wrapper):
     assert result is None or result in G
 
 
+def bench_bipartite_BC_n1000_m3000_k100000(benchmark, backend_wrapper):
+    # Example how to run:
+    # $ pytest -sv -k "bench_bipartite_BC" \
+    #   --benchmark-json="logs/None__bipartite_BC__None.json" \
+    #   bench_algos.py
+    n = 1000
+    m = 3000
+    k = 100000
+    graph_obj = nx.bipartite.generators.gnmk_random_graph(n, m, k)
+    G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
+    nodes = list(range(n))
+    result = benchmark.pedantic(
+        target=backend_wrapper(nx.bipartite.betweenness_centrality),
+        args=(G, nodes),
+        rounds=rounds,
+        iterations=iterations,
+        warmup_rounds=warmup_rounds,
+    )
+    assert type(result) is dict
+
+
 @pytest.mark.skip(reason="benchmark not implemented")
 def bench_complete_bipartite_graph(benchmark, graph_obj, backend_wrapper):
     pass
diff --git a/nx_cugraph/algorithms/bipartite/__init__.py b/nx_cugraph/algorithms/bipartite/__init__.py
index bfc7f1d4d..ce5e47ead 100644
--- a/nx_cugraph/algorithms/bipartite/__init__.py
+++ b/nx_cugraph/algorithms/bipartite/__init__.py
@@ -10,4 +10,6 @@
 # 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 .centrality import *
 from .generators import *
diff --git a/nx_cugraph/algorithms/bipartite/centrality.py b/nx_cugraph/algorithms/bipartite/centrality.py
new file mode 100644
index 000000000..7e4b0baa9
--- /dev/null
+++ b/nx_cugraph/algorithms/bipartite/centrality.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2024, 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 cupy as cp
+import pylibcugraph as plc
+
+from nx_cugraph.convert import _to_graph
+from nx_cugraph.utils import networkx_algorithm
+
+__all__ = ["betweenness_centrality"]
+
+
+@networkx_algorithm(
+    name="bipartite_betweenness_centrality",
+    version_added="24.12",
+    _plc="betweenness_centrality",
+)
+def betweenness_centrality(G, nodes):
+    G = _to_graph(G)
+
+    node_ids, values = plc.betweenness_centrality(
+        resource_handle=plc.ResourceHandle(),
+        graph=G._get_plc_graph(),
+        k=None,
+        random_state=None,
+        normalized=False,
+        include_endpoints=False,
+        do_expensive_check=False,
+    )
+    top_node_ids = G._nodekeys_to_nodearray(set(nodes))
+    bottom_node_ids = cp.delete(cp.arange(G._N, dtype=top_node_ids.dtype), top_node_ids)
+    n = top_node_ids.size
+    m = bottom_node_ids.size
+    s, t = divmod(n - 1, m)
+    bet_max_top = (
+        ((m**2) * ((s + 1) ** 2))
+        + (m * (s + 1) * (2 * t - s - 1))
+        - (t * ((2 * s) - t + 3))
+    ) / 2.0
+    p, r = divmod(m - 1, n)
+    bet_max_bot = (
+        ((n**2) * ((p + 1) ** 2))
+        + (n * (p + 1) * (2 * r - p - 1))
+        - (r * ((2 * p) - r + 3))
+    ) / 2.0
+
+    values = values[cp.argsort(node_ids)]
+
+    values[top_node_ids] /= bet_max_top
+    values[bottom_node_ids] /= bet_max_bot
+
+    return G._nodearray_to_dict(values)