From 6e819b5d753a027f87e4bf8fa7cfa42e2453e936 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Sat, 23 Apr 2022 19:05:44 +0900 Subject: [PATCH 01/10] Initial implementation of closeness_centrality --- docs/source/api.rst | 3 ++ retworkx-core/src/centrality.rs | 72 +++++++++++++++++++++++++++++--- retworkx/__init__.py | 23 ++++++++++ src/centrality.rs | 45 ++++++++++++++++++++ src/lib.rs | 2 + tests/digraph/test_centrality.py | 20 +++++++++ tests/graph/test_centrality.py | 20 +++++++++ tests/test_dispatch.py | 4 ++ 8 files changed, 183 insertions(+), 6 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index d35ef4510..b87b53bd4 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -48,6 +48,7 @@ Centrality :toctree: apiref retworkx.betweenness_centrality + retworkx.closeness_centrality .. _traversal: @@ -289,6 +290,7 @@ the functions from the explicitly typed based on the data type. retworkx.digraph_spring_layout retworkx.digraph_num_shortest_paths_unweighted retworkx.digraph_betweenness_centrality + retworkx.digraph_closeness_centrality retworkx.digraph_unweighted_average_shortest_path_length retworkx.digraph_bfs_search retworkx.digraph_dijkstra_search @@ -336,6 +338,7 @@ typed API based on the data type. retworkx.graph_spring_layout retworkx.graph_num_shortest_paths_unweighted retworkx.graph_betweenness_centrality + retworkx.graph_closeness_centrality retworkx.graph_unweighted_average_shortest_path_length retworkx.graph_bfs_search retworkx.graph_dijkstra_search diff --git a/retworkx-core/src/centrality.rs b/retworkx-core/src/centrality.rs index 3474be99a..56d0a71c7 100644 --- a/retworkx-core/src/centrality.rs +++ b/retworkx-core/src/centrality.rs @@ -14,14 +14,11 @@ use std::collections::VecDeque; use std::sync::RwLock; use hashbrown::HashMap; +use petgraph::algo::dijkstra; use petgraph::graph::NodeIndex; use petgraph::visit::{ - GraphBase, - GraphProp, // allows is_directed - IntoNeighborsDirected, - IntoNodeIdentifiers, - NodeCount, - NodeIndexable, + GraphBase, GraphProp, IntoEdges, IntoEdgesDirected, IntoNeighborsDirected, IntoNodeIdentifiers, + NodeCount, NodeIndexable, Reversed, Visitable, }; use rayon::prelude::*; @@ -297,3 +294,66 @@ where sigma, } } + +/// Compute the closeness centrality of all nodes in a graph. +/// +/// Arguments: +/// +/// * `graph` - The graph object to run the algorithm on +/// * `wf_improved` - If True, scale by the fraction of nodes reachable. +/// +/// # Example +/// ```rust +/// use retworkx_core::petgraph; +/// use retworkx_core::centrality::closeness_centrality; +/// +/// // Calculate the betweeness centrality of Graph +/// let g = petgraph::graph::UnGraph::::from_edges(&[ +/// (0, 4), (1, 2), (2, 3), (3, 4), (1, 4) +/// ]); +/// let output = closeness_centrality(&g, true); +/// assert_eq!( +/// vec![Some(1./2.), Some(2./3.), Some(4./7.), Some(2./3.), Some(4./5.)], +/// output +/// ); +/// +/// // Calculate the betweeness centrality of DiGraph +/// let dg = petgraph::graph::Diraph::::from_edges(&[ +/// (0, 4), (1, 2), (2, 3), (3, 4), (1, 4) +/// ]); +/// let output = closeness_centrality(&dg, true); +/// assert_eq!( +/// vec![Some(0.), Some(0.), Some(1./4.), Some(1./3.), Some(4./5.)], +/// output +/// ); +/// ``` +pub fn closeness_centrality(graph: G, wf_improved: bool) -> Vec> +where + G: NodeIndexable + + IntoNodeIdentifiers + + GraphBase + + IntoEdges + + Visitable + + NodeCount + + IntoEdgesDirected, +{ + let max_index = graph.node_bound(); + let mut betweenness: Vec> = vec![None; max_index]; + for node_s in graph.node_identifiers() { + let is = graph.to_index(node_s); + let dists = dijkstra(Reversed(&graph), node_s, None, |_| 1).into_values(); + let reachable_node_count = dists.len(); + if reachable_node_count == 1 { + betweenness[is] = Some(0.0); + continue; + } + let dists_sum: usize = dists.sum(); + betweenness[is] = Some((reachable_node_count - 1) as f64 / dists_sum as f64); + if wf_improved { + let node_count = graph.node_count(); + betweenness[is] = betweenness[is] + .map(|c| c * (reachable_node_count - 1) as f64 / (node_count - 1) as f64); + } + } + betweenness +} diff --git a/retworkx/__init__.py b/retworkx/__init__.py index 460111643..991b77cf3 100644 --- a/retworkx/__init__.py +++ b/retworkx/__init__.py @@ -1557,6 +1557,29 @@ def _graph_betweenness_centrality(graph, normalized=True, endpoints=False, paral ) +@functools.singledispatch +def closeness_centrality(graph, wf_improved=True): + r"""Returns the closeness centrality of each node in the graph. + + :param PyDiGraph graph: The input graph + :param bool wf_improved: If True, scale by the fraction of nodes reachable. + + :returns: A dictionary mapping each node index to its closeness centrality. + :rtype: dict + """ + raise TypeError("Invalid input type %s for graph" % type(graph)) + + +@closeness_centrality.register(PyDiGraph) +def _digraph_closeness_centrality(graph, wf_improved=True): + return digraph_closeness_centrality(graph, wf_improved=wf_improved) + + +@closeness_centrality.register(PyGraph) +def _graph_closeness_centrality(graph, wf_improved=True): + return graph_closeness_centrality(graph, wf_improved=wf_improved) + + @functools.singledispatch def vf2_mapping( first, diff --git a/src/centrality.rs b/src/centrality.rs index df87c7f8a..f9d872e4b 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -132,3 +132,48 @@ pub fn digraph_betweenness_centrality( .collect(), } } + +/// Compute the closeness centrality of all nodes in a PyGraph. +/// +/// :param PyGraph graph: The input graph +/// :param bool normalized: Whether to normalize the betweenness scores by the number of distinct +/// paths between all pairs of nodes. +/// :param bool endpoints: Whether to include the endpoints of paths in pathlengths used to +/// compute the betweenness. +/// :param int parallel_threshold: The number of nodes to calculate the +/// the betweenness centrality in parallel at if the number of nodes in +/// the graph is less than this value it will run in a single thread. The +/// default value is 50 +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// betweenness score for each node. +/// :rtype: CentralityMapping +#[pyfunction(wf_improved = "true")] +#[pyo3(text_signature = "(graph, /, wf_improved=True)")] +pub fn graph_closeness_centrality(graph: &graph::PyGraph, wf_improved: bool) -> CentralityMapping { + let betweenness = centrality::closeness_centrality(&graph.graph, wf_improved); + CentralityMapping { + centralities: betweenness + .into_iter() + .enumerate() + .filter_map(|(i, v)| v.map(|x| (i, x))) + .collect(), + } +} + +/// Compute the closeness centrality of all nodes in a PyDiGraph. +#[pyfunction(wf_improved = "true")] +#[pyo3(text_signature = "(graph, /, wf_improved=True)")] +pub fn digraph_closeness_centrality( + graph: &digraph::PyDiGraph, + wf_improved: bool, +) -> CentralityMapping { + let betweenness = centrality::closeness_centrality(&graph.graph, wf_improved); + CentralityMapping { + centralities: betweenness + .into_iter() + .enumerate() + .filter_map(|(i, v)| v.map(|x| (i, x))) + .collect(), + } +} diff --git a/src/lib.rs b/src/lib.rs index 4a35418d1..763115378 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -350,6 +350,8 @@ fn retworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(graph_all_pairs_dijkstra_shortest_paths))?; m.add_wrapped(wrap_pyfunction!(graph_betweenness_centrality))?; m.add_wrapped(wrap_pyfunction!(digraph_betweenness_centrality))?; + m.add_wrapped(wrap_pyfunction!(graph_closeness_centrality))?; + m.add_wrapped(wrap_pyfunction!(digraph_closeness_centrality))?; m.add_wrapped(wrap_pyfunction!(graph_astar_shortest_path))?; m.add_wrapped(wrap_pyfunction!(digraph_astar_shortest_path))?; m.add_wrapped(wrap_pyfunction!(graph_greedy_color))?; diff --git a/tests/digraph/test_centrality.py b/tests/digraph/test_centrality.py index da0540e0d..552716311 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -85,6 +85,16 @@ def test_betweenness_centrality_unnormalized_parallel(self): expected = {0: 0.0, 1: 2.0, 2: 2.0, 3: 0.0} self.assertEqual(expected, betweenness) + def test_closeness_centrality(self): + betweenness = retworkx.digraph_closeness_centrality(self.graph) + expected = {0: 0.0, 1: 1.0 / 3.0, 2: 4.0 / 9.0, 3: 0.5} + self.assertEqual(expected, betweenness) + + def test_closeness_centrality_wf_improved(self): + betweenness = retworkx.digraph_closeness_centrality(self.graph, wf_improved=False) + expected = {0: 0.0, 1: 1.0, 2: 2.0 / 3.0, 3: 0.5} + self.assertEqual(expected, betweenness) + class TestCentralityDiGraphDeletedNode(unittest.TestCase): def setUp(self): @@ -128,3 +138,13 @@ def test_betweenness_centrality_unnormalized(self): ) expected = {0: 0.0, 1: 2.0, 2: 2.0, 4: 0.0} self.assertEqual(expected, betweenness) + + def test_closeness_centrality(self): + betweenness = retworkx.digraph_closeness_centrality(self.graph) + expected = {0: 0.0, 1: 1.0 / 3.0, 2: 4.0 / 9.0, 4: 0.5} + self.assertEqual(expected, betweenness) + + def test_closeness_centrality_wf_improved(self): + betweenness = retworkx.digraph_closeness_centrality(self.graph, wf_improved=False) + expected = {0: 0.0, 1: 1.0, 2: 2.0 / 3.0, 4: 0.5} + self.assertEqual(expected, betweenness) diff --git a/tests/graph/test_centrality.py b/tests/graph/test_centrality.py index 5c382361e..5adbfb513 100644 --- a/tests/graph/test_centrality.py +++ b/tests/graph/test_centrality.py @@ -56,6 +56,16 @@ def test_betweenness_centrality_unnormalized(self): expected = {0: 0.0, 1: 2.0, 2: 2.0, 3: 0.0} self.assertEqual(expected, betweenness) + def test_closeness_centrality(self): + betweenness = retworkx.graph_closeness_centrality(self.graph) + expected = {0: 0.5, 1: 0.75, 2: 0.75, 3: 0.5} + self.assertEqual(expected, betweenness) + + def test_closeness_centrality_wf_improved(self): + betweenness = retworkx.graph_closeness_centrality(self.graph, wf_improved=False) + expected = {0: 0.5, 1: 0.75, 2: 0.75, 3: 0.5} + self.assertEqual(expected, betweenness) + class TestCentralityGraphDeletedNode(unittest.TestCase): def setUp(self): @@ -99,3 +109,13 @@ def test_betweenness_centrality_unnormalized(self): ) expected = {0: 0.0, 1: 2.0, 2: 2.0, 4: 0.0} self.assertEqual(expected, betweenness) + + def test_closeness_centrality(self): + betweenness = retworkx.graph_closeness_centrality(self.graph) + expected = {0: 0.5, 1: 0.75, 2: 0.75, 4: 0.5} + self.assertEqual(expected, betweenness) + + def test_closeness_centrality_wf_improved(self): + betweenness = retworkx.graph_closeness_centrality(self.graph, wf_improved=False) + expected = {0: 0.5, 1: 0.75, 2: 0.75, 4: 0.5} + self.assertEqual(expected, betweenness) diff --git a/tests/test_dispatch.py b/tests/test_dispatch.py index 575bdda35..5bdc83e55 100644 --- a/tests/test_dispatch.py +++ b/tests/test_dispatch.py @@ -101,6 +101,10 @@ def test_betweenness_centrality(self): res = retworkx.betweenness_centrality(self.graph) self.assertIsInstance(res, retworkx.CentralityMapping) + def test_closeness_centrality(self): + res = retworkx.closeness_centrality(self.graph) + self.assertIsInstance(res, retworkx.CentralityMapping) + class TestDispatchPyDiGraph(TestDispatchPyGraph): From 46044a622f8f75bf376324f5b78f08c391cf6645 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Sun, 24 Apr 2022 10:54:22 +0900 Subject: [PATCH 02/10] fix typo --- retworkx-core/src/centrality.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/retworkx-core/src/centrality.rs b/retworkx-core/src/centrality.rs index 56d0a71c7..07beb2cbd 100644 --- a/retworkx-core/src/centrality.rs +++ b/retworkx-core/src/centrality.rs @@ -318,7 +318,7 @@ where /// ); /// /// // Calculate the betweeness centrality of DiGraph -/// let dg = petgraph::graph::Diraph::::from_edges(&[ +/// let dg = petgraph::graph::DiGraph::::from_edges(&[ /// (0, 4), (1, 2), (2, 3), (3, 4), (1, 4) /// ]); /// let output = closeness_centrality(&dg, true); From 1a239469ee3ab59559419f40bb8eeee5caeffede Mon Sep 17 00:00:00 2001 From: derbuihan Date: Tue, 26 Apr 2022 23:04:26 +0900 Subject: [PATCH 03/10] add tests --- retworkx-core/src/centrality.rs | 16 +++-- retworkx-core/tests/centrality.rs | 112 ++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 retworkx-core/tests/centrality.rs diff --git a/retworkx-core/src/centrality.rs b/retworkx-core/src/centrality.rs index 07beb2cbd..653e45888 100644 --- a/retworkx-core/src/centrality.rs +++ b/retworkx-core/src/centrality.rs @@ -341,18 +341,22 @@ where let mut betweenness: Vec> = vec![None; max_index]; for node_s in graph.node_identifiers() { let is = graph.to_index(node_s); - let dists = dijkstra(Reversed(&graph), node_s, None, |_| 1).into_values(); - let reachable_node_count = dists.len(); - if reachable_node_count == 1 { + let map = dijkstra(Reversed(&graph), node_s, None, |_| 1); + let mut reachable_nodes_count = 0; + let mut dists_sum = 0; + for (_, &value) in map.iter() { + reachable_nodes_count += 1; + dists_sum += value; + } + if reachable_nodes_count == 1 { betweenness[is] = Some(0.0); continue; } - let dists_sum: usize = dists.sum(); - betweenness[is] = Some((reachable_node_count - 1) as f64 / dists_sum as f64); + betweenness[is] = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64); if wf_improved { let node_count = graph.node_count(); betweenness[is] = betweenness[is] - .map(|c| c * (reachable_node_count - 1) as f64 / (node_count - 1) as f64); + .map(|c| c * (reachable_nodes_count - 1) as f64 / (node_count - 1) as f64); } } betweenness diff --git a/retworkx-core/tests/centrality.rs b/retworkx-core/tests/centrality.rs new file mode 100644 index 000000000..39f92efea --- /dev/null +++ b/retworkx-core/tests/centrality.rs @@ -0,0 +1,112 @@ +use petgraph::visit::Reversed; +use petgraph::Graph; +use retworkx_core::centrality::closeness_centrality; +use retworkx_core::petgraph::graph::{DiGraph, UnGraph}; + +#[test] +fn test_simple() { + let g = UnGraph::::from_edges(&[(1, 2), (2, 3), (3, 4), (1, 4)]); + let c = closeness_centrality(&g, true); + assert_eq!( + vec![ + Some(0.0), + Some(0.5625), + Some(0.5625), + Some(0.5625), + Some(0.5625) + ], + c + ); +} + +#[test] +fn test_wf_improved() { + let g = UnGraph::::from_edges(&[(0, 1), (1, 2), (2, 3), (4, 5), (5, 6)]); + let c = closeness_centrality(&g, true); + assert_eq!( + vec![ + Some(1. / 4.), + Some(3. / 8.), + Some(3. / 8.), + Some(1. / 4.), + Some(2. / 9.), + Some(1. / 3.), + Some(2. / 9.) + ], + c + ); + let cwf = closeness_centrality(&g, false); + assert_eq!( + vec![ + Some(1. / 2.), + Some(3. / 4.), + Some(3. / 4.), + Some(1. / 2.), + Some(2. / 3.), + Some(1.), + Some(2. / 3.) + ], + cwf + ); +} + +#[test] +fn test_digraph() { + let g = DiGraph::::from_edges(&[(0, 1), (1, 2)]); + let c = closeness_centrality(&g, true); + assert_eq!(vec![Some(0.), Some(1. / 2.), Some(2. / 3.)], c); + + let cr = closeness_centrality(Reversed(&g), true); + assert_eq!(vec![Some(2. / 3.), Some(1. / 2.), Some(0.)], cr); +} + +#[test] +fn test_k5() { + let g = UnGraph::::from_edges(&[ + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 2), + (1, 3), + (1, 4), + (2, 3), + (2, 4), + (3, 4), + ]); + let c = closeness_centrality(&g, true); + assert_eq!( + vec![Some(1.0), Some(1.0), Some(1.0), Some(1.0), Some(1.0)], + c + ); +} + +#[test] +fn test_path() { + let g = UnGraph::::from_edges(&[(0, 1), (1, 2)]); + let c = closeness_centrality(&g, true); + assert_eq!(vec![Some(2. / 3.), Some(1.), Some(2. / 3.)], c); +} + +#[test] +fn test_weighted_closeness() { + let mut g = Graph::new(); + let s = g.add_node(0); + let u = g.add_node(0); + let x = g.add_node(0); + let v = g.add_node(0); + let y = g.add_node(0); + g.add_edge(s, u, 10.); + g.add_edge(s, x, 5.); + g.add_edge(u, v, 1.); + g.add_edge(u, x, 2.); + g.add_edge(v, y, 1.); + g.add_edge(x, u, 3.); + g.add_edge(x, v, 5.); + g.add_edge(x, y, 2.); + g.add_edge(y, s, 7.); + g.add_edge(y, v, 6.); + let c = closeness_centrality(&g, true); + println!("{:?}", c); + assert_eq!(0, 0) +} From 1de4da139b55caaf3e75983be8114b5bb5b15fd9 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Wed, 27 Apr 2022 21:36:47 +0900 Subject: [PATCH 04/10] apply the suggestions. --- retworkx-core/src/centrality.rs | 3 ++- retworkx-core/tests/centrality.rs | 42 ++++++++++++++++++++----------- src/centrality.rs | 20 +++++++-------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/retworkx-core/src/centrality.rs b/retworkx-core/src/centrality.rs index 653e45888..0acb0b376 100644 --- a/retworkx-core/src/centrality.rs +++ b/retworkx-core/src/centrality.rs @@ -331,11 +331,12 @@ pub fn closeness_centrality(graph: G, wf_improved: bool) -> Vec> where G: NodeIndexable + IntoNodeIdentifiers - + GraphBase + + GraphBase + IntoEdges + Visitable + NodeCount + IntoEdgesDirected, + G::NodeId: std::hash::Hash + Eq, { let max_index = graph.node_bound(); let mut betweenness: Vec> = vec![None; max_index]; diff --git a/retworkx-core/tests/centrality.rs b/retworkx-core/tests/centrality.rs index 39f92efea..d0008e92a 100644 --- a/retworkx-core/tests/centrality.rs +++ b/retworkx-core/tests/centrality.rs @@ -1,3 +1,15 @@ +// 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. + use petgraph::visit::Reversed; use petgraph::Graph; use retworkx_core::centrality::closeness_centrality; @@ -25,26 +37,26 @@ fn test_wf_improved() { let c = closeness_centrality(&g, true); assert_eq!( vec![ - Some(1. / 4.), - Some(3. / 8.), - Some(3. / 8.), - Some(1. / 4.), - Some(2. / 9.), - Some(1. / 3.), - Some(2. / 9.) + Some(1.0 / 4.0), + Some(3.0 / 8.0), + Some(3.0 / 8.0), + Some(1.0 / 4.0), + Some(2.0 / 9.0), + Some(1.0 / 3.0), + Some(2.0 / 9.0) ], c ); let cwf = closeness_centrality(&g, false); assert_eq!( vec![ - Some(1. / 2.), - Some(3. / 4.), - Some(3. / 4.), - Some(1. / 2.), - Some(2. / 3.), - Some(1.), - Some(2. / 3.) + Some(1.0 / 2.0), + Some(3.0 / 4.0), + Some(3.0 / 4.0), + Some(1.0 / 2.0), + Some(2.0 / 3.0), + Some(1.0), + Some(2.0 / 3.0) ], cwf ); @@ -85,7 +97,7 @@ fn test_k5() { fn test_path() { let g = UnGraph::::from_edges(&[(0, 1), (1, 2)]); let c = closeness_centrality(&g, true); - assert_eq!(vec![Some(2. / 3.), Some(1.), Some(2. / 3.)], c); + assert_eq!(vec![Some(2.0 / 3.0), Some(1.0), Some(2.0 / 3.0)], c); } #[test] diff --git a/src/centrality.rs b/src/centrality.rs index f9d872e4b..5c2e8e556 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -136,17 +136,10 @@ pub fn digraph_betweenness_centrality( /// Compute the closeness centrality of all nodes in a PyGraph. /// /// :param PyGraph graph: The input graph -/// :param bool normalized: Whether to normalize the betweenness scores by the number of distinct -/// paths between all pairs of nodes. -/// :param bool endpoints: Whether to include the endpoints of paths in pathlengths used to -/// compute the betweenness. -/// :param int parallel_threshold: The number of nodes to calculate the -/// the betweenness centrality in parallel at if the number of nodes in -/// the graph is less than this value it will run in a single thread. The -/// default value is 50 +/// :param bool wf_improved: If True, scale by the fraction of nodes reachable. /// -/// :returns: a read-only dict-like object whose keys are the node indices and values are the -/// betweenness score for each node. +/// :returns: a read-only dict-like object whose keys are the node indices and values are its +/// closeness centrality score for each node. /// :rtype: CentralityMapping #[pyfunction(wf_improved = "true")] #[pyo3(text_signature = "(graph, /, wf_improved=True)")] @@ -162,6 +155,13 @@ pub fn graph_closeness_centrality(graph: &graph::PyGraph, wf_improved: bool) -> } /// Compute the closeness centrality of all nodes in a PyDiGraph. +/// +/// :param PyDiGraph graph: The input digraph +/// :param bool wf_improved: If True, scale by the fraction of nodes reachable. +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are its +/// closeness centrality score for each node. +/// :rtype: CentralityMapping #[pyfunction(wf_improved = "true")] #[pyo3(text_signature = "(graph, /, wf_improved=True)")] pub fn digraph_closeness_centrality( From 05de1ac60da42607664e4e37680e5aa598830070 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Thu, 28 Apr 2022 00:11:50 +0900 Subject: [PATCH 05/10] add a releasenote and improve Doc --- ...closeness-centrality-459c5c7e35cb2e63.yaml | 29 +++++++++++++++++++ retworkx/__init__.py | 25 +++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml diff --git a/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml b/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml new file mode 100644 index 000000000..26b8ddc26 --- /dev/null +++ b/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml @@ -0,0 +1,29 @@ +--- +features: + - | + Added a new function, :func:`~retworkx.closeness_centrality` to compute + closeness centrality of all nodes in a :class:`~retworkx.PyGraph` or + :class:`~retworkx.PyDiGraph` object. + + The closeness centrality of a node :math:`u` is the reciprocal of the + average shortest path distance to :math:`u` over all :math:`n-1` reachable + nodes. + + .. math:: + + C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + where :math:`d(v, u)` is the shortest-path distance between :math:`v` and + :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. + + Wasserman and Faust propose an improved formula for graphs with more than + one connected component. The result is "a ratio of the fraction of actors + in the group who are reachable, to the average distance" from the reachable + actors. + + .. math:: + + C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + where :math:`N` is the number of nodes in the graph. By default, this is + enabled. diff --git a/retworkx/__init__.py b/retworkx/__init__.py index 991b77cf3..4d978c2c9 100644 --- a/retworkx/__init__.py +++ b/retworkx/__init__.py @@ -1561,8 +1561,31 @@ def _graph_betweenness_centrality(graph, normalized=True, endpoints=False, paral def closeness_centrality(graph, wf_improved=True): r"""Returns the closeness centrality of each node in the graph. + The closeness centrality of a node :math:`u` is the reciprocal of the + average shortest path distance to :math:`u` over all :math:`n-1` reachable + nodes. + + .. math:: + + C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + where :math:`d(v, u)` is the shortest-path distance between :math:`v` and + :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. + + Wasserman and Faust propose an improved formula for graphs with more than + one connected component. The result is "a ratio of the fraction of actors + in the group who are reachable, to the average distance" from the reachable + actors. + + .. math:: + + C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + where :math:`N` is the number of nodes in the graph. + :param PyDiGraph graph: The input graph - :param bool wf_improved: If True, scale by the fraction of nodes reachable. + :param bool wf_improved: This is optional; the default is True. If True, + scale by the fraction of nodes reachable. :returns: A dictionary mapping each node index to its closeness centrality. :rtype: dict From c8745d68fa9a167efa1eff7713625b59a2302539 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Thu, 5 May 2022 23:41:19 +0900 Subject: [PATCH 06/10] apply the suggestions --- retworkx-core/src/centrality.rs | 34 +++++++++++++++++-------------- retworkx-core/tests/centrality.rs | 24 ---------------------- retworkx/__init__.py | 3 ++- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/retworkx-core/src/centrality.rs b/retworkx-core/src/centrality.rs index 0acb0b376..dd5321248 100644 --- a/retworkx-core/src/centrality.rs +++ b/retworkx-core/src/centrality.rs @@ -295,19 +295,27 @@ where } } -/// Compute the closeness centrality of all nodes in a graph. +/// Compute the closeness centrality of each node in the graph. +/// +/// The closeness centrality of a node `u` is the reciprocal of the average +/// shortest path distance to `u` over all `n-1` reachable nodes. +/// +/// Wasserman and Faust propose an improved formula for graphs with more than +/// one connected component. The result is "a ratio of the fraction of actors +/// in the group who are reachable, to the average distance" from the reachable +/// actors. You can enable this by setting `wf_improved` to `true`. /// /// Arguments: /// /// * `graph` - The graph object to run the algorithm on -/// * `wf_improved` - If True, scale by the fraction of nodes reachable. +/// * `wf_improved` - If `true`, scale by the fraction of nodes reachable. /// /// # Example /// ```rust /// use retworkx_core::petgraph; /// use retworkx_core::centrality::closeness_centrality; /// -/// // Calculate the betweeness centrality of Graph +/// // Calculate the closeness centrality of Graph /// let g = petgraph::graph::UnGraph::::from_edges(&[ /// (0, 4), (1, 2), (2, 3), (3, 4), (1, 4) /// ]); @@ -317,7 +325,7 @@ where /// output /// ); /// -/// // Calculate the betweeness centrality of DiGraph +/// // Calculate the closeness centrality of DiGraph /// let dg = petgraph::graph::DiGraph::::from_edges(&[ /// (0, 4), (1, 2), (2, 3), (3, 4), (1, 4) /// ]); @@ -339,26 +347,22 @@ where G::NodeId: std::hash::Hash + Eq, { let max_index = graph.node_bound(); - let mut betweenness: Vec> = vec![None; max_index]; + let mut closeness: Vec> = vec![None; max_index]; for node_s in graph.node_identifiers() { let is = graph.to_index(node_s); let map = dijkstra(Reversed(&graph), node_s, None, |_| 1); - let mut reachable_nodes_count = 0; - let mut dists_sum = 0; - for (_, &value) in map.iter() { - reachable_nodes_count += 1; - dists_sum += value; - } + let reachable_nodes_count = map.len(); + let dists_sum: usize = map.into_values().sum(); if reachable_nodes_count == 1 { - betweenness[is] = Some(0.0); + closeness[is] = Some(0.0); continue; } - betweenness[is] = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64); + closeness[is] = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64); if wf_improved { let node_count = graph.node_count(); - betweenness[is] = betweenness[is] + closeness[is] = closeness[is] .map(|c| c * (reachable_nodes_count - 1) as f64 / (node_count - 1) as f64); } } - betweenness + closeness } diff --git a/retworkx-core/tests/centrality.rs b/retworkx-core/tests/centrality.rs index d0008e92a..0a28a097c 100644 --- a/retworkx-core/tests/centrality.rs +++ b/retworkx-core/tests/centrality.rs @@ -11,7 +11,6 @@ // under the License. use petgraph::visit::Reversed; -use petgraph::Graph; use retworkx_core::centrality::closeness_centrality; use retworkx_core::petgraph::graph::{DiGraph, UnGraph}; @@ -99,26 +98,3 @@ fn test_path() { let c = closeness_centrality(&g, true); assert_eq!(vec![Some(2.0 / 3.0), Some(1.0), Some(2.0 / 3.0)], c); } - -#[test] -fn test_weighted_closeness() { - let mut g = Graph::new(); - let s = g.add_node(0); - let u = g.add_node(0); - let x = g.add_node(0); - let v = g.add_node(0); - let y = g.add_node(0); - g.add_edge(s, u, 10.); - g.add_edge(s, x, 5.); - g.add_edge(u, v, 1.); - g.add_edge(u, x, 2.); - g.add_edge(v, y, 1.); - g.add_edge(x, u, 3.); - g.add_edge(x, v, 5.); - g.add_edge(x, y, 2.); - g.add_edge(y, s, 7.); - g.add_edge(y, v, 6.); - let c = closeness_centrality(&g, true); - println!("{:?}", c); - assert_eq!(0, 0) -} diff --git a/retworkx/__init__.py b/retworkx/__init__.py index 4d978c2c9..30f196a58 100644 --- a/retworkx/__init__.py +++ b/retworkx/__init__.py @@ -1583,7 +1583,8 @@ def closeness_centrality(graph, wf_improved=True): where :math:`N` is the number of nodes in the graph. - :param PyDiGraph graph: The input graph + :param graph: The input graph. Can either be a + :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. :param bool wf_improved: This is optional; the default is True. If True, scale by the fraction of nodes reachable. From d8fc186bf872699447bac9a0643d1ea99e1a95e4 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Fri, 6 May 2022 16:13:19 +0900 Subject: [PATCH 07/10] supports rust version 1.48.0 --- retworkx-core/src/centrality.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/retworkx-core/src/centrality.rs b/retworkx-core/src/centrality.rs index dd5321248..7b92ed0f2 100644 --- a/retworkx-core/src/centrality.rs +++ b/retworkx-core/src/centrality.rs @@ -352,7 +352,7 @@ where let is = graph.to_index(node_s); let map = dijkstra(Reversed(&graph), node_s, None, |_| 1); let reachable_nodes_count = map.len(); - let dists_sum: usize = map.into_values().sum(); + let dists_sum: usize = map.into_iter().map(|(_, v)| v).sum(); if reachable_nodes_count == 1 { closeness[is] = Some(0.0); continue; From 29516fbb532983c16707404aa60c25b361b39036 Mon Sep 17 00:00:00 2001 From: derbuihan Date: Fri, 6 May 2022 22:17:34 +0900 Subject: [PATCH 08/10] improve variable name --- src/centrality.rs | 56 ++++++++++++++++++++++++++++---- tests/digraph/test_centrality.py | 16 ++++----- tests/graph/test_centrality.py | 16 ++++----- 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/centrality.rs b/src/centrality.rs index 5c2e8e556..4660733e6 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -135,18 +135,40 @@ pub fn digraph_betweenness_centrality( /// Compute the closeness centrality of all nodes in a PyGraph. /// +/// The closeness centrality of a node :math:`u` is the reciprocal of the +/// average shortest path distance to :math:`u` over all :math:`n-1` reachable +/// nodes. +/// +/// .. math:: +/// +/// C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, +/// +/// where :math:`d(v, u)` is the shortest-path distance between :math:`v` and +/// :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. +/// +/// Wasserman and Faust propose an improved formula for graphs with more than +/// one connected component. The result is "a ratio of the fraction of actors +/// in the group who are reachable, to the average distance" from the reachable +/// actors. +/// +/// .. math:: +/// +/// C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, +/// +/// where :math:`N` is the number of nodes in the graph. +/// /// :param PyGraph graph: The input graph /// :param bool wf_improved: If True, scale by the fraction of nodes reachable. /// -/// :returns: a read-only dict-like object whose keys are the node indices and values are its -/// closeness centrality score for each node. +/// :returns: a read-only dict-like object whose keys are the node indices and +/// values are its closeness centrality score for each node. /// :rtype: CentralityMapping #[pyfunction(wf_improved = "true")] #[pyo3(text_signature = "(graph, /, wf_improved=True)")] pub fn graph_closeness_centrality(graph: &graph::PyGraph, wf_improved: bool) -> CentralityMapping { - let betweenness = centrality::closeness_centrality(&graph.graph, wf_improved); + let closeness = centrality::closeness_centrality(&graph.graph, wf_improved); CentralityMapping { - centralities: betweenness + centralities: closeness .into_iter() .enumerate() .filter_map(|(i, v)| v.map(|x| (i, x))) @@ -156,6 +178,28 @@ pub fn graph_closeness_centrality(graph: &graph::PyGraph, wf_improved: bool) -> /// Compute the closeness centrality of all nodes in a PyDiGraph. /// +/// The closeness centrality of a node :math:`u` is the reciprocal of the +/// average shortest path distance to :math:`u` over all :math:`n-1` reachable +/// nodes. +/// +/// .. math:: +/// +/// C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, +/// +/// where :math:`d(v, u)` is the shortest-path distance between :math:`v` and +/// :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. +/// +/// Wasserman and Faust propose an improved formula for graphs with more than +/// one connected component. The result is "a ratio of the fraction of actors +/// in the group who are reachable, to the average distance" from the reachable +/// actors. +/// +/// .. math:: +/// +/// C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, +/// +/// where :math:`N` is the number of nodes in the graph. +/// /// :param PyDiGraph graph: The input digraph /// :param bool wf_improved: If True, scale by the fraction of nodes reachable. /// @@ -168,9 +212,9 @@ pub fn digraph_closeness_centrality( graph: &digraph::PyDiGraph, wf_improved: bool, ) -> CentralityMapping { - let betweenness = centrality::closeness_centrality(&graph.graph, wf_improved); + let closeness = centrality::closeness_centrality(&graph.graph, wf_improved); CentralityMapping { - centralities: betweenness + centralities: closeness .into_iter() .enumerate() .filter_map(|(i, v)| v.map(|x| (i, x))) diff --git a/tests/digraph/test_centrality.py b/tests/digraph/test_centrality.py index 552716311..029cdf26a 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -86,14 +86,14 @@ def test_betweenness_centrality_unnormalized_parallel(self): self.assertEqual(expected, betweenness) def test_closeness_centrality(self): - betweenness = retworkx.digraph_closeness_centrality(self.graph) + closeness = retworkx.digraph_closeness_centrality(self.graph) expected = {0: 0.0, 1: 1.0 / 3.0, 2: 4.0 / 9.0, 3: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) def test_closeness_centrality_wf_improved(self): - betweenness = retworkx.digraph_closeness_centrality(self.graph, wf_improved=False) + closeness = retworkx.digraph_closeness_centrality(self.graph, wf_improved=False) expected = {0: 0.0, 1: 1.0, 2: 2.0 / 3.0, 3: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) class TestCentralityDiGraphDeletedNode(unittest.TestCase): @@ -140,11 +140,11 @@ def test_betweenness_centrality_unnormalized(self): self.assertEqual(expected, betweenness) def test_closeness_centrality(self): - betweenness = retworkx.digraph_closeness_centrality(self.graph) + closeness = retworkx.digraph_closeness_centrality(self.graph) expected = {0: 0.0, 1: 1.0 / 3.0, 2: 4.0 / 9.0, 4: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) def test_closeness_centrality_wf_improved(self): - betweenness = retworkx.digraph_closeness_centrality(self.graph, wf_improved=False) + closeness = retworkx.digraph_closeness_centrality(self.graph, wf_improved=False) expected = {0: 0.0, 1: 1.0, 2: 2.0 / 3.0, 4: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) diff --git a/tests/graph/test_centrality.py b/tests/graph/test_centrality.py index 5adbfb513..b1f366f75 100644 --- a/tests/graph/test_centrality.py +++ b/tests/graph/test_centrality.py @@ -57,14 +57,14 @@ def test_betweenness_centrality_unnormalized(self): self.assertEqual(expected, betweenness) def test_closeness_centrality(self): - betweenness = retworkx.graph_closeness_centrality(self.graph) + closeness = retworkx.graph_closeness_centrality(self.graph) expected = {0: 0.5, 1: 0.75, 2: 0.75, 3: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) def test_closeness_centrality_wf_improved(self): - betweenness = retworkx.graph_closeness_centrality(self.graph, wf_improved=False) + closeness = retworkx.graph_closeness_centrality(self.graph, wf_improved=False) expected = {0: 0.5, 1: 0.75, 2: 0.75, 3: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) class TestCentralityGraphDeletedNode(unittest.TestCase): @@ -111,11 +111,11 @@ def test_betweenness_centrality_unnormalized(self): self.assertEqual(expected, betweenness) def test_closeness_centrality(self): - betweenness = retworkx.graph_closeness_centrality(self.graph) + closeness = retworkx.graph_closeness_centrality(self.graph) expected = {0: 0.5, 1: 0.75, 2: 0.75, 4: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) def test_closeness_centrality_wf_improved(self): - betweenness = retworkx.graph_closeness_centrality(self.graph, wf_improved=False) + closeness = retworkx.graph_closeness_centrality(self.graph, wf_improved=False) expected = {0: 0.5, 1: 0.75, 2: 0.75, 4: 0.5} - self.assertEqual(expected, betweenness) + self.assertEqual(expected, closeness) From 6082d680b38a71db72ee20e579077fe6dc34c803 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 10 Mar 2023 12:44:01 -0500 Subject: [PATCH 09/10] Fix up docstrings and release note --- docs/source/api.rst | 4 +- ...closeness-centrality-459c5c7e35cb2e63.yaml | 55 ++++++++------ rustworkx-core/src/centrality.rs | 14 ++-- rustworkx/__init__.py | 36 ++++++---- src/centrality.rs | 72 +++++++++++-------- 5 files changed, 110 insertions(+), 71 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index de04bb87c..9d8010133 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -48,13 +48,13 @@ Shortest Paths .. _centrality: Centrality --------------- +---------- .. autosummary:: :toctree: apiref - retworkx.betweenness_centrality rustworkx.betweenness_centrality + rustworkx.edge_betweenness_centrality rustworkx.eigenvector_centrality rustworkx.closeness_centrality diff --git a/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml b/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml index 26b8ddc26..6252c21ce 100644 --- a/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml +++ b/releasenotes/notes/closeness-centrality-459c5c7e35cb2e63.yaml @@ -1,29 +1,42 @@ --- features: - | - Added a new function, :func:`~retworkx.closeness_centrality` to compute - closeness centrality of all nodes in a :class:`~retworkx.PyGraph` or - :class:`~retworkx.PyDiGraph` object. - - The closeness centrality of a node :math:`u` is the reciprocal of the - average shortest path distance to :math:`u` over all :math:`n-1` reachable - nodes. - + Added a new function, :func:`~.closeness_centrality` to compute the + closeness centrality of all nodes in a :class:`~.PyGraph` or + :class:`~.PyDiGraph` object. + + The closeness centrality of a node :math:`u` is defined as the the + reciprocal of the average shortest path distance to :math:`u` over all + :math:`n-1` reachable nodes. In it's general form this can be expressed as: + .. math:: - + C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, - + where :math:`d(v, u)` is the shortest-path distance between :math:`v` and :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. - - Wasserman and Faust propose an improved formula for graphs with more than - one connected component. The result is "a ratio of the fraction of actors - in the group who are reachable, to the average distance" from the reachable - actors. - - .. math:: + For example, to visualize the closeness centrality of a graph: + + .. jupyter-execute:: + + import matplotlib.pyplot as plt + + import rustworkx as rx + from rustworkx.visualization import mpl_draw - C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, - - where :math:`N` is the number of nodes in the graph. By default, this is - enabled. + graph = rx.generators.hexagonal_lattice_graph(4, 4) + centrality = rx.closeness_centrality(graph) + # Generate a color list + colors = [] + for node in graph.node_indices(): + colors.append(centrality[node]) + # Generate a visualization with a colorbar + plt.rcParams['figure.figsize'] = [15, 10] + ax = plt.gca() + sm = plt.cm.ScalarMappable(norm=plt.Normalize( + vmin=min(centrality.values()), + vmax=max(centrality.values()) + )) + plt.colorbar(sm, ax=ax) + plt.title("Closeness Centrality of a 4 x 4 Hexagonal Lattice Graph") + mpl_draw(graph, node_color=colors, ax=ax) diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index 89d56fbe1..b476ea6ff 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -194,7 +194,7 @@ where /// Ulrik Brandes: On Variants of Shortest-Path Betweenness /// Centrality and their Generic Computation. /// Social Networks 30(2):136-145, 2008. -/// https://doi.org/10.1016/j.socnet.2007.11.001. +/// . /// /// This function is multithreaded and will run in parallel if the number /// of nodes in the graph is above the value of ``parallel_threshold``. If the @@ -838,10 +838,14 @@ mod test_eigenvector_centrality { /// The closeness centrality of a node `u` is the reciprocal of the average /// shortest path distance to `u` over all `n-1` reachable nodes. /// -/// Wasserman and Faust propose an improved formula for graphs with more than -/// one connected component. The result is "a ratio of the fraction of actors -/// in the group who are reachable, to the average distance" from the reachable -/// actors. You can enable this by setting `wf_improved` to `true`. +/// In the case of a graphs with more than one connected component there is +/// an alternative improved formula that calculates the closeness centrality +/// as "a ratio of the fraction of actors in the group who are reachable, to +/// the average distance" [^WF]. You can enable this by setting `wf_improved` to `true`. +/// +/// [^WF] Wasserman, S., & Faust, K. (1994). Social Network Analysis: +/// Methods and Applications (Structural Analysis in the Social Sciences). +/// Cambridge: Cambridge University Press. doi:10.1017/CBO9780511815478 /// /// Arguments: /// diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index b0a09d109..40e3cd6c7 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -1602,29 +1602,34 @@ def _graph_betweenness_centrality(graph, normalized=True, endpoints=False, paral @functools.singledispatch def closeness_centrality(graph, wf_improved=True): - r"""Returns the closeness centrality of each node in the graph. + r"""Compute the closeness centrality of each node in a graph object. + + The closeness centrality of a node :math:`u` is defined as the + reciprocal of the average shortest path distance to :math:`u` over all + :math:`n-1` reachable nodes in the graph. In it's general form this can + be expressed as: - The closeness centrality of a node :math:`u` is the reciprocal of the - average shortest path distance to :math:`u` over all :math:`n-1` reachable - nodes. - .. math:: C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, - where :math:`d(v, u)` is the shortest-path distance between :math:`v` and - :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. + where: + + * :math:`d(v, u)` - the shortest-path distance between :math:`v` and + :math:`u` + * :math:`n` - the number of nodes that can reach :math:`u`. - Wasserman and Faust propose an improved formula for graphs with more than - one connected component. The result is "a ratio of the fraction of actors - in the group who are reachable, to the average distance" from the reachable - actors. + In the case of a graphs with more than one connected component there is + an alternative improved formula that calculates the closeness centrality + as "a ratio of the fraction of actors in the group who are reachable, to + the average distance" [WF]_. This can be expressed as .. math:: C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, - where :math:`N` is the number of nodes in the graph. + where :math:`N` is the number of nodes in the graph. This alternative + formula can be used with the ``wf_improved`` argument. :param graph: The input graph. Can either be a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. @@ -1633,6 +1638,10 @@ def closeness_centrality(graph, wf_improved=True): :returns: A dictionary mapping each node index to its closeness centrality. :rtype: dict + + .. [WF] Wasserman, S., & Faust, K. (1994). Social Network Analysis: + Methods and Applications (Structural Analysis in the Social Sciences). + Cambridge: Cambridge University Press. doi:10.1017/CBO9780511815478 """ raise TypeError("Invalid input type %s for graph" % type(graph)) @@ -1649,7 +1658,7 @@ def _graph_closeness_centrality(graph, wf_improved=True): @functools.singledispatch def edge_betweenness_centrality(graph, normalized=True, parallel_threshold=50): - """Compute the edge betweenness centrality of all edges in a graph. + r"""Compute the edge betweenness centrality of all edges in a graph. Edge betweenness centrality of an edge :math:`e` is the sum of the fraction of all-pairs shortest paths that pass through :math`e` @@ -1688,6 +1697,7 @@ def edge_betweenness_centrality(graph, normalized=True, parallel_threshold=50): betweenness score for each node. :rtype: EdgeCentralityMapping """ + raise TypeError("Invalid input type %s for graph" % type(graph)) @edge_betweenness_centrality.register(PyDiGraph) diff --git a/src/centrality.rs b/src/centrality.rs index c5ac3acb1..553fe9f97 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -160,35 +160,41 @@ pub fn digraph_betweenness_centrality( } } -/// Compute the closeness centrality of all nodes in a PyGraph. +/// Compute the closeness centrality of each node in a :class:`~.PyGraph` object. /// -/// The closeness centrality of a node :math:`u` is the reciprocal of the -/// average shortest path distance to :math:`u` over all :math:`n-1` reachable -/// nodes. +/// The closeness centrality of a node :math:`u` is defined as the +/// reciprocal of the average shortest path distance to :math:`u` over all +/// :math:`n-1` reachable nodes in the graph. In it's general form this can +/// be expressed as: /// /// .. math:: /// /// C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, /// -/// where :math:`d(v, u)` is the shortest-path distance between :math:`v` and -/// :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. +/// where: /// -/// Wasserman and Faust propose an improved formula for graphs with more than -/// one connected component. The result is "a ratio of the fraction of actors -/// in the group who are reachable, to the average distance" from the reachable -/// actors. +/// * :math:`d(v, u)` - the shortest-path distance between :math:`v` and +/// :math:`u` +/// * :math:`n` - the number of nodes that can reach :math:`u`. +/// +/// In the case of a graphs with more than one connected component there is +/// an alternative improved formula that calculates the closeness centrality +/// as "a ratio of the fraction of actors in the group who are reachable, to +/// the average distance" [WF]_. This can be expressed as /// /// .. math:: /// /// C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, /// -/// where :math:`N` is the number of nodes in the graph. +/// where :math:`N` is the number of nodes in the graph. This alternative +/// formula can be used with the ``wf_improved`` argument. /// -/// :param PyGraph graph: The input graph -/// :param bool wf_improved: If True, scale by the fraction of nodes reachable. +/// :param PyGraph graph: The input graph. Can either be a +/// :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. +/// :param bool wf_improved: This is optional; the default is True. If True, +/// scale by the fraction of nodes reachable. /// -/// :returns: a read-only dict-like object whose keys are the node indices and -/// values are its closeness centrality score for each node. +/// :returns: A dictionary mapping each node index to its closeness centrality. /// :rtype: CentralityMapping #[pyfunction(signature = (graph, wf_improved=true))] pub fn graph_closeness_centrality(graph: &graph::PyGraph, wf_improved: bool) -> CentralityMapping { @@ -202,35 +208,41 @@ pub fn graph_closeness_centrality(graph: &graph::PyGraph, wf_improved: bool) -> } } -/// Compute the closeness centrality of all nodes in a PyDiGraph. +/// Compute the closeness centrality of each node in a :class:`~.PyDiGraph` object. /// -/// The closeness centrality of a node :math:`u` is the reciprocal of the -/// average shortest path distance to :math:`u` over all :math:`n-1` reachable -/// nodes. +/// The closeness centrality of a node :math:`u` is defined as the +/// reciprocal of the average shortest path distance to :math:`u` over all +/// :math:`n-1` reachable nodes in the graph. In it's general form this can +/// be expressed as: /// /// .. math:: /// /// C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, /// -/// where :math:`d(v, u)` is the shortest-path distance between :math:`v` and -/// :math:`u`, and :math:`n` is the number of nodes that can reach :math:`u`. +/// where: +/// +/// * :math:`d(v, u)` - the shortest-path distance between :math:`v` and +/// :math:`u` +/// * :math:`n` - the number of nodes that can reach :math:`u`. /// -/// Wasserman and Faust propose an improved formula for graphs with more than -/// one connected component. The result is "a ratio of the fraction of actors -/// in the group who are reachable, to the average distance" from the reachable -/// actors. +/// In the case of a graphs with more than one connected component there is +/// an alternative improved formula that calculates the closeness centrality +/// as "a ratio of the fraction of actors in the group who are reachable, to +/// the average distance" [WF]_. This can be expressed as /// /// .. math:: /// /// C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, /// -/// where :math:`N` is the number of nodes in the graph. +/// where :math:`N` is the number of nodes in the graph. This alternative +/// formula can be used with the ``wf_improved`` argument. /// -/// :param PyDiGraph graph: The input digraph -/// :param bool wf_improved: If True, scale by the fraction of nodes reachable. +/// :param PyDiGraph graph: The input graph. Can either be a +/// :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph`. +/// :param bool wf_improved: This is optional; the default is True. If True, +/// scale by the fraction of nodes reachable. /// -/// :returns: a read-only dict-like object whose keys are the node indices and values are its -/// closeness centrality score for each node. +/// :returns: A dictionary mapping each node index to its closeness centrality. /// :rtype: CentralityMapping #[pyfunction(signature = (graph, wf_improved=true))] pub fn digraph_closeness_centrality( From 55debb31a5d21f4cc807408cba65540bc2d6722c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 10 Mar 2023 13:41:06 -0500 Subject: [PATCH 10/10] Fix rustworkx-core tests --- rustworkx-core/src/centrality.rs | 4 ++-- {retworkx-core => rustworkx-core}/tests/centrality.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename {retworkx-core => rustworkx-core}/tests/centrality.rs (95%) diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index b476ea6ff..a816af427 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -854,8 +854,8 @@ mod test_eigenvector_centrality { /// /// # Example /// ```rust -/// use retworkx_core::petgraph; -/// use retworkx_core::centrality::closeness_centrality; +/// use rustworkx_core::petgraph; +/// use rustworkx_core::centrality::closeness_centrality; /// /// // Calculate the closeness centrality of Graph /// let g = petgraph::graph::UnGraph::::from_edges(&[ diff --git a/retworkx-core/tests/centrality.rs b/rustworkx-core/tests/centrality.rs similarity index 95% rename from retworkx-core/tests/centrality.rs rename to rustworkx-core/tests/centrality.rs index 0a28a097c..66fa8dc06 100644 --- a/retworkx-core/tests/centrality.rs +++ b/rustworkx-core/tests/centrality.rs @@ -11,8 +11,8 @@ // under the License. use petgraph::visit::Reversed; -use retworkx_core::centrality::closeness_centrality; -use retworkx_core::petgraph::graph::{DiGraph, UnGraph}; +use rustworkx_core::centrality::closeness_centrality; +use rustworkx_core::petgraph::graph::{DiGraph, UnGraph}; #[test] fn test_simple() {