From 651409f403cfd458ab6c92aae0e2a3e6df4b9484 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 5 Oct 2023 18:32:00 -0400 Subject: [PATCH 1/3] Fix build errors with Rust 1.73.0 (#996) The recent Rust 1.73.0 release introduced some changes to clippy's behavior that are causing failures in CI (and correctly calling out issues in our code). This commit updates the rustworkx source to correct these failures. --- rustworkx-core/src/centrality.rs | 2 +- src/dag_algo/mod.rs | 1 - src/matching/mod.rs | 2 +- src/score.rs | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index c099c6304..2d856244f 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -738,7 +738,7 @@ where { let alpha: f64 = alpha.unwrap_or(0.1); - let mut beta: HashMap = beta_map.unwrap_or_else(HashMap::new); + let mut beta: HashMap = beta_map.unwrap_or_default(); if beta.is_empty() { // beta_map was none diff --git a/src/dag_algo/mod.rs b/src/dag_algo/mod.rs index 489cf2344..04068342d 100644 --- a/src/dag_algo/mod.rs +++ b/src/dag_algo/mod.rs @@ -628,7 +628,6 @@ pub fn collect_bicolor_runs( } } else { for color in colors { - let color = color; ensure_vector_has_index!(pending_list, block_id, color); if let Some(color_block_id) = block_id[color] { block_list[color_block_id].append(&mut pending_list[color]); diff --git a/src/matching/mod.rs b/src/matching/mod.rs index fac47e973..af9740ad9 100644 --- a/src/matching/mod.rs +++ b/src/matching/mod.rs @@ -92,7 +92,7 @@ fn _inner_is_matching(graph: &graph::PyGraph, matching: &HashSet<(usize, usize)> .contains_edge(NodeIndex::new(e.0), NodeIndex::new(e.1)) }; - if !matching.iter().all(|e| has_edge(e)) { + if !matching.iter().all(has_edge) { return false; } let mut found: HashSet = HashSet::with_capacity(2 * matching.len()); diff --git a/src/score.rs b/src/score.rs index 4dc888c5b..95e0d5bea 100644 --- a/src/score.rs +++ b/src/score.rs @@ -10,6 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. #![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::incorrect_partial_ord_impl_on_ord_type)] use std::cmp::Ordering; use std::ops::{Add, AddAssign}; From c5b41fc51b7d47001010b933a8e0a6979af9c4bd Mon Sep 17 00:00:00 2001 From: Arjun Bhamra <33864884+abhamra@users.noreply.github.com> Date: Thu, 5 Oct 2023 23:21:28 -0400 Subject: [PATCH 2/3] Added clear, clear_edges functions to PyGraph and PyDiGraph objects (#993) * First pass at testing infrastructure + graph.rs file changes * Fixed clear, clear_edges for graph.rs and digraph.rs, added tests, added documentation * fix for docstring for clear_edges in digraph * docstring fix for graph.rs * Minor fixes for codestyle for python lint * Added new tests to show reuse after using clear, clear_edges, updated docs * Adding fix for build error with prev commit * fixes from black codestyle format * fixed flake8 F841 - variable assigned to but never used errors * retry at fix for F841 error, used noqa * fixes for black * fixes for CICD, F841 local variable unused error * flake8 fixes --- ...ear_edges-for-graphs-041b166aa541639c.yaml | 10 +++ src/digraph.rs | 14 ++++ src/graph.rs | 13 ++++ tests/rustworkx_tests/digraph/test_clear.py | 66 ++++++++++++++++ tests/rustworkx_tests/graph/test_clear.py | 76 +++++++++++++++++++ 5 files changed, 179 insertions(+) create mode 100644 releasenotes/notes/add-clear-and-clear_edges-for-graphs-041b166aa541639c.yaml create mode 100644 tests/rustworkx_tests/digraph/test_clear.py create mode 100644 tests/rustworkx_tests/graph/test_clear.py diff --git a/releasenotes/notes/add-clear-and-clear_edges-for-graphs-041b166aa541639c.yaml b/releasenotes/notes/add-clear-and-clear_edges-for-graphs-041b166aa541639c.yaml new file mode 100644 index 000000000..ef6931f54 --- /dev/null +++ b/releasenotes/notes/add-clear-and-clear_edges-for-graphs-041b166aa541639c.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added a new function, :func:`clear` that clears all nodes and edges + from a :class:`rustworkx.PyGraph` or :class:`rustworkx.PyDiGraph` + + - | + Added a new function, :func:`clear_edges` that clears all edges for + :class:`rustworkx.PyGraph` or :class:`rustworkx.PyDiGraph` without + modifying nodes diff --git a/src/digraph.rs b/src/digraph.rs index 0fe27c4c3..bf5395084 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -492,6 +492,20 @@ impl PyDiGraph { } false } + + /// Clear all nodes and edges + #[pyo3(text_signature = "(self)")] + pub fn clear(&mut self) { + self.graph.clear(); + self.node_removed = true; + } + + /// Clears all edges, leaves nodes intact + #[pyo3(text_signature = "(self)")] + pub fn clear_edges(&mut self) { + self.graph.clear_edges(); + } + /// Return the number of nodes in the graph #[pyo3(text_signature = "(self)")] pub fn num_nodes(&self) -> usize { diff --git a/src/graph.rs b/src/graph.rs index 1d97404c5..58d463813 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -368,6 +368,19 @@ impl PyGraph { false } + /// Clears all nodes and edges + #[pyo3(text_signature = "(self)")] + pub fn clear(&mut self) { + self.graph.clear(); + self.node_removed = true; + } + + /// Clears all edges, leaves nodes intact + #[pyo3(text_signature = "(self)")] + pub fn clear_edges(&mut self) { + self.graph.clear_edges(); + } + /// Return the number of nodes in the graph #[pyo3(text_signature = "(self)")] pub fn num_nodes(&self) -> usize { diff --git a/tests/rustworkx_tests/digraph/test_clear.py b/tests/rustworkx_tests/digraph/test_clear.py new file mode 100644 index 000000000..043ae43e6 --- /dev/null +++ b/tests/rustworkx_tests/digraph/test_clear.py @@ -0,0 +1,66 @@ +# 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 unittest + +import rustworkx + + +class TestClear(unittest.TestCase): + def test_clear(self): + dag = rustworkx.PyDAG() + node_a = dag.add_node("a") + dag.add_child(node_a, "b", {"a": 1}) + dag.add_child(node_a, "c", {"a": 2}) + dag.clear() + self.assertEqual(dag.num_nodes(), 0) + self.assertEqual(dag.num_edges(), 0) + self.assertEqual(dag.nodes(), []) + self.assertEqual(dag.edges(), []) + + def test_clear_reuse(self): + dag = rustworkx.PyDAG() + node_a = dag.add_node("a") + dag.add_child(node_a, "b", {"a": 1}) + dag.add_child(node_a, "c", {"a": 2}) + dag.clear() + node_a = dag.add_node("a") + dag.add_child(node_a, "b", {"a": 1}) + dag.add_child(node_a, "c", {"a": 2}) + self.assertEqual(dag.num_nodes(), 3) + self.assertEqual(dag.num_edges(), 2) + self.assertEqual(dag.nodes(), ["a", "b", "c"]) + self.assertEqual(dag.edges(), [{"a": 1}, {"a": 2}]) + + def test_clear_edges(self): + dag = rustworkx.PyDAG() + node_a = dag.add_node("a") + dag.add_child(node_a, "b", {"a": 1}) + dag.add_child(node_a, "c", {"a": 2}) + dag.clear_edges() + self.assertEqual(dag.num_nodes(), 3) + self.assertEqual(dag.num_edges(), 0) + self.assertEqual(dag.nodes(), ["a", "b", "c"]) + self.assertEqual(dag.edges(), []) + + def test_clear_edges_reuse(self): + dag = rustworkx.PyDAG() + node_a = dag.add_node("a") + node_b = dag.add_child(node_a, "b", {"a": 1}) + node_c = dag.add_child(node_a, "c", {"a": 2}) + dag.clear_edges() + dag.add_edge(node_a, node_b, {"a": 1}) + dag.add_edge(node_a, node_c, {"a": 2}) + self.assertEqual(dag.num_nodes(), 3) + self.assertEqual(dag.num_edges(), 2) + self.assertEqual(dag.nodes(), ["a", "b", "c"]) + self.assertEqual(dag.edges(), [{"a": 1}, {"a": 2}]) diff --git a/tests/rustworkx_tests/graph/test_clear.py b/tests/rustworkx_tests/graph/test_clear.py new file mode 100644 index 000000000..3672564fb --- /dev/null +++ b/tests/rustworkx_tests/graph/test_clear.py @@ -0,0 +1,76 @@ +# Licensed under the Apache License, Version 3.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 unittest + +import rustworkx + + +class TestClear(unittest.TestCase): + def test_clear(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + graph.add_edge(node_a, node_b, {"a": 1}) + node_c = graph.add_node("c") + graph.add_edge(node_a, node_c, {"a": 2}) + graph.clear() + self.assertEqual(graph.num_nodes(), 0) + self.assertEqual(graph.num_edges(), 0) + self.assertEqual(graph.nodes(), []) + self.assertEqual(graph.edges(), []) + + def test_clear_reuse(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + graph.add_edge(node_a, node_b, {"a": 1}) + node_c = graph.add_node("c") + graph.add_edge(node_a, node_c, {"a": 2}) + graph.clear() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + graph.add_edge(node_a, node_b, {"a": 1}) + node_c = graph.add_node("c") + graph.add_edge(node_a, node_c, {"a": 2}) + self.assertEqual(graph.num_nodes(), 3) + self.assertEqual(graph.num_edges(), 2) + self.assertEqual(graph.nodes(), ["a", "b", "c"]) + self.assertEqual(graph.edges(), [{"a": 1}, {"a": 2}]) + + def test_clear_edges(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + graph.add_edge(node_a, node_b, {"e1", 1}) + node_c = graph.add_node("c") + graph.add_edge(node_a, node_c, {"e2", 2}) + graph.clear_edges() + self.assertEqual(graph.num_edges(), 0) + self.assertEqual(graph.edges(), []) + self.assertEqual(graph.num_nodes(), 3) + self.assertEqual(graph.nodes(), ["a", "b", "c"]) + + def test_clear_edges_reuse(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + graph.add_edge(node_a, node_b, {"e1", 1}) + node_c = graph.add_node("c") + graph.add_edge(node_a, node_c, {"e2", 2}) + graph.clear_edges() + graph.add_edge(node_a, node_b, {"e1", 1}) + graph.add_edge(node_a, node_c, {"e2", 2}) + self.assertEqual(graph.num_nodes(), 3) + self.assertEqual(graph.num_edges(), 2) + self.assertEqual(graph.nodes(), ["a", "b", "c"]) + self.assertEqual(graph.edges(), [{"e1", 1}, {"e2", 2}]) From 2d5694edb547d024e8831fb9789a47df920533b3 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Fri, 6 Oct 2023 16:25:23 +0300 Subject: [PATCH 3/3] mapping is not possible when a node has no neighbors (#995) * mapping is not possible when a node has no neighbors * suggested improvement --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- rustworkx-core/src/token_swapper.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rustworkx-core/src/token_swapper.rs b/rustworkx-core/src/token_swapper.rs index c60e31425..8e30e9a76 100644 --- a/rustworkx-core/src/token_swapper.rs +++ b/rustworkx-core/src/token_swapper.rs @@ -205,6 +205,11 @@ where } let id_node = self.rev_node_map[&node]; let id_token = self.rev_node_map[&tokens[&node]]; + + if self.graph.neighbors(id_node).next().is_none() { + return Err(MapNotPossible {}); + } + for id_neighbor in self.graph.neighbors(id_node) { let neighbor = self.node_map[&id_neighbor]; let dist_neighbor: DictMap = dijkstra( @@ -705,4 +710,19 @@ mod test_token_swapper { Err(_) => (), }; } + + #[test] + fn test_edgeless_graph_fails() { + let mut g = petgraph::graph::UnGraph::<(), ()>::new_undirected(); + let a = g.add_node(()); + let b = g.add_node(()); + let c = g.add_node(()); + let d = g.add_node(()); + g.add_edge(c, d, ()); + let mapping = HashMap::from([(a, b), (b, a)]); + match token_swapper(&g, mapping, Some(10), Some(4), Some(50)) { + Ok(_) => panic!("This should error"), + Err(_) => (), + }; + } }