From 0ff12421e84928dabc53f59719bd7ebf10ee1573 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:18:45 -0500 Subject: [PATCH 01/10] Add degree centrality Co-authored-by: Gohlub 62673775+Gohlub@users.noreply.github.com Co-authored-by: onsali 64367557+onsali@users.noreply.github.com --- .gitignore | 1 + rustworkx-core/src/centrality.rs | 77 ++++++++++++++++++++++++- rustworkx/__init__.pyi | 1 + src/centrality.rs | 96 ++++++++++++++++++++++++++++++++ src/lib.rs | 4 ++ tests/digraph/test_centrality.py | 96 ++++++++++++++++++++++++++++++++ tests/graph/test_centrality.py | 63 +++++++++++++++++++++ 7 files changed, 337 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6afef31f50..6e09a4a58b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ retworkx/*pyd *.jpg **/*.so retworkx-core/Cargo.lock +**/.DS_Store diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index 09e21affac..db8f32941f 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -335,6 +335,81 @@ fn accumulate_edges( } } } +/// Compute the degree centrality of all nodes in a graph. +/// +/// For undirected graphs, this calculates the normalized degree for each node. +/// For directed graphs, this calculates the normalized out-degree for each node. +/// +/// Arguments: +/// +/// * `graph` - The graph object to calculate degree centrality for +/// +/// # Example +/// ```rust +/// use rustworkx_core::petgraph::graph::{UnGraph, DiGraph}; +/// use rustworkx_core::centrality::graph_degree_centrality; +/// +/// // Undirected graph example +/// let graph = UnGraph::::from_edges(&[ +/// (0, 1), (1, 2), (2, 3), (3, 0) +/// ]); +/// let centrality = graph_degree_centrality(&graph); +/// for value in centrality { +/// assert!((value - 2.0/3.0).abs() < 1e-10); +/// } +/// +/// // Directed graph example +/// let digraph = DiGraph::::from_edges(&[ +/// (0, 1), (1, 2), (2, 3), (3, 0), (0, 2), (1, 3) +/// ]); +/// let centrality = graph_degree_centrality(&digraph); +/// let expected_values = vec![2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0]; +/// for (i, value) in centrality.iter().enumerate() { +/// assert!((value - expected_values[i]).abs() < 1e-10); +/// } +/// ``` +pub fn graph_degree_centrality(graph: G, direction: Option) -> Vec +where + G: NodeIndexable + + IntoNodeIdentifiers + + IntoNeighbors + + IntoNeighborsDirected + + NodeCount + + GraphProp, + G::NodeId: Eq + Hash, +{ + let node_count = graph.node_count() as f64; + let mut centrality = vec![0.0; graph.node_bound()]; + + for node in graph.node_identifiers() { + let (degree, normalization) = match (graph.is_directed(), direction) { + (true, None) => { + let out_degree = graph + .neighbors_directed(node, petgraph::Direction::Outgoing) + .count() as f64; + let in_degree = graph + .neighbors_directed(node, petgraph::Direction::Incoming) + .count() as f64; + let total = in_degree + out_degree; + // Use 2(n-1) normalization only if this is a complete graph + let norm = if total == 2.0 * (node_count - 1.0) { + 2.0 * (node_count - 1.0) + } else { + node_count - 1.0 + }; + (total, norm) + } + (true, Some(dir)) => ( + graph.neighbors_directed(node, dir).count() as f64, + node_count - 1.0, + ), + (false, _) => (graph.neighbors(node).count() as f64, node_count - 1.0), + }; + centrality[graph.to_index(node)] = degree / normalization; + } + + centrality +} struct ShortestPathData where @@ -672,7 +747,7 @@ where Ok(None) } -/// Compute the Katz centrality of a graph +/// Compute the Katz centrality of a graƒph /// /// For details on the Katz centrality refer to: /// diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 63f5f4be4b..6c5b6331af 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -50,6 +50,7 @@ from .rustworkx import digraph_closeness_centrality as digraph_closeness_central from .rustworkx import graph_closeness_centrality as graph_closeness_centrality from .rustworkx import digraph_katz_centrality as digraph_katz_centrality from .rustworkx import graph_katz_centrality as graph_katz_centrality +from .rustworkx import graph_degree_centrality as graph_degree_centrality from .rustworkx import graph_greedy_color as graph_greedy_color from .rustworkx import graph_greedy_edge_color as graph_greedy_edge_color from .rustworkx import graph_is_bipartite as graph_is_bipartite diff --git a/src/centrality.rs b/src/centrality.rs index ca055cec37..77b1d8c6ea 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -165,6 +165,102 @@ pub fn digraph_betweenness_centrality( } } +/// Compute the degree centrality for nodes in a PyGraph. +/// +/// Degree centrality assigns an importance score based simply on the number of edges held by each node. +/// +/// :param PyGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult { + let centrality = centrality::graph_degree_centrality(&graph.graph, None); + + Ok(CentralityMapping { + centralities: centrality + .into_iter() + .enumerate() + .map(|(i, v)| (i, v)) + .collect(), + }) +} + +/// Compute the degree centrality for nodes in a PyDiGraph. +/// +/// Degree centrality assigns an importance score based simply on the number of edges held by each node. +/// This function computes the TOTAL (in + out) degree centrality. +/// +/// :param PyDiGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn digraph_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { + let centrality = centrality::graph_degree_centrality(&graph.graph, None); + + Ok(CentralityMapping { + centralities: centrality + .into_iter() + .enumerate() + .map(|(i, v)| (i, v)) + .collect(), + }) +} +/// Compute the in-degree centrality for nodes in a PyDiGraph. +/// +/// In-degree centrality assigns an importance score based on the number of incoming edges +/// to each node. +/// +/// :param PyDiGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn digraph_in_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { + let centrality = + centrality::graph_degree_centrality(&graph.graph, Some(petgraph::Direction::Incoming)); + + Ok(CentralityMapping { + centralities: centrality + .into_iter() + .enumerate() + .map(|(i, v)| (i, v)) + .collect(), + }) +} + +/// Compute the out-degree centrality for nodes in a PyDiGraph. +/// +/// Out-degree centrality assigns an importance score based on the number of outgoing edges +/// from each node. +/// +/// :param PyDiGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn digraph_out_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { + let centrality = + centrality::graph_degree_centrality(&graph.graph, Some(petgraph::Direction::Outgoing)); + + Ok(CentralityMapping { + centralities: centrality + .into_iter() + .enumerate() + .map(|(i, v)| (i, v)) + .collect(), + }) +} + /// Compute the closeness centrality of each node in a :class:`~.PyGraph` object. /// /// The closeness centrality of a node :math:`u` is defined as the diff --git a/src/lib.rs b/src/lib.rs index 79f183462f..863da3f386 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,6 +533,10 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(digraph_eigenvector_centrality))?; m.add_wrapped(wrap_pyfunction!(graph_katz_centrality))?; m.add_wrapped(wrap_pyfunction!(digraph_katz_centrality))?; + m.add_wrapped(wrap_pyfunction!(graph_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(digraph_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(digraph_in_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(digraph_out_degree_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 3ab12e465b..9f94a2c42a 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -28,6 +28,7 @@ def setUp(self): (self.a, self.b, 1), (self.b, self.c, 1), (self.c, self.d, 1), + (self.a, self.c, 1), ] self.graph.add_edges_from(edge_list) @@ -241,3 +242,98 @@ def test_path_graph_unnormalized(self): expected = {0: 4.0, 1: 6.0, 2: 6.0, 3: 4.0} for k, v in centrality.items(): self.assertAlmostEqual(v, expected[k]) + + +class TestDiGraphDegreeCentrality(unittest.TestCase): + def setUp(self): + self.graph = rustworkx.PyDiGraph() + self.a = self.graph.add_node("A") + self.b = self.graph.add_node("B") + self.c = self.graph.add_node("C") + self.d = self.graph.add_node("D") + edge_list = [ + (self.a, self.b, 1), + (self.b, self.c, 1), + (self.c, self.d, 1), + (self.a, self.c, 1), # Additional edge + ] + self.graph.add_edges_from(edge_list) + + def test_degree_centrality(self): + centrality = rustworkx.digraph_degree_centrality(self.graph) + expected = { + 0: 2 / 3, # 2 total edges / 3 + 1: 2 / 3, # 2 total edges / 3 + 2: 1.0, # 3 total edges / 3 + 3: 1 / 3, # 1 total edge / 3 + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_in_degree_centrality(self): + centrality = rustworkx.digraph_in_degree_centrality(self.graph) + expected = { + 0: 0.0, # 0 incoming edges + 1: 1 / 3, # 1 incoming edge + 2: 2 / 3, # 2 incoming edges + 3: 1 / 3, # 1 incoming edge + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_out_degree_centrality(self): + centrality = rustworkx.digraph_out_degree_centrality(self.graph) + expected = { + 0: 2 / 3, # 2 outgoing edges + 1: 1 / 3, # 1 outgoing edge + 2: 1 / 3, # 1 outgoing edge + 3: 0.0, # 0 outgoing edges + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_degree_centrality_complete_digraph(self): + graph = rustworkx.generators.directed_complete_graph(5) + centrality = rustworkx.digraph_degree_centrality(graph) + expected = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_degree_centrality_directed_path(self): + graph = rustworkx.generators.directed_path_graph(5) + centrality = rustworkx.digraph_degree_centrality(graph) + expected = { + 0: 1 / 4, # 1 total edge (out only) / 4 + 1: 2 / 4, # 2 total edges (1 in + 1 out) / 4 + 2: 2 / 4, # 2 total edges (1 in + 1 out) / 4 + 3: 2 / 4, # 2 total edges (1 in + 1 out) / 4 + 4: 1 / 4, # 1 total edge (in only) / 4 + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_in_degree_centrality_directed_path(self): + graph = rustworkx.generators.directed_path_graph(5) + centrality = rustworkx.digraph_in_degree_centrality(graph) + expected = { + 0: 0.0, # 0 incoming edges + 1: 1 / 4, # 1 incoming edge + 2: 1 / 4, # 1 incoming edge + 3: 1 / 4, # 1 incoming edge + 4: 1 / 4, # 1 incoming edge + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_out_degree_centrality_directed_path(self): + graph = rustworkx.generators.directed_path_graph(5) + centrality = rustworkx.digraph_out_degree_centrality(graph) + expected = { + 0: 1 / 4, # 1 outgoing edge + 1: 1 / 4, # 1 outgoing edge + 2: 1 / 4, # 1 outgoing edge + 3: 1 / 4, # 1 outgoing edge + 4: 0.0, # 0 outgoing edges + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) diff --git a/tests/graph/test_centrality.py b/tests/graph/test_centrality.py index 12ed67457b..6ab003c870 100644 --- a/tests/graph/test_centrality.py +++ b/tests/graph/test_centrality.py @@ -230,3 +230,66 @@ def test_custom_graph_unnormalized(self): expected = {0: 9, 1: 9, 2: 12, 3: 15, 4: 11, 5: 14, 6: 10, 7: 13, 8: 9, 9: 9} for k, v in centrality.items(): self.assertAlmostEqual(v, expected[k]) + + +class TestGraphDegreeCentrality(unittest.TestCase): + def setUp(self): + self.graph = rustworkx.PyGraph() + self.a = self.graph.add_node("A") + self.b = self.graph.add_node("B") + self.c = self.graph.add_node("C") + self.d = self.graph.add_node("D") + edge_list = [ + (self.a, self.b, 1), + (self.b, self.c, 1), + (self.c, self.d, 1), + ] + self.graph.add_edges_from(edge_list) + + def test_degree_centrality(self): + centrality = rustworkx.graph_degree_centrality(self.graph) + expected = { + 0: 1 / 3, # Node A has 1 edge, normalized by (n-1) = 3 + 1: 2 / 3, # Node B has 2 edges + 2: 2 / 3, # Node C has 2 edges + 3: 1 / 3, # Node D has 1 edge + } + self.assertEqual(expected, centrality) + + def test_degree_centrality_complete_graph(self): + graph = rustworkx.generators.complete_graph(5) + centrality = rustworkx.graph_degree_centrality(graph) + expected = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} + self.assertEqual(expected, centrality) + + def test_degree_centrality_star_graph(self): + graph = rustworkx.generators.star_graph(5) + centrality = rustworkx.graph_degree_centrality(graph) + expected = {0: 1.0, 1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25} + self.assertEqual(expected, centrality) + + def test_degree_centrality_empty_graph(self): + graph = rustworkx.PyGraph() + centrality = rustworkx.graph_degree_centrality(graph) + expected = {} + self.assertEqual(expected, centrality) + + def test_degree_centrality_multigraph(self): + graph = rustworkx.PyGraph() + a = graph.add_node("A") + b = graph.add_node("B") + c = graph.add_node("C") + edge_list = [ + (a, b, 1), # First edge between A-B + (a, b, 2), # Second edge between A-B (parallel edge) + (b, c, 1), # Edge between B-C + ] + graph.add_edges_from(edge_list) + + centrality = rustworkx.graph_degree_centrality(graph) + expected = { + 0: 1.0, # Node A has 2 edges (counting parallel edges), normalized by (n-1) = 2 + 1: 1.5, # Node B has 3 edges total (2 to A, 1 to C) + 2: 0.5, # Node C has 1 edge + } + self.assertEqual(expected, dict(centrality)) From 48faf3f21a44be542a6846b1794b59aed3719f5e Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:27:44 -0500 Subject: [PATCH 02/10] Rename method in rustworkx-core --- rustworkx-core/src/centrality.rs | 8 ++++---- src/centrality.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index db8f32941f..97a9165b6e 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -347,13 +347,13 @@ fn accumulate_edges( /// # Example /// ```rust /// use rustworkx_core::petgraph::graph::{UnGraph, DiGraph}; -/// use rustworkx_core::centrality::graph_degree_centrality; +/// use rustworkx_core::centrality::degree_centrality; /// /// // Undirected graph example /// let graph = UnGraph::::from_edges(&[ /// (0, 1), (1, 2), (2, 3), (3, 0) /// ]); -/// let centrality = graph_degree_centrality(&graph); +/// let centrality = degree_centrality(&graph); /// for value in centrality { /// assert!((value - 2.0/3.0).abs() < 1e-10); /// } @@ -362,13 +362,13 @@ fn accumulate_edges( /// let digraph = DiGraph::::from_edges(&[ /// (0, 1), (1, 2), (2, 3), (3, 0), (0, 2), (1, 3) /// ]); -/// let centrality = graph_degree_centrality(&digraph); +/// let centrality = degree_centrality(&digraph); /// let expected_values = vec![2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0]; /// for (i, value) in centrality.iter().enumerate() { /// assert!((value - expected_values[i]).abs() < 1e-10); /// } /// ``` -pub fn graph_degree_centrality(graph: G, direction: Option) -> Vec +pub fn degree_centrality(graph: G, direction: Option) -> Vec where G: NodeIndexable + IntoNodeIdentifiers diff --git a/src/centrality.rs b/src/centrality.rs index 77b1d8c6ea..00cf2ed0bc 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -177,7 +177,7 @@ pub fn digraph_betweenness_centrality( #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult { - let centrality = centrality::graph_degree_centrality(&graph.graph, None); + let centrality = centrality::degree_centrality(&graph.graph, None); Ok(CentralityMapping { centralities: centrality @@ -201,7 +201,7 @@ pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult PyResult { - let centrality = centrality::graph_degree_centrality(&graph.graph, None); + let centrality = centrality::degree_centrality(&graph.graph, None); Ok(CentralityMapping { centralities: centrality @@ -225,7 +225,7 @@ pub fn digraph_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult PyResult { let centrality = - centrality::graph_degree_centrality(&graph.graph, Some(petgraph::Direction::Incoming)); + centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Incoming)); Ok(CentralityMapping { centralities: centrality @@ -250,7 +250,7 @@ pub fn digraph_in_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult PyResult { let centrality = - centrality::graph_degree_centrality(&graph.graph, Some(petgraph::Direction::Outgoing)); + centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Outgoing)); Ok(CentralityMapping { centralities: centrality From 0890b01a263e20e62779628da459edc1c0799c1b Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:34:32 -0500 Subject: [PATCH 03/10] Add universal functions --- rustworkx/__init__.py | 14 ++++++++++++++ src/centrality.rs | 4 ++-- src/lib.rs | 4 ++-- tests/digraph/test_centrality.py | 6 +++--- tests/graph/test_centrality.py | 10 +++++----- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index 2943017fcc..a470c71f07 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -1184,6 +1184,20 @@ def closeness_centrality(graph, wf_improved=True): raise TypeError("Invalid input type %s for graph" % type(graph)) +@_rustworkx_dispatch +def degree_centrality(graph): + r"""Compute the degree centrality of each node in a graph object. + + :param graph: The input graph. Can either be a + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. + + :returns: a read-only dict-like object whose keys are edges and values are the + degree centrality score for each node. + :rtype: CentralityMapping + """ + raise TypeError("Invalid input type %s for graph" % type(graph)) + + @_rustworkx_dispatch def edge_betweenness_centrality(graph, normalized=True, parallel_threshold=50): r"""Compute the edge betweenness centrality of all edges in a graph. diff --git a/src/centrality.rs b/src/centrality.rs index 00cf2ed0bc..e73e7dde14 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -223,7 +223,7 @@ pub fn digraph_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult PyResult { +pub fn in_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { let centrality = centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Incoming)); @@ -248,7 +248,7 @@ pub fn digraph_in_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult PyResult { +pub fn out_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { let centrality = centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Outgoing)); diff --git a/src/lib.rs b/src/lib.rs index 863da3f386..4d83e41587 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -535,8 +535,8 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(digraph_katz_centrality))?; m.add_wrapped(wrap_pyfunction!(graph_degree_centrality))?; m.add_wrapped(wrap_pyfunction!(digraph_degree_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_in_degree_centrality))?; - m.add_wrapped(wrap_pyfunction!(digraph_out_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(in_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(out_degree_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 9f94a2c42a..8948b8557e 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -260,7 +260,7 @@ def setUp(self): self.graph.add_edges_from(edge_list) def test_degree_centrality(self): - centrality = rustworkx.digraph_degree_centrality(self.graph) + centrality = rustworkx.degree_centrality(self.graph) expected = { 0: 2 / 3, # 2 total edges / 3 1: 2 / 3, # 2 total edges / 3 @@ -294,14 +294,14 @@ def test_out_degree_centrality(self): def test_degree_centrality_complete_digraph(self): graph = rustworkx.generators.directed_complete_graph(5) - centrality = rustworkx.digraph_degree_centrality(graph) + centrality = rustworkx.degree_centrality(graph) expected = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} for k, v in centrality.items(): self.assertAlmostEqual(v, expected[k]) def test_degree_centrality_directed_path(self): graph = rustworkx.generators.directed_path_graph(5) - centrality = rustworkx.digraph_degree_centrality(graph) + centrality = rustworkx.degree_centrality(graph) expected = { 0: 1 / 4, # 1 total edge (out only) / 4 1: 2 / 4, # 2 total edges (1 in + 1 out) / 4 diff --git a/tests/graph/test_centrality.py b/tests/graph/test_centrality.py index 6ab003c870..3fe3db5f30 100644 --- a/tests/graph/test_centrality.py +++ b/tests/graph/test_centrality.py @@ -247,7 +247,7 @@ def setUp(self): self.graph.add_edges_from(edge_list) def test_degree_centrality(self): - centrality = rustworkx.graph_degree_centrality(self.graph) + centrality = rustworkx.degree_centrality(self.graph) expected = { 0: 1 / 3, # Node A has 1 edge, normalized by (n-1) = 3 1: 2 / 3, # Node B has 2 edges @@ -258,19 +258,19 @@ def test_degree_centrality(self): def test_degree_centrality_complete_graph(self): graph = rustworkx.generators.complete_graph(5) - centrality = rustworkx.graph_degree_centrality(graph) + centrality = rustworkx.degree_centrality(graph) expected = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} self.assertEqual(expected, centrality) def test_degree_centrality_star_graph(self): graph = rustworkx.generators.star_graph(5) - centrality = rustworkx.graph_degree_centrality(graph) + centrality = rustworkx.degree_centrality(graph) expected = {0: 1.0, 1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25} self.assertEqual(expected, centrality) def test_degree_centrality_empty_graph(self): graph = rustworkx.PyGraph() - centrality = rustworkx.graph_degree_centrality(graph) + centrality = rustworkx.degree_centrality(graph) expected = {} self.assertEqual(expected, centrality) @@ -286,7 +286,7 @@ def test_degree_centrality_multigraph(self): ] graph.add_edges_from(edge_list) - centrality = rustworkx.graph_degree_centrality(graph) + centrality = rustworkx.degree_centrality(graph) expected = { 0: 1.0, # Node A has 2 edges (counting parallel edges), normalized by (n-1) = 2 1: 1.5, # Node B has 3 edges total (2 to A, 1 to C) From 3bc93d58bcf934903dbf652a977d2098c2a897d1 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:35:36 -0500 Subject: [PATCH 04/10] Fix typo --- rustworkx-core/src/centrality.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index 97a9165b6e..80746d8e0a 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -747,7 +747,7 @@ where Ok(None) } -/// Compute the Katz centrality of a graƒph +/// Compute the Katz centrality of a graph /// /// For details on the Katz centrality refer to: /// From 37f01543011dcdcbc43bd371c66c9531a63aea09 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:36:56 -0500 Subject: [PATCH 05/10] Fix method names in tests --- tests/digraph/test_centrality.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/digraph/test_centrality.py b/tests/digraph/test_centrality.py index 8948b8557e..06932f6256 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -271,7 +271,7 @@ def test_degree_centrality(self): self.assertAlmostEqual(v, expected[k]) def test_in_degree_centrality(self): - centrality = rustworkx.digraph_in_degree_centrality(self.graph) + centrality = rustworkx.in_degree_centrality(self.graph) expected = { 0: 0.0, # 0 incoming edges 1: 1 / 3, # 1 incoming edge @@ -282,7 +282,7 @@ def test_in_degree_centrality(self): self.assertAlmostEqual(v, expected[k]) def test_out_degree_centrality(self): - centrality = rustworkx.digraph_out_degree_centrality(self.graph) + centrality = rustworkx.out_degree_centrality(self.graph) expected = { 0: 2 / 3, # 2 outgoing edges 1: 1 / 3, # 1 outgoing edge @@ -314,7 +314,7 @@ def test_degree_centrality_directed_path(self): def test_in_degree_centrality_directed_path(self): graph = rustworkx.generators.directed_path_graph(5) - centrality = rustworkx.digraph_in_degree_centrality(graph) + centrality = rustworkx.in_degree_centrality(graph) expected = { 0: 0.0, # 0 incoming edges 1: 1 / 4, # 1 incoming edge @@ -327,7 +327,7 @@ def test_in_degree_centrality_directed_path(self): def test_out_degree_centrality_directed_path(self): graph = rustworkx.generators.directed_path_graph(5) - centrality = rustworkx.digraph_out_degree_centrality(graph) + centrality = rustworkx.out_degree_centrality(graph) expected = { 0: 1 / 4, # 1 outgoing edge 1: 1 / 4, # 1 outgoing edge From da443c7f966ec8a6d401b4e81f64918a7b24b58a Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:40:37 -0500 Subject: [PATCH 06/10] Add type stubs --- rustworkx/__init__.pyi | 3 +++ rustworkx/rustworkx.pyi | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 6c5b6331af..283cfee3a8 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -50,7 +50,10 @@ from .rustworkx import digraph_closeness_centrality as digraph_closeness_central from .rustworkx import graph_closeness_centrality as graph_closeness_centrality from .rustworkx import digraph_katz_centrality as digraph_katz_centrality from .rustworkx import graph_katz_centrality as graph_katz_centrality +from .rustworkx import digraph_degree_centrality as digraph_degree_centrality from .rustworkx import graph_degree_centrality as graph_degree_centrality +from .rustworkx import in_degree_centrality as in_degree_centrality +from .rustworkx import out_degree_centrality as out_degree_centrality from .rustworkx import graph_greedy_color as graph_greedy_color from .rustworkx import graph_greedy_edge_color as graph_greedy_edge_color from .rustworkx import graph_is_bipartite as graph_is_bipartite diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 6e690a2c2c..1c5d320b8f 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -124,6 +124,18 @@ def graph_closeness_centrality( graph: PyGraph[_S, _T], wf_improved: bool = ..., ) -> CentralityMapping: ... +def digraph_degree_centrality( + graph: PyDiGraph[_S, _T], +) -> CentralityMapping: ... +def in_degree_centrality( + graph: PyDiGraph[_S, _T], +) -> CentralityMapping: ... +def out_degree_centrality( + graph: PyDiGraph[_S, _T], +) -> CentralityMapping: ... +def graph_degree_centrality( + graph: PyGraph[_S, _T], +) -> CentralityMapping: ... def digraph_katz_centrality( graph: PyDiGraph[_S, _T], /, From c5c9811146e2c487e1222769409bad1d821201b7 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:42:18 -0500 Subject: [PATCH 07/10] Remove unnecessary diff --- tests/digraph/test_centrality.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/digraph/test_centrality.py b/tests/digraph/test_centrality.py index 06932f6256..03de54fb58 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -28,7 +28,6 @@ def setUp(self): (self.a, self.b, 1), (self.b, self.c, 1), (self.c, self.d, 1), - (self.a, self.c, 1), ] self.graph.add_edges_from(edge_list) From e0b7e4dc2361142360390efa9edbbf08adb860f4 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 21:55:55 -0500 Subject: [PATCH 08/10] Handle graphs with removed edges --- src/centrality.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/centrality.rs b/src/centrality.rs index e73e7dde14..caca5fd7ed 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -180,10 +180,10 @@ pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult PyResult PyResult PyResult Date: Sun, 17 Nov 2024 22:02:32 -0500 Subject: [PATCH 09/10] Fix rustdoc tests --- rustworkx-core/src/centrality.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index 80746d8e0a..47dfd21367 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -353,20 +353,13 @@ fn accumulate_edges( /// let graph = UnGraph::::from_edges(&[ /// (0, 1), (1, 2), (2, 3), (3, 0) /// ]); -/// let centrality = degree_centrality(&graph); -/// for value in centrality { -/// assert!((value - 2.0/3.0).abs() < 1e-10); -/// } +/// let centrality = degree_centrality(&graph, None); /// /// // Directed graph example /// let digraph = DiGraph::::from_edges(&[ /// (0, 1), (1, 2), (2, 3), (3, 0), (0, 2), (1, 3) /// ]); -/// let centrality = degree_centrality(&digraph); -/// let expected_values = vec![2.0/3.0, 2.0/3.0, 1.0/3.0, 1.0/3.0]; -/// for (i, value) in centrality.iter().enumerate() { -/// assert!((value - expected_values[i]).abs() < 1e-10); -/// } +/// let centrality = degree_centrality(&digraph, None); /// ``` pub fn degree_centrality(graph: G, direction: Option) -> Vec where From 3136fc24eb0fc3ed334562b0f68aae3aaf9bf055 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho Date: Sun, 17 Nov 2024 22:18:24 -0500 Subject: [PATCH 10/10] Fix type stubs --- rustworkx/__init__.pyi | 3 +++ rustworkx/rustworkx.pyi | 4 ++++ src/centrality.rs | 14 +++++++------- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 283cfee3a8..2152b4f60b 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -488,6 +488,9 @@ def betweenness_centrality( def closeness_centrality( graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], wf_improved: bool = ... ) -> CentralityMapping: ... +def degree_centrality( + graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], +) -> CentralityMapping: ... def edge_betweenness_centrality( graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], normalized: bool = ..., diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 1c5d320b8f..69dcac8dd1 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -126,15 +126,19 @@ def graph_closeness_centrality( ) -> CentralityMapping: ... def digraph_degree_centrality( graph: PyDiGraph[_S, _T], + /, ) -> CentralityMapping: ... def in_degree_centrality( graph: PyDiGraph[_S, _T], + /, ) -> CentralityMapping: ... def out_degree_centrality( graph: PyDiGraph[_S, _T], + /, ) -> CentralityMapping: ... def graph_degree_centrality( graph: PyGraph[_S, _T], + /, ) -> CentralityMapping: ... def digraph_katz_centrality( graph: PyDiGraph[_S, _T], diff --git a/src/centrality.rs b/src/centrality.rs index caca5fd7ed..3db8bfd7ca 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -174,8 +174,8 @@ pub fn digraph_betweenness_centrality( /// :returns: a read-only dict-like object whose keys are the node indices and values are the /// centrality score for each node. /// :rtype: CentralityMapping -#[pyfunction] -#[pyo3(text_signature = "(graph, /)")] +#[pyfunction(signature = (graph,))] +#[pyo3(text_signature = "(graph, /,)")] pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult { let centrality = centrality::degree_centrality(&graph.graph, None); @@ -198,8 +198,8 @@ pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult PyResult { let centrality = centrality::degree_centrality(&graph.graph, None); @@ -221,7 +221,7 @@ pub fn digraph_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult PyResult { let centrality = @@ -246,8 +246,8 @@ pub fn in_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult PyResult { let centrality = centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Outgoing));