From 49d859494cf43701134ea1488acfd11f206af6cf Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 12 May 2023 09:15:11 +0300 Subject: [PATCH 01/26] trying to implement line_graph and edge_coloring --- docs/source/api.rst | 1 + docs/source/sources.txt | 1 + src/coloring.rs | 63 +++++++++++++++++++- src/lib.rs | 1 + tests/rustworkx_tests/graph/test_coloring.py | 28 +++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 44a7dab7a..076478510 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -185,6 +185,7 @@ Other Algorithm Functions rustworkx.transitivity rustworkx.core_number rustworkx.graph_greedy_color + rustworkx.graph_greedy_edge_color rustworkx.metric_closure rustworkx.is_planar diff --git a/docs/source/sources.txt b/docs/source/sources.txt index ac2bb5b09..eb6a5f074 100644 --- a/docs/source/sources.txt +++ b/docs/source/sources.txt @@ -123,6 +123,7 @@ rustworkx.graph_distance_matrix.html rustworkx.graph_floyd_warshall.html rustworkx.graph_floyd_warshall_numpy.html rustworkx.graph_greedy_color.html +rustworkx.graph_greedy_edge_color.html rustworkx.graph_is_isomorphic.html rustworkx.graph_is_subgraph_isomorphic.html rustworkx.graph_k_shortest_path_lengths.html diff --git a/src/coloring.rs b/src/coloring.rs index 329eb8385..17daefb6e 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -11,8 +11,8 @@ // under the License. use crate::graph; +use crate::StablePyGraph; use rustworkx_core::dictmap::*; - use hashbrown::{HashMap, HashSet}; use std::cmp::Reverse; @@ -23,6 +23,8 @@ use pyo3::Python; use petgraph::graph::NodeIndex; use petgraph::prelude::*; use petgraph::visit::NodeCount; +use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences}; +use petgraph::EdgeType; use rayon::prelude::*; @@ -66,5 +68,64 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult( + py: Python, + graph: &StablePyGraph +) -> (StablePyGraph, HashMap) { + + let mut out_graph = StablePyGraph::::with_capacity(graph.edge_count(), 0); + let mut out_edge_map = HashMap::::with_capacity(graph.edge_count()); + + for edge in graph.edge_references() { + let e0 = edge.id(); + let n0 = out_graph.add_node(py.None()); + out_edge_map.insert(e0, n0); + } + + // There must be a better way to iterate over all pairs of edges, but I can't get + // combinations() to work. + for node in graph.node_references() { + for edge0 in graph.edges(node.0) { + for edge1 in graph.edges(node.0) { + if edge0.id().index() < edge1.id().index() { + let node0 = out_edge_map.get(&edge0.id()).unwrap(); + let node1 = out_edge_map.get(&edge1.id()).unwrap(); + out_graph.add_edge(*node0, *node1, py.None()); + } + } + } + } + + (out_graph, out_edge_map) +} + +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { + let (line_graph, edge_to_node_map) = line_graph(py, &graph.graph); + + let line_graph = graph::PyGraph { + graph: line_graph, + multigraph: false, + node_removed: false, + attrs: py.None(), + }; + + let colors = graph_greedy_color(py, &line_graph).unwrap(); + + println!("Examining colors of the line graph: {:?}", colors); + + // for a in colors { + // println!("{:?}", a); + // } + + + + let out_dict = PyDict::new(py); Ok(out_dict.into()) } diff --git a/src/lib.rs b/src/lib.rs index 7f941e2e6..39e2d6186 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -426,6 +426,7 @@ fn rustworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { 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))?; + m.add_wrapped(wrap_pyfunction!(graph_greedy_edge_color))?; m.add_wrapped(wrap_pyfunction!(graph_tensor_product))?; m.add_wrapped(wrap_pyfunction!(digraph_tensor_product))?; m.add_wrapped(wrap_pyfunction!(directed_gnp_random_graph))?; diff --git a/tests/rustworkx_tests/graph/test_coloring.py b/tests/rustworkx_tests/graph/test_coloring.py index f5ad611e4..878907dbb 100644 --- a/tests/rustworkx_tests/graph/test_coloring.py +++ b/tests/rustworkx_tests/graph/test_coloring.py @@ -44,3 +44,31 @@ def test_simple_graph_large_degree(self): graph.add_edge(node_a, node_c, 1) res = rustworkx.graph_greedy_color(graph) self.assertEqual({0: 0, 1: 1, 2: 1}, res) + + +class TestGrapEdgehColoring(unittest.TestCase): + def test_simple_graph(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node(1) + node_b = graph.add_node(2) + node_c = graph.add_node(3) + node_d = graph.add_node(4) + node_e = graph.add_node(5) + + edge_ab = graph.add_edge(node_a, node_b, 1) + edge_ac = graph.add_edge(node_a, node_c, 1) + edge_ad = graph.add_edge(node_a, node_d, 1) + edge_de = graph.add_edge(node_d, node_e, 1) + + print(edge_ab) + print(edge_ac) + print(edge_ad) + print(edge_de) + + print("============") + res = rustworkx.graph_greedy_edge_color(graph) + print(res) + + +if __name__ == "__main__": + unittest.main() From cbcc239e59e6373c9f2b3f2c089e6dde197c4446 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 13 May 2023 14:52:43 +0300 Subject: [PATCH 02/26] minor refactoring --- src/coloring.rs | 69 ++++++++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index 17daefb6e..7b38fb348 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -9,7 +9,6 @@ // 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 crate::graph; use crate::StablePyGraph; use rustworkx_core::dictmap::*; @@ -22,32 +21,26 @@ use pyo3::Python; use petgraph::graph::NodeIndex; use petgraph::prelude::*; -use petgraph::visit::NodeCount; +// use petgraph::visit::NodeCount; use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences}; use petgraph::EdgeType; use rayon::prelude::*; -/// Color a PyGraph using a largest_first strategy greedy graph coloring. -/// -/// :param PyGraph: The input PyGraph object to color -/// -/// :returns: A dictionary where keys are node indices and the value is -/// the color -/// :rtype: dict -#[pyfunction] -#[pyo3(text_signature = "(graph, /)")] -pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult { + +fn greedy_color( + graph: &StablePyGraph +) -> DictMap { let mut colors: DictMap = DictMap::new(); - let mut node_vec: Vec = graph.graph.node_indices().collect(); + let mut node_vec: Vec = graph.node_indices().collect(); let mut sort_map: HashMap = HashMap::with_capacity(graph.node_count()); for k in node_vec.iter() { - sort_map.insert(*k, graph.graph.edges(*k).count()); + sort_map.insert(*k, graph.edges(*k).count()); } node_vec.par_sort_by_key(|k| Reverse(sort_map.get(k))); for u_index in node_vec { let mut neighbor_colors: HashSet = HashSet::new(); - for edge in graph.graph.edges(u_index) { + for edge in graph.edges(u_index) { let target = edge.target().index(); let existing_color = match colors.get(&target) { Some(node) => node, @@ -64,11 +57,28 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult PyResult { + let colors = greedy_color(&graph.graph); + let out_dict = PyDict::new(py); for (index, color) in colors { out_dict.set_item(index, color)?; } - Ok(out_dict.into()) } @@ -107,25 +117,32 @@ fn line_graph( #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { + println!("original graph: {:?}", graph.graph); + let (line_graph, edge_to_node_map) = line_graph(py, &graph.graph); - let line_graph = graph::PyGraph { - graph: line_graph, - multigraph: false, - node_removed: false, - attrs: py.None(), - }; + println!("line_graph: {:?}", line_graph); + println!("edge_to_node_map: {:?}", edge_to_node_map); - let colors = graph_greedy_color(py, &line_graph).unwrap(); + let colors = greedy_color(&line_graph); println!("Examining colors of the line graph: {:?}", colors); - // for a in colors { - // println!("{:?}", a); + // for (index, color) in colors { + // println!("{:?}, {:?}", index, color); // } + let out_dict = PyDict::new(py); + for edge in graph.graph.edge_references() { + let e0 = edge.id(); + let n0 = edge_to_node_map.get(&e0).unwrap(); + let c0 = colors.get(&n0.index()).unwrap(); + println!("original edge {:?} corresponds to node {:?} and has color {:?}", e0, n0, c0); + + out_dict.set_item(e0.index(), c0)?; + + } - let out_dict = PyDict::new(py); Ok(out_dict.into()) } From 6c44a7af45963e0a73de1433d5773327a501820f Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 13 May 2023 18:39:46 +0300 Subject: [PATCH 03/26] more refactoring --- src/coloring.rs | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index 7b38fb348..2c24d0a86 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -114,35 +114,34 @@ fn line_graph( (out_graph, out_edge_map) } -#[pyfunction] -#[pyo3(text_signature = "(graph, /)")] -pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { - println!("original graph: {:?}", graph.graph); - - let (line_graph, edge_to_node_map) = line_graph(py, &graph.graph); - - println!("line_graph: {:?}", line_graph); - println!("edge_to_node_map: {:?}", edge_to_node_map); +fn greedy_edge_color( + py: Python, + graph: &StablePyGraph +) -> DictMap { + let (line_graph, edge_to_node_map) = line_graph(py, &graph); let colors = greedy_color(&line_graph); - println!("Examining colors of the line graph: {:?}", colors); - - // for (index, color) in colors { - // println!("{:?}, {:?}", index, color); - // } - - let out_dict = PyDict::new(py); + let mut edge_colors: DictMap = DictMap::new(); - for edge in graph.graph.edge_references() { + for edge in graph.edge_references() { let e0 = edge.id(); let n0 = edge_to_node_map.get(&e0).unwrap(); let c0 = colors.get(&n0.index()).unwrap(); - println!("original edge {:?} corresponds to node {:?} and has color {:?}", e0, n0, c0); + edge_colors.insert(e0.index(), *c0); + } + edge_colors +} - out_dict.set_item(e0.index(), c0)?; - } +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { + let edge_colors = greedy_edge_color(py, &graph.graph); + let out_dict = PyDict::new(py); + for (index, color) in edge_colors { + out_dict.set_item(index, color)?; + } Ok(out_dict.into()) } From 93bad32c29fe5c07c6162ab569f0402e8f94c7ec Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 16 May 2023 15:04:35 +0300 Subject: [PATCH 04/26] moving greedy_color to core --- rustworkx-core/src/coloring.rs | 77 ++++++++++++++++++++ rustworkx-core/src/lib.rs | 2 + rustworkx-core/tests/coloring.rs | 35 +++++++++ src/coloring.rs | 49 ++----------- tests/rustworkx_tests/graph/test_coloring.py | 4 +- 5 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 rustworkx-core/src/coloring.rs create mode 100644 rustworkx-core/tests/coloring.rs diff --git a/rustworkx-core/src/coloring.rs b/rustworkx-core/src/coloring.rs new file mode 100644 index 000000000..69523af02 --- /dev/null +++ b/rustworkx-core/src/coloring.rs @@ -0,0 +1,77 @@ +// 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 crate::dictmap::*; +use hashbrown::{HashMap, HashSet}; +use std::cmp::Reverse; + +use std::hash::Hash; + +use petgraph::visit::{ + NodeCount, + EdgeCount, + IntoEdges, + IntoNeighborsDirected, + IntoNodeIdentifiers, + NodeIndexable, + Visitable, + EdgeRef, + IntoEdgeReferences, +}; + +use rayon::prelude::*; + +pub fn greedy_color(graph: G) -> DictMap +where + G: NodeCount + + EdgeCount + + IntoEdges + + Visitable + + NodeIndexable + + IntoNeighborsDirected + + IntoNodeIdentifiers + + IntoEdgeReferences + , + G::NodeId: Hash + Eq + Send + Sync, + +{ + let mut colors: DictMap = DictMap::new(); + let mut node_vec: Vec = graph.node_identifiers().collect(); + + let mut sort_map: HashMap = HashMap::with_capacity(graph.node_count()); + for k in node_vec.iter() { + sort_map.insert(*k, graph.edges(*k).count()); + } + node_vec.par_sort_by_key(|k| Reverse(sort_map.get(k))); + + for node in node_vec { + let mut neighbor_colors: HashSet = HashSet::new(); + for edge in graph.edges(node) { + let target = edge.target(); + let existing_color = match colors.get(&target) { + Some(color) => color, + None => continue, + }; + neighbor_colors.insert(*existing_color); + } + let mut current_color: usize = 0; + loop { + if !neighbor_colors.contains(¤t_color) { + break; + } + current_color += 1; + } + colors.insert(node, current_color); + } + + colors +} diff --git a/rustworkx-core/src/lib.rs b/rustworkx-core/src/lib.rs index ab54ad5dc..301c35a36 100644 --- a/rustworkx-core/src/lib.rs +++ b/rustworkx-core/src/lib.rs @@ -85,6 +85,8 @@ pub mod distancemap; mod min_scored; /// Module for swapping tokens pub mod token_swapper; +/// Module for coloring algorithms. +pub mod coloring; pub mod utils; // re-export petgraph so there is a consistent version available to users and diff --git a/rustworkx-core/tests/coloring.rs b/rustworkx-core/tests/coloring.rs new file mode 100644 index 000000000..220cc0cb8 --- /dev/null +++ b/rustworkx-core/tests/coloring.rs @@ -0,0 +1,35 @@ +// 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 hashbrown::HashSet; +use petgraph::graph::Graph; +use petgraph::graph::NodeIndex; +use petgraph::visit::Visitable; +use petgraph::Undirected; + +use rustworkx_core::coloring::greedy_color; + +#[test] +fn test_blah() { + let graph = Graph::<(), (), Undirected>::from_edges(&[ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + (4, 5), + (5, 6), + (6, 7), + (7, 4), + ]); + let coloring = greedy_color(&graph); +} diff --git a/src/coloring.rs b/src/coloring.rs index 2c24d0a86..9bd166821 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -12,8 +12,7 @@ use crate::graph; use crate::StablePyGraph; use rustworkx_core::dictmap::*; -use hashbrown::{HashMap, HashSet}; -use std::cmp::Reverse; +use hashbrown::HashMap; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -21,46 +20,9 @@ use pyo3::Python; use petgraph::graph::NodeIndex; use petgraph::prelude::*; -// use petgraph::visit::NodeCount; use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences}; use petgraph::EdgeType; - -use rayon::prelude::*; - - -fn greedy_color( - graph: &StablePyGraph -) -> DictMap { - let mut colors: DictMap = DictMap::new(); - let mut node_vec: Vec = graph.node_indices().collect(); - let mut sort_map: HashMap = HashMap::with_capacity(graph.node_count()); - for k in node_vec.iter() { - sort_map.insert(*k, graph.edges(*k).count()); - } - node_vec.par_sort_by_key(|k| Reverse(sort_map.get(k))); - for u_index in node_vec { - let mut neighbor_colors: HashSet = HashSet::new(); - for edge in graph.edges(u_index) { - let target = edge.target().index(); - let existing_color = match colors.get(&target) { - Some(node) => node, - None => continue, - }; - neighbor_colors.insert(*existing_color); - } - let mut count: usize = 0; - loop { - if !neighbor_colors.contains(&count) { - break; - } - count += 1; - } - colors.insert(u_index.index(), count); - } - - colors - -} +use rustworkx_core::coloring::greedy_color; /// Color a PyGraph using a largest_first strategy greedy graph coloring. @@ -77,7 +39,7 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult( for edge in graph.edge_references() { let e0 = edge.id(); let n0 = edge_to_node_map.get(&e0).unwrap(); - let c0 = colors.get(&n0.index()).unwrap(); + let c0 = colors.get(n0).unwrap(); edge_colors.insert(e0.index(), *c0); } edge_colors } - #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { @@ -145,3 +106,5 @@ pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult

Date: Tue, 16 May 2023 15:35:50 +0300 Subject: [PATCH 05/26] running fmt --- rustworkx-core/src/coloring.rs | 15 +++------------ rustworkx-core/src/lib.rs | 4 ++-- rustworkx-core/tests/coloring.rs | 1 - src/coloring.rs | 15 +++------------ 4 files changed, 8 insertions(+), 27 deletions(-) diff --git a/rustworkx-core/src/coloring.rs b/rustworkx-core/src/coloring.rs index 69523af02..84bfb4ae9 100644 --- a/rustworkx-core/src/coloring.rs +++ b/rustworkx-core/src/coloring.rs @@ -17,15 +17,8 @@ use std::cmp::Reverse; use std::hash::Hash; use petgraph::visit::{ - NodeCount, - EdgeCount, - IntoEdges, - IntoNeighborsDirected, - IntoNodeIdentifiers, - NodeIndexable, - Visitable, - EdgeRef, - IntoEdgeReferences, + EdgeCount, EdgeRef, IntoEdgeReferences, IntoEdges, IntoNeighborsDirected, IntoNodeIdentifiers, + NodeCount, NodeIndexable, Visitable, }; use rayon::prelude::*; @@ -39,10 +32,8 @@ where + NodeIndexable + IntoNeighborsDirected + IntoNodeIdentifiers - + IntoEdgeReferences - , + + IntoEdgeReferences, G::NodeId: Hash + Eq + Send + Sync, - { let mut colors: DictMap = DictMap::new(); let mut node_vec: Vec = graph.node_identifiers().collect(); diff --git a/rustworkx-core/src/lib.rs b/rustworkx-core/src/lib.rs index 301c35a36..8743f8936 100644 --- a/rustworkx-core/src/lib.rs +++ b/rustworkx-core/src/lib.rs @@ -80,13 +80,13 @@ pub mod planar; pub mod shortest_path; pub mod traversal; // These modules define additional data structures +/// Module for coloring algorithms. +pub mod coloring; pub mod dictmap; pub mod distancemap; mod min_scored; /// Module for swapping tokens pub mod token_swapper; -/// Module for coloring algorithms. -pub mod coloring; pub mod utils; // re-export petgraph so there is a consistent version available to users and diff --git a/rustworkx-core/tests/coloring.rs b/rustworkx-core/tests/coloring.rs index 220cc0cb8..d507d715f 100644 --- a/rustworkx-core/tests/coloring.rs +++ b/rustworkx-core/tests/coloring.rs @@ -10,7 +10,6 @@ // License for the specific language governing permissions and limitations // under the License. - use hashbrown::HashSet; use petgraph::graph::Graph; use petgraph::graph::NodeIndex; diff --git a/src/coloring.rs b/src/coloring.rs index 9bd166821..b6e493f45 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -11,8 +11,8 @@ // under the License. use crate::graph; use crate::StablePyGraph; -use rustworkx_core::dictmap::*; use hashbrown::HashMap; +use rustworkx_core::dictmap::*; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -24,7 +24,6 @@ use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences}; use petgraph::EdgeType; use rustworkx_core::coloring::greedy_color; - /// Color a PyGraph using a largest_first strategy greedy graph coloring. /// /// :param PyGraph: The input PyGraph object to color @@ -44,12 +43,10 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult( py: Python, - graph: &StablePyGraph + graph: &StablePyGraph, ) -> (StablePyGraph, HashMap) { - let mut out_graph = StablePyGraph::::with_capacity(graph.edge_count(), 0); let mut out_edge_map = HashMap::::with_capacity(graph.edge_count()); @@ -76,11 +73,7 @@ fn line_graph( (out_graph, out_edge_map) } - -fn greedy_edge_color( - py: Python, - graph: &StablePyGraph -) -> DictMap { +fn greedy_edge_color(py: Python, graph: &StablePyGraph) -> DictMap { let (line_graph, edge_to_node_map) = line_graph(py, &graph); let colors = greedy_color(&line_graph); @@ -106,5 +99,3 @@ pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult

Date: Tue, 16 May 2023 15:37:28 +0300 Subject: [PATCH 06/26] running clippy --- src/coloring.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coloring.rs b/src/coloring.rs index b6e493f45..4015ae409 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -74,7 +74,7 @@ fn line_graph( } fn greedy_edge_color(py: Python, graph: &StablePyGraph) -> DictMap { - let (line_graph, edge_to_node_map) = line_graph(py, &graph); + let (line_graph, edge_to_node_map) = line_graph(py, graph); let colors = greedy_color(&line_graph); let mut edge_colors: DictMap = DictMap::new(); From 3cacbe32146c94d7666e03535190d6b36431e620 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 16 May 2023 16:38:02 +0300 Subject: [PATCH 07/26] adapting tests --- rustworkx-core/src/coloring.rs | 20 +++----------- rustworkx-core/tests/coloring.rs | 45 ++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/rustworkx-core/src/coloring.rs b/rustworkx-core/src/coloring.rs index 84bfb4ae9..8b3da298f 100644 --- a/rustworkx-core/src/coloring.rs +++ b/rustworkx-core/src/coloring.rs @@ -10,29 +10,17 @@ // License for the specific language governing permissions and limitations // under the License. -use crate::dictmap::*; -use hashbrown::{HashMap, HashSet}; use std::cmp::Reverse; - use std::hash::Hash; -use petgraph::visit::{ - EdgeCount, EdgeRef, IntoEdgeReferences, IntoEdges, IntoNeighborsDirected, IntoNodeIdentifiers, - NodeCount, NodeIndexable, Visitable, -}; - +use crate::dictmap::*; +use hashbrown::{HashMap, HashSet}; +use petgraph::visit::{EdgeRef, IntoEdges, IntoNodeIdentifiers, NodeCount}; use rayon::prelude::*; pub fn greedy_color(graph: G) -> DictMap where - G: NodeCount - + EdgeCount - + IntoEdges - + Visitable - + NodeIndexable - + IntoNeighborsDirected - + IntoNodeIdentifiers - + IntoEdgeReferences, + G: NodeCount + IntoNodeIdentifiers + IntoEdges, G::NodeId: Hash + Eq + Send + Sync, { let mut colors: DictMap = DictMap::new(); diff --git a/rustworkx-core/tests/coloring.rs b/rustworkx-core/tests/coloring.rs index d507d715f..41a1383cf 100644 --- a/rustworkx-core/tests/coloring.rs +++ b/rustworkx-core/tests/coloring.rs @@ -10,25 +10,42 @@ // License for the specific language governing permissions and limitations // under the License. -use hashbrown::HashSet; +//! Test module for coloring algorithms. + use petgraph::graph::Graph; use petgraph::graph::NodeIndex; -use petgraph::visit::Visitable; use petgraph::Undirected; +use rustworkx_core::dictmap::*; use rustworkx_core::coloring::greedy_color; #[test] -fn test_blah() { - let graph = Graph::<(), (), Undirected>::from_edges(&[ - (0, 1), - (1, 2), - (2, 3), - (3, 4), - (4, 5), - (5, 6), - (6, 7), - (7, 4), - ]); - let coloring = greedy_color(&graph); +fn test_greedy_color_empty_graph() { + let graph = Graph::<(), (), Undirected>::new_undirected(); + let colors = greedy_color(&graph); + let expected_colors = DictMap::new(); + assert_eq!(colors, expected_colors); +} + +#[test] +fn test_greedy_color_simple_graph() { + let graph = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2)]); + let colors = greedy_color(&graph); + let mut expected_colors = DictMap::new(); + expected_colors.insert(NodeIndex::new(0), 0); + expected_colors.insert(NodeIndex::new(1), 1); + expected_colors.insert(NodeIndex::new(2), 1); + assert_eq!(colors, expected_colors); +} + +#[test] +fn test_greedy_color_simple_graph_large_degree() { + let graph = + Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (0, 2), (0, 2), (0, 2), (0, 2)]); + let colors = greedy_color(&graph); + let mut expected_colors = DictMap::new(); + expected_colors.insert(NodeIndex::new(0), 0); + expected_colors.insert(NodeIndex::new(1), 1); + expected_colors.insert(NodeIndex::new(2), 1); + assert_eq!(colors, expected_colors); } From 3980a14f228312e21a20e7b498c05e02e8705e52 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 8 Jun 2023 11:49:18 +0300 Subject: [PATCH 08/26] fixes --- src/coloring.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index c3d4891c5..d6e114234 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -11,17 +11,18 @@ // under the License. use crate::graph; -use rustworkx_core::coloring::greedy_node_color; +use crate::StablePyGraph; +use hashbrown::HashMap; +use rustworkx_core::dictmap::*; use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::Python; -// The following three might not be needed -use crate::StablePyGraph; -use hashbrown::HashMap; -use rustworkx_core::dictmap::*; - +use rustworkx_core::coloring::greedy_node_color; +use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, EdgeRef}; +use petgraph::EdgeType; +use petgraph::graph::{EdgeIndex, NodeIndex}; /// Color a :class:`~.PyGraph` object using a greedy graph coloring algorithm. /// @@ -103,7 +104,7 @@ fn line_graph( fn greedy_edge_color(py: Python, graph: &StablePyGraph) -> DictMap { let (line_graph, edge_to_node_map) = line_graph(py, graph); - let colors = greedy_color(&line_graph); + let colors = greedy_node_color(&line_graph); let mut edge_colors: DictMap = DictMap::new(); From 07c5ba73f3bd8020aed0944eba9395ed907256f4 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 8 Jun 2023 16:34:10 +0300 Subject: [PATCH 09/26] first pass on implementing line_graph in rustworkx-core --- rustworkx-core/src/lib.rs | 1 + rustworkx-core/src/line_graph.rs | 111 +++++++++++++++++++++++++++++++ src/coloring.rs | 1 + 3 files changed, 113 insertions(+) create mode 100644 rustworkx-core/src/line_graph.rs diff --git a/rustworkx-core/src/lib.rs b/rustworkx-core/src/lib.rs index 754535bf5..c95bd90ee 100644 --- a/rustworkx-core/src/lib.rs +++ b/rustworkx-core/src/lib.rs @@ -74,6 +74,7 @@ pub type Result = core::result::Result; pub mod centrality; /// Module for coloring algorithms. pub mod coloring; +pub mod line_graph; pub mod connectivity; pub mod generators; /// Module for maximum weight matching algorithms. diff --git a/rustworkx-core/src/line_graph.rs b/rustworkx-core/src/line_graph.rs new file mode 100644 index 000000000..5d42704d6 --- /dev/null +++ b/rustworkx-core/src/line_graph.rs @@ -0,0 +1,111 @@ +// 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 std::hash::Hash; +use hashbrown::HashMap; +use petgraph::visit::{EdgeRef, IntoEdges, IntoNodeIdentifiers, NodeCount, EdgeCount, IntoEdgeReferences, GraphBase, EdgeIndexable, Data}; +use petgraph::data::{Build, Create}; +use petgraph::graph::{EdgeIndex, NodeIndex}; +use std::fmt::Debug; +// pub fn line_graph(graph: G) -> (G, HashMap) { + +pub fn line_graph( + graph: K, + mut default_node_weight: F, + mut default_edge_weight: H +) -> (G, HashMap::) + where + K: NodeCount + EdgeCount + IntoEdgeReferences + EdgeIndexable + std::fmt::Debug + IntoNodeIdentifiers + IntoEdges, + G: Build + Create + Data + Debug, + G::NodeId: Debug, + F: FnMut() -> T, + H: FnMut() -> M, + T: Clone, + K::EdgeId: Hash + Eq + Debug, + K::NodeId: Debug, + K::EdgeRef: Debug, +{ + println!("I am here"); + println!("Graph {:?}", graph); + let num_edges = graph.edge_count(); + println!("G has {} edges", num_edges); + let mut out_graph = G::with_capacity(num_edges, 0); + let mut out_edge_map = HashMap::::with_capacity(graph.edge_count()); + + for edge in graph.edge_references() { + let n0 = out_graph.add_node(default_node_weight()); + out_edge_map.insert(edge.id(), n0); + } + println!("out_edge_map {:?}: ", out_edge_map); + + for node in graph.node_identifiers() { + println!("found node {:?}", node); + // for edge0 in graph.edges(node) { + // println!("edge0 is {:?}", edge0); + // + // } + let edges: Vec = graph.edges(node).collect(); + println!("edges {:?}", edges); + for i in 0..edges.len() { + for j in i+1..edges.len() { + println!("i = {}, edge_i = {:?}, j = {}, edge_j = {:?}", i, edges[i], j, edges[j]); + let node0 = out_edge_map.get(&edges[i].id()).unwrap(); + let node1 = out_edge_map.get(&edges[j].id()).unwrap(); + println!("node0 = {:?}, node1 = {:?}", node0, node1); + out_graph.add_edge(*node0, *node1, default_edge_weight()); + } + } + } + println!("Out Graph {:?}", out_graph); + println!("Out edge map {:?}", out_edge_map); + + (out_graph, out_edge_map) +} + + +#[cfg(test)] + +mod test_line_graph { + use crate::line_graph::line_graph; + use crate::dictmap::DictMap; + use crate::petgraph::Graph; + use hashbrown::HashMap; + + use petgraph::graph::{EdgeIndex, NodeIndex}; + use petgraph::Undirected; + use crate::petgraph::visit::EdgeRef; + + #[test] + fn test_greedy_node_color_simple_graph() { + // Simple graph + let graph = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (1, 2), (0, 3)]); + let (out_graph, out_edge_map): (petgraph::graph::UnGraph<(), ()>, HashMap::) = line_graph(&graph, || (), || ()); + let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; + let actual_edge_list = out_graph.edge_references() + .map(|edge| (edge.source().index(), edge.target().index())) + .collect::>(); + + assert_eq!(expected_edge_list, actual_edge_list); + + let expected_map: HashMap = [ + (EdgeIndex::new(0), NodeIndex::new(0)), + (EdgeIndex::new(1), NodeIndex::new(1)), + (EdgeIndex::new(2), NodeIndex::new(2)), + (EdgeIndex::new(3), NodeIndex::new(3)), + ] + .into_iter() + .collect(); + assert_eq!(expected_map, out_edge_map); + + + } +} diff --git a/src/coloring.rs b/src/coloring.rs index d6e114234..3798ffd80 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -120,6 +120,7 @@ fn greedy_edge_color(py: Python, graph: &StablePyGraph) -> Dic #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { + println!("Running graph_greedy_edge_color...."); let edge_colors = greedy_edge_color(py, &graph.graph); let out_dict = PyDict::new(py); From 3c4513e1b7c47524dc5554e4b2a1db0590f3f44f Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 8 Jun 2023 16:55:07 +0300 Subject: [PATCH 10/26] cleanup --- rustworkx-core/src/lib.rs | 2 +- rustworkx-core/src/line_graph.rs | 102 +++++++++++++------------------ 2 files changed, 43 insertions(+), 61 deletions(-) diff --git a/rustworkx-core/src/lib.rs b/rustworkx-core/src/lib.rs index c95bd90ee..5bd20f556 100644 --- a/rustworkx-core/src/lib.rs +++ b/rustworkx-core/src/lib.rs @@ -74,9 +74,9 @@ pub type Result = core::result::Result; pub mod centrality; /// Module for coloring algorithms. pub mod coloring; -pub mod line_graph; pub mod connectivity; pub mod generators; +pub mod line_graph; /// Module for maximum weight matching algorithms. pub mod max_weight_matching; pub mod planar; diff --git a/rustworkx-core/src/line_graph.rs b/rustworkx-core/src/line_graph.rs index 5d42704d6..9113070b7 100644 --- a/rustworkx-core/src/line_graph.rs +++ b/rustworkx-core/src/line_graph.rs @@ -11,92 +11,74 @@ // under the License. use std::hash::Hash; + use hashbrown::HashMap; -use petgraph::visit::{EdgeRef, IntoEdges, IntoNodeIdentifiers, NodeCount, EdgeCount, IntoEdgeReferences, GraphBase, EdgeIndexable, Data}; -use petgraph::data::{Build, Create}; -use petgraph::graph::{EdgeIndex, NodeIndex}; -use std::fmt::Debug; -// pub fn line_graph(graph: G) -> (G, HashMap) { +use petgraph::data::Create; +use petgraph::visit::{Data, EdgeCount, EdgeRef, IntoEdges, IntoNodeIdentifiers}; pub fn line_graph( - graph: K, + input_graph: K, mut default_node_weight: F, - mut default_edge_weight: H -) -> (G, HashMap::) - where - K: NodeCount + EdgeCount + IntoEdgeReferences + EdgeIndexable + std::fmt::Debug + IntoNodeIdentifiers + IntoEdges, - G: Build + Create + Data + Debug, - G::NodeId: Debug, - F: FnMut() -> T, - H: FnMut() -> M, - T: Clone, - K::EdgeId: Hash + Eq + Debug, - K::NodeId: Debug, - K::EdgeRef: Debug, + mut default_edge_weight: H, +) -> (G, HashMap) +where + K: EdgeCount + IntoNodeIdentifiers + IntoEdges, + G: Create + Data, + F: FnMut() -> T, + H: FnMut() -> M, + K::EdgeId: Hash + Eq, { - println!("I am here"); - println!("Graph {:?}", graph); - let num_edges = graph.edge_count(); - println!("G has {} edges", num_edges); - let mut out_graph = G::with_capacity(num_edges, 0); - let mut out_edge_map = HashMap::::with_capacity(graph.edge_count()); + let num_edges = input_graph.edge_count(); + let mut output_graph = G::with_capacity(num_edges, 0); + let mut output_edge_map = + HashMap::::with_capacity(input_graph.edge_count()); - for edge in graph.edge_references() { - let n0 = out_graph.add_node(default_node_weight()); - out_edge_map.insert(edge.id(), n0); + for edge in input_graph.edge_references() { + let new_node = output_graph.add_node(default_node_weight()); + output_edge_map.insert(edge.id(), new_node); } - println!("out_edge_map {:?}: ", out_edge_map); - for node in graph.node_identifiers() { - println!("found node {:?}", node); - // for edge0 in graph.edges(node) { - // println!("edge0 is {:?}", edge0); - // - // } - let edges: Vec = graph.edges(node).collect(); - println!("edges {:?}", edges); + for node in input_graph.node_identifiers() { + let edges: Vec = input_graph.edges(node).collect(); for i in 0..edges.len() { - for j in i+1..edges.len() { - println!("i = {}, edge_i = {:?}, j = {}, edge_j = {:?}", i, edges[i], j, edges[j]); - let node0 = out_edge_map.get(&edges[i].id()).unwrap(); - let node1 = out_edge_map.get(&edges[j].id()).unwrap(); - println!("node0 = {:?}, node1 = {:?}", node0, node1); - out_graph.add_edge(*node0, *node1, default_edge_weight()); + for j in i + 1..edges.len() { + let node0 = output_edge_map.get(&edges[i].id()).unwrap(); + let node1 = output_edge_map.get(&edges[j].id()).unwrap(); + output_graph.add_edge(*node0, *node1, default_edge_weight()); } } } - println!("Out Graph {:?}", out_graph); - println!("Out edge map {:?}", out_edge_map); - - (out_graph, out_edge_map) + (output_graph, output_edge_map) } - #[cfg(test)] mod test_line_graph { use crate::line_graph::line_graph; - use crate::dictmap::DictMap; + use crate::petgraph::visit::EdgeRef; use crate::petgraph::Graph; use hashbrown::HashMap; - use petgraph::graph::{EdgeIndex, NodeIndex}; use petgraph::Undirected; - use crate::petgraph::visit::EdgeRef; #[test] fn test_greedy_node_color_simple_graph() { // Simple graph - let graph = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (1, 2), (0, 3)]); - let (out_graph, out_edge_map): (petgraph::graph::UnGraph<(), ()>, HashMap::) = line_graph(&graph, || (), || ()); - let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; - let actual_edge_list = out_graph.edge_references() - .map(|edge| (edge.source().index(), edge.target().index())) - .collect::>(); + let input_graph = + Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (1, 2), (0, 3)]); + + let (output_graph, output_edge_map): ( + petgraph::graph::UnGraph<(), ()>, + HashMap, + ) = line_graph(&input_graph, || (), || ()); - assert_eq!(expected_edge_list, actual_edge_list); + let output_edge_list = output_graph + .edge_references() + .map(|edge| (edge.source().index(), edge.target().index())) + .collect::>(); - let expected_map: HashMap = [ + let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; + let expected_edge_map: HashMap = [ (EdgeIndex::new(0), NodeIndex::new(0)), (EdgeIndex::new(1), NodeIndex::new(1)), (EdgeIndex::new(2), NodeIndex::new(2)), @@ -104,8 +86,8 @@ mod test_line_graph { ] .into_iter() .collect(); - assert_eq!(expected_map, out_edge_map); - + assert_eq!(output_edge_list, expected_edge_list); + assert_eq!(output_edge_map, expected_edge_map); } } From d475d08c28ddc9cd8a72eaea6ded2fcc2db88693 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 8 Jun 2023 17:16:50 +0300 Subject: [PATCH 11/26] docs --- rustworkx-core/src/line_graph.rs | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/rustworkx-core/src/line_graph.rs b/rustworkx-core/src/line_graph.rs index 9113070b7..7eaeedfda 100644 --- a/rustworkx-core/src/line_graph.rs +++ b/rustworkx-core/src/line_graph.rs @@ -16,6 +16,58 @@ use hashbrown::HashMap; use petgraph::data::Create; use petgraph::visit::{Data, EdgeCount, EdgeRef, IntoEdges, IntoNodeIdentifiers}; +/// Constructs the line graph of an undirected graph. +/// +/// The line graph `L(G)` of a graph `G` represents the adjacencies between edges of G. +/// `L(G)` contains a vertex for every edge in `G`, and `L(G)` contains an edge between two +/// vertices if the corresponding edges in `G` have a vertex in common. +/// +/// Arguments: +/// +/// * `input_graph` - The input graph `G`. +/// * `default_node_weight` - A callable that will return the weight to use +/// for newly created nodes. +/// * `default_edge_weight` - A callable that will return the weight object +/// to use for newly created edges. +/// +/// Returns the constructed line graph `L(G)`, and the map from the edges of `L(G)` to +/// the vertices of `G`. +/// +/// # Example +/// ```rust +/// use crate::line_graph::line_graph; +/// use crate::petgraph::visit::EdgeRef; +/// use crate::petgraph::Graph; +/// use hashbrown::HashMap; +/// use petgraph::graph::{EdgeIndex, NodeIndex}; +/// use petgraph::Undirected; +/// +/// let input_graph = +/// Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (1, 2), (0, 3)]); +/// +/// let (output_graph, output_edge_map): ( +/// petgraph::graph::UnGraph<(), ()>, +/// HashMap, +/// ) = line_graph(&input_graph, || (), || ()); +/// +/// let output_edge_list = output_graph +/// .edge_references() +/// .map(|edge| (edge.source().index(), edge.target().index())) +/// .collect::>();/// +/// +/// let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; +/// let expected_edge_map: HashMap = [ +/// (EdgeIndex::new(0), NodeIndex::new(0)), +/// (EdgeIndex::new(1), NodeIndex::new(1)), +/// (EdgeIndex::new(2), NodeIndex::new(2)), +/// (EdgeIndex::new(3), NodeIndex::new(3)), +/// ] +/// .into_iter() +/// .collect(); +/// +/// assert_eq!(output_edge_list, expected_edge_list); +/// assert_eq!(output_edge_map, expected_edge_map); +/// ``` pub fn line_graph( input_graph: K, mut default_node_weight: F, From e78d06931057826718047dce113491922f8183d0 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 8 Jun 2023 17:26:05 +0300 Subject: [PATCH 12/26] docs fix --- rustworkx-core/src/line_graph.rs | 6 ++-- rustworkx-core/tests/coloring.rs | 51 -------------------------------- 2 files changed, 3 insertions(+), 54 deletions(-) delete mode 100644 rustworkx-core/tests/coloring.rs diff --git a/rustworkx-core/src/line_graph.rs b/rustworkx-core/src/line_graph.rs index 7eaeedfda..4fefdcdc7 100644 --- a/rustworkx-core/src/line_graph.rs +++ b/rustworkx-core/src/line_graph.rs @@ -35,9 +35,9 @@ use petgraph::visit::{Data, EdgeCount, EdgeRef, IntoEdges, IntoNodeIdentifiers}; /// /// # Example /// ```rust -/// use crate::line_graph::line_graph; -/// use crate::petgraph::visit::EdgeRef; -/// use crate::petgraph::Graph; +/// use rustworkx_core::line_graph::line_graph; +/// use rustworkx_core::petgraph::visit::EdgeRef; +/// use rustworkx_core::petgraph::Graph; /// use hashbrown::HashMap; /// use petgraph::graph::{EdgeIndex, NodeIndex}; /// use petgraph::Undirected; diff --git a/rustworkx-core/tests/coloring.rs b/rustworkx-core/tests/coloring.rs deleted file mode 100644 index 41a1383cf..000000000 --- a/rustworkx-core/tests/coloring.rs +++ /dev/null @@ -1,51 +0,0 @@ -// 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. - -//! Test module for coloring algorithms. - -use petgraph::graph::Graph; -use petgraph::graph::NodeIndex; -use petgraph::Undirected; -use rustworkx_core::dictmap::*; - -use rustworkx_core::coloring::greedy_color; - -#[test] -fn test_greedy_color_empty_graph() { - let graph = Graph::<(), (), Undirected>::new_undirected(); - let colors = greedy_color(&graph); - let expected_colors = DictMap::new(); - assert_eq!(colors, expected_colors); -} - -#[test] -fn test_greedy_color_simple_graph() { - let graph = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2)]); - let colors = greedy_color(&graph); - let mut expected_colors = DictMap::new(); - expected_colors.insert(NodeIndex::new(0), 0); - expected_colors.insert(NodeIndex::new(1), 1); - expected_colors.insert(NodeIndex::new(2), 1); - assert_eq!(colors, expected_colors); -} - -#[test] -fn test_greedy_color_simple_graph_large_degree() { - let graph = - Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (0, 2), (0, 2), (0, 2), (0, 2)]); - let colors = greedy_color(&graph); - let mut expected_colors = DictMap::new(); - expected_colors.insert(NodeIndex::new(0), 0); - expected_colors.insert(NodeIndex::new(1), 1); - expected_colors.insert(NodeIndex::new(2), 1); - assert_eq!(colors, expected_colors); -} From 79b7b4d56783161d381bd2c0046144ea74baaf5c Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 9 Jun 2023 09:49:50 +0300 Subject: [PATCH 13/26] exposing line_graph to rustworkx python interface --- docs/source/api.rst | 1 + src/coloring.rs | 46 ++++++++++++++++---- src/lib.rs | 1 + tests/rustworkx_tests/graph/test_coloring.py | 2 +- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 339a0d616..0bf578075 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -199,6 +199,7 @@ Other Algorithm Functions rustworkx.core_number rustworkx.graph_greedy_color rustworkx.graph_greedy_edge_color + rustworkx.graph_line_graph rustworkx.metric_closure rustworkx.is_planar diff --git a/src/coloring.rs b/src/coloring.rs index 3798ffd80..db8ab332f 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -13,16 +13,19 @@ use crate::graph; use crate::StablePyGraph; use hashbrown::HashMap; +use petgraph::Undirected; use rustworkx_core::dictmap::*; use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::Python; -use rustworkx_core::coloring::greedy_node_color; -use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, EdgeRef}; -use petgraph::EdgeType; use petgraph::graph::{EdgeIndex, NodeIndex}; +use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences}; +use petgraph::EdgeType; +use pyo3::pyclass::boolean_struct::True; +use rustworkx_core::coloring::greedy_node_color; +use rustworkx_core::line_graph::line_graph; /// Color a :class:`~.PyGraph` object using a greedy graph coloring algorithm. /// @@ -67,12 +70,39 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult (graph::PyGraph, DictMap) { + let default_fn = || py.None(); + + let (output_graph, output_edge_to_node_map): ( + StablePyGraph, + HashMap, + ) = line_graph(&graph.graph, default_fn, default_fn); + + let output_graph_py = graph::PyGraph { + graph: output_graph, + node_removed: false, + multigraph: false, + attrs: py.None(), + }; + + let mut output_edge_to_node_map_py: DictMap = DictMap::new(); + + for edge in graph.graph.edge_references() { + let edge_id = edge.id(); + let node_id = output_edge_to_node_map.get(&edge_id).unwrap(); + output_edge_to_node_map_py.insert(edge_id.index(), node_id.index()); + } + (output_graph_py, output_edge_to_node_map_py) +} - - - -fn line_graph( +fn line_graph_tmp( py: Python, graph: &StablePyGraph, ) -> (StablePyGraph, HashMap) { @@ -103,7 +133,7 @@ fn line_graph( } fn greedy_edge_color(py: Python, graph: &StablePyGraph) -> DictMap { - let (line_graph, edge_to_node_map) = line_graph(py, graph); + let (line_graph, edge_to_node_map) = line_graph_tmp(py, graph); let colors = greedy_node_color(&line_graph); let mut edge_colors: DictMap = DictMap::new(); diff --git a/src/lib.rs b/src/lib.rs index 6d6e99973..91634407b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -434,6 +434,7 @@ fn rustworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(digraph_astar_shortest_path))?; m.add_wrapped(wrap_pyfunction!(graph_greedy_color))?; m.add_wrapped(wrap_pyfunction!(graph_greedy_edge_color))?; + m.add_wrapped(wrap_pyfunction!(graph_line_graph))?; m.add_wrapped(wrap_pyfunction!(graph_tensor_product))?; m.add_wrapped(wrap_pyfunction!(digraph_tensor_product))?; m.add_wrapped(wrap_pyfunction!(directed_gnp_random_graph))?; diff --git a/tests/rustworkx_tests/graph/test_coloring.py b/tests/rustworkx_tests/graph/test_coloring.py index d3cc7261c..b68082834 100644 --- a/tests/rustworkx_tests/graph/test_coloring.py +++ b/tests/rustworkx_tests/graph/test_coloring.py @@ -46,7 +46,7 @@ def test_simple_graph_large_degree(self): self.assertEqual({0: 0, 1: 1, 2: 1}, res) -class TestGraphEdgehColoring(unittest.TestCase): +class TestGraphEdgeColoring(unittest.TestCase): def test_simple_graph(self): graph = rustworkx.PyGraph() node_a = graph.add_node(1) From 7dec989aeabec979b1a9c39f68fa1aef1108ef57 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 9 Jun 2023 09:59:03 +0300 Subject: [PATCH 14/26] moving graph_line_graph to a separate package --- src/coloring.rs | 31 ------------------------- src/lib.rs | 2 ++ src/line_graph.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 src/line_graph.rs diff --git a/src/coloring.rs b/src/coloring.rs index db8ab332f..d98a352a6 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -70,37 +70,6 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult (graph::PyGraph, DictMap) { - let default_fn = || py.None(); - - let (output_graph, output_edge_to_node_map): ( - StablePyGraph, - HashMap, - ) = line_graph(&graph.graph, default_fn, default_fn); - - let output_graph_py = graph::PyGraph { - graph: output_graph, - node_removed: false, - multigraph: false, - attrs: py.None(), - }; - - let mut output_edge_to_node_map_py: DictMap = DictMap::new(); - - for edge in graph.graph.edge_references() { - let edge_id = edge.id(); - let node_id = output_edge_to_node_map.get(&edge_id).unwrap(); - output_edge_to_node_map_py.insert(edge_id.index(), node_id.index()); - } - - (output_graph_py, output_edge_to_node_map_py) -} fn line_graph_tmp( py: Python, diff --git a/src/lib.rs b/src/lib.rs index 91634407b..8e890bfce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ mod iterators; mod json; mod layout; mod link_analysis; +mod line_graph; mod matching; mod planar; mod random_graph; @@ -48,6 +49,7 @@ use graphml::*; use isomorphism::*; use json::*; use layout::*; +use line_graph::*; use link_analysis::*; use matching::*; diff --git a/src/line_graph.rs b/src/line_graph.rs new file mode 100644 index 000000000..0eb068563 --- /dev/null +++ b/src/line_graph.rs @@ -0,0 +1,59 @@ +// 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 crate::graph; +use crate::StablePyGraph; +use hashbrown::HashMap; +use petgraph::Undirected; +use rustworkx_core::dictmap::*; + +use pyo3::prelude::*; +use pyo3::types::PyDict; +use pyo3::Python; + +use petgraph::graph::{EdgeIndex, NodeIndex}; +use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences}; +use petgraph::EdgeType; +use pyo3::pyclass::boolean_struct::True; +use rustworkx_core::coloring::greedy_node_color; +use rustworkx_core::line_graph::line_graph; + +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn graph_line_graph( + py: Python, + graph: &graph::PyGraph, +) -> (graph::PyGraph, DictMap) { + let default_fn = || py.None(); + + let (output_graph, output_edge_to_node_map): ( + StablePyGraph, + HashMap, + ) = line_graph(&graph.graph, default_fn, default_fn); + + let output_graph_py = graph::PyGraph { + graph: output_graph, + node_removed: false, + multigraph: false, + attrs: py.None(), + }; + + let mut output_edge_to_node_map_py: DictMap = DictMap::new(); + + for edge in graph.graph.edge_references() { + let edge_id = edge.id(); + let node_id = output_edge_to_node_map.get(&edge_id).unwrap(); + output_edge_to_node_map_py.insert(edge_id.index(), node_id.index()); + } + + (output_graph_py, output_edge_to_node_map_py) +} From 99f0224dc741978866661831b0eda73a1adef35d Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 9 Jun 2023 10:57:32 +0300 Subject: [PATCH 15/26] minor cleanup --- src/coloring.rs | 2 -- src/lib.rs | 2 +- src/line_graph.rs | 16 ++++++---------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index d98a352a6..9fa5157e2 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -23,7 +23,6 @@ use pyo3::Python; use petgraph::graph::{EdgeIndex, NodeIndex}; use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences}; use petgraph::EdgeType; -use pyo3::pyclass::boolean_struct::True; use rustworkx_core::coloring::greedy_node_color; use rustworkx_core::line_graph::line_graph; @@ -70,7 +69,6 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult( py: Python, graph: &StablePyGraph, diff --git a/src/lib.rs b/src/lib.rs index 8e890bfce..adce8159b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,8 @@ mod isomorphism; mod iterators; mod json; mod layout; -mod link_analysis; mod line_graph; +mod link_analysis; mod matching; mod planar; mod random_graph; diff --git a/src/line_graph.rs b/src/line_graph.rs index 0eb068563..0b0694d84 100644 --- a/src/line_graph.rs +++ b/src/line_graph.rs @@ -10,23 +10,19 @@ // License for the specific language governing permissions and limitations // under the License. -use crate::graph; -use crate::StablePyGraph; +use crate::{graph, StablePyGraph}; + use hashbrown::HashMap; + +use petgraph::graph::{EdgeIndex, NodeIndex}; +use petgraph::visit::{EdgeRef, IntoEdgeReferences}; use petgraph::Undirected; use rustworkx_core::dictmap::*; +use rustworkx_core::line_graph::line_graph; use pyo3::prelude::*; -use pyo3::types::PyDict; use pyo3::Python; -use petgraph::graph::{EdgeIndex, NodeIndex}; -use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences}; -use petgraph::EdgeType; -use pyo3::pyclass::boolean_struct::True; -use rustworkx_core::coloring::greedy_node_color; -use rustworkx_core::line_graph::line_graph; - #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn graph_line_graph( From 97e32cae8ac0998495edb0e44185501888525c78 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 9 Jun 2023 11:26:59 +0300 Subject: [PATCH 16/26] more cleanup --- src/coloring.rs | 73 ++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 55 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index 9fa5157e2..84ec18443 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -10,22 +10,20 @@ // License for the specific language governing permissions and limitations // under the License. -use crate::graph; -use crate::StablePyGraph; +use crate::{graph, StablePyGraph}; + use hashbrown::HashMap; + +use petgraph::graph::{EdgeIndex, NodeIndex}; +use petgraph::visit::{EdgeRef, IntoEdgeReferences}; use petgraph::Undirected; -use rustworkx_core::dictmap::*; +use rustworkx_core::coloring::greedy_node_color; +use rustworkx_core::line_graph::line_graph; use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::Python; -use petgraph::graph::{EdgeIndex, NodeIndex}; -use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences}; -use petgraph::EdgeType; -use rustworkx_core::coloring::greedy_node_color; -use rustworkx_core::line_graph::line_graph; - /// Color a :class:`~.PyGraph` object using a greedy graph coloring algorithm. /// /// This function uses a `largest-first` strategy as described in [1]_ and colors @@ -69,60 +67,25 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult( - py: Python, - graph: &StablePyGraph, -) -> (StablePyGraph, HashMap) { - let mut out_graph = StablePyGraph::::with_capacity(graph.edge_count(), 0); - let mut out_edge_map = HashMap::::with_capacity(graph.edge_count()); - - for edge in graph.edge_references() { - let e0 = edge.id(); - let n0 = out_graph.add_node(py.None()); - out_edge_map.insert(e0, n0); - } - - // There must be a better way to iterate over all pairs of edges, but I can't get - // combinations() to work. - for node in graph.node_references() { - for edge0 in graph.edges(node.0) { - for edge1 in graph.edges(node.0) { - if edge0.id().index() < edge1.id().index() { - let node0 = out_edge_map.get(&edge0.id()).unwrap(); - let node1 = out_edge_map.get(&edge1.id()).unwrap(); - out_graph.add_edge(*node0, *node1, py.None()); - } - } - } - } +#[pyfunction] +#[pyo3(text_signature = "(graph, /)")] +pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { + println!("Running NEW graph_greedy_edge_color...."); - (out_graph, out_edge_map) -} + let default_fn = || py.None(); + let (new_graph, edge_to_node_map): (StablePyGraph, HashMap) = + line_graph(&graph.graph, default_fn, default_fn); -fn greedy_edge_color(py: Python, graph: &StablePyGraph) -> DictMap { - let (line_graph, edge_to_node_map) = line_graph_tmp(py, graph); - let colors = greedy_node_color(&line_graph); + let colors = greedy_node_color(&new_graph); - let mut edge_colors: DictMap = DictMap::new(); + let out_dict = PyDict::new(py); - for edge in graph.edge_references() { + for edge in graph.graph.edge_references() { let e0 = edge.id(); let n0 = edge_to_node_map.get(&e0).unwrap(); let c0 = colors.get(n0).unwrap(); - edge_colors.insert(e0.index(), *c0); + out_dict.set_item(e0.index(), c0)?; } - edge_colors -} -#[pyfunction] -#[pyo3(text_signature = "(graph, /)")] -pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult { - println!("Running graph_greedy_edge_color...."); - let edge_colors = greedy_edge_color(py, &graph.graph); - - let out_dict = PyDict::new(py); - for (index, color) in edge_colors { - out_dict.set_item(index, color)?; - } Ok(out_dict.into()) } From 370407c28d4fce1edffdd0957553c75a387a9a71 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 9 Jun 2023 11:30:51 +0300 Subject: [PATCH 17/26] more cleanup --- src/coloring.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index 84ec18443..02595b7e4 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -79,12 +79,11 @@ pub fn graph_greedy_edge_color(py: Python, graph: &graph::PyGraph) -> PyResult

Date: Fri, 9 Jun 2023 20:53:10 +0300 Subject: [PATCH 18/26] tests for graph_greedy_edge_color --- src/coloring.rs | 2 - tests/rustworkx_tests/graph/test_coloring.py | 63 +++++++++++++++----- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index 02595b7e4..a88e37f6f 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -70,8 +70,6 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult PyResult { - println!("Running NEW graph_greedy_edge_color...."); - let default_fn = || py.None(); let (new_graph, edge_to_node_map): (StablePyGraph, HashMap) = line_graph(&graph.graph, default_fn, default_fn); diff --git a/tests/rustworkx_tests/graph/test_coloring.py b/tests/rustworkx_tests/graph/test_coloring.py index b68082834..ebbaa260f 100644 --- a/tests/rustworkx_tests/graph/test_coloring.py +++ b/tests/rustworkx_tests/graph/test_coloring.py @@ -47,7 +47,7 @@ def test_simple_graph_large_degree(self): class TestGraphEdgeColoring(unittest.TestCase): - def test_simple_graph(self): + def test_graph(self): graph = rustworkx.PyGraph() node_a = graph.add_node(1) node_b = graph.add_node(2) @@ -55,21 +55,52 @@ def test_simple_graph(self): node_d = graph.add_node(4) node_e = graph.add_node(5) - edge_ab = graph.add_edge(node_a, node_b, 1) - edge_ac = graph.add_edge(node_a, node_c, 1) - edge_ad = graph.add_edge(node_a, node_d, 1) - edge_de = graph.add_edge(node_d, node_e, 1) - - print(edge_ab) - print(edge_ac) - print(edge_ad) - print(edge_de) - - print("============") - res = rustworkx.graph_greedy_edge_color(graph) - print("=====RESULT=======") - print(res) - print(graph.edge_indices()[2]) + graph.add_edge(node_a, node_b, 1) + graph.add_edge(node_a, node_c, 1) + graph.add_edge(node_a, node_d, 1) + graph.add_edge(node_d, node_e, 1) + + edge_colors = rustworkx.graph_greedy_edge_color(graph) + self.assertEqual({0: 1, 1: 2, 2: 0, 3: 1}, edge_colors) + + def test_graph_with_holes(self): + """Graph with missing node and edge indices.""" + graph = rustworkx.PyGraph() + + node_a = graph.add_node("A") + node_b = graph.add_node("B") + node_c = graph.add_node("C") + node_d = graph.add_node("D") + node_e = graph.add_node("E") + + graph.add_edge(node_a, node_b, 1) + graph.add_edge(node_b, node_c, 1) + graph.add_edge(node_c, node_d, 1) + graph.add_edge(node_d, node_e, 1) + + graph.remove_node(node_c) + + edge_colors = rustworkx.graph_greedy_edge_color(graph) + self.assertEqual({0: 0, 3: 0}, edge_colors) + + def test_graph_without_edges(self): + graph = rustworkx.PyGraph() + graph.add_node(1) + graph.add_node(2) + edge_colors = rustworkx.graph_greedy_edge_color(graph) + self.assertEqual({}, edge_colors) + + def test_graph_multiple_edges(self): + """Graph with multiple edges between two nodes.""" + graph = rustworkx.PyGraph() + node_a = graph.add_node(1) + node_b = graph.add_node(2) + graph.add_edge(node_a, node_b, 1) + graph.add_edge(node_a, node_b, 1) + graph.add_edge(node_a, node_b, 1) + graph.add_edge(node_a, node_b, 1) + edge_colors = rustworkx.graph_greedy_edge_color(graph) + self.assertEqual({0: 0, 1: 1, 2: 2, 3: 3}, edge_colors) if __name__ == "__main__": From ecdbe16d384395325eacaa9df0e9b9803b8673c9 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 9 Jun 2023 21:20:29 +0300 Subject: [PATCH 19/26] additional test + docs --- src/coloring.rs | 17 +++++++++++++++++ tests/rustworkx_tests/graph/test_coloring.py | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/src/coloring.rs b/src/coloring.rs index a88e37f6f..fcb02b0ad 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -67,6 +67,23 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult PyResult { diff --git a/tests/rustworkx_tests/graph/test_coloring.py b/tests/rustworkx_tests/graph/test_coloring.py index ebbaa260f..76cdc4464 100644 --- a/tests/rustworkx_tests/graph/test_coloring.py +++ b/tests/rustworkx_tests/graph/test_coloring.py @@ -102,6 +102,11 @@ def test_graph_multiple_edges(self): edge_colors = rustworkx.graph_greedy_edge_color(graph) self.assertEqual({0: 0, 1: 1, 2: 2, 3: 3}, edge_colors) + def test_cycle_graph(self): + graph = rustworkx.generators.cycle_graph(7) + edge_colors = rustworkx.graph_greedy_edge_color(graph) + self.assertEqual({0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1, 6: 2}, edge_colors) + if __name__ == "__main__": unittest.main() From 715f270ae59c2921d4172644428b61c92cde2db3 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 10 Jun 2023 00:02:59 +0300 Subject: [PATCH 20/26] line graph tests --- rustworkx-core/src/line_graph.rs | 2 +- tests/rustworkx_tests/graph/test_coloring.py | 44 +++++++------- .../rustworkx_tests/graph/test_line_graph.py | 58 +++++++++++++++++++ 3 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 tests/rustworkx_tests/graph/test_line_graph.py diff --git a/rustworkx-core/src/line_graph.rs b/rustworkx-core/src/line_graph.rs index 4fefdcdc7..61ddde4d0 100644 --- a/rustworkx-core/src/line_graph.rs +++ b/rustworkx-core/src/line_graph.rs @@ -53,7 +53,7 @@ use petgraph::visit::{Data, EdgeCount, EdgeRef, IntoEdges, IntoNodeIdentifiers}; /// let output_edge_list = output_graph /// .edge_references() /// .map(|edge| (edge.source().index(), edge.target().index())) -/// .collect::>();/// +/// .collect::>(); /// /// let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; /// let expected_edge_map: HashMap = [ diff --git a/tests/rustworkx_tests/graph/test_coloring.py b/tests/rustworkx_tests/graph/test_coloring.py index 76cdc4464..675729662 100644 --- a/tests/rustworkx_tests/graph/test_coloring.py +++ b/tests/rustworkx_tests/graph/test_coloring.py @@ -23,20 +23,20 @@ def test_empty_graph(self): def test_simple_graph(self): graph = rustworkx.PyGraph() - node_a = graph.add_node(1) - node_b = graph.add_node(2) + node_a = graph.add_node("a") + node_b = graph.add_node("b") graph.add_edge(node_a, node_b, 1) - node_c = graph.add_node(3) + node_c = graph.add_node("c") graph.add_edge(node_a, node_c, 1) res = rustworkx.graph_greedy_color(graph) self.assertEqual({0: 0, 1: 1, 2: 1}, res) def test_simple_graph_large_degree(self): graph = rustworkx.PyGraph() - node_a = graph.add_node(1) - node_b = graph.add_node(2) + node_a = graph.add_node("a") + node_b = graph.add_node("b") graph.add_edge(node_a, node_b, 1) - node_c = graph.add_node(3) + node_c = graph.add_node("c") graph.add_edge(node_a, node_c, 1) graph.add_edge(node_a, node_c, 1) graph.add_edge(node_a, node_c, 1) @@ -49,11 +49,11 @@ def test_simple_graph_large_degree(self): class TestGraphEdgeColoring(unittest.TestCase): def test_graph(self): graph = rustworkx.PyGraph() - node_a = graph.add_node(1) - node_b = graph.add_node(2) - node_c = graph.add_node(3) - node_d = graph.add_node(4) - node_e = graph.add_node(5) + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + node_d = graph.add_node("d") + node_e = graph.add_node("e") graph.add_edge(node_a, node_b, 1) graph.add_edge(node_a, node_c, 1) @@ -67,11 +67,11 @@ def test_graph_with_holes(self): """Graph with missing node and edge indices.""" graph = rustworkx.PyGraph() - node_a = graph.add_node("A") - node_b = graph.add_node("B") - node_c = graph.add_node("C") - node_d = graph.add_node("D") - node_e = graph.add_node("E") + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + node_d = graph.add_node("d") + node_e = graph.add_node("e") graph.add_edge(node_a, node_b, 1) graph.add_edge(node_b, node_c, 1) @@ -85,16 +85,16 @@ def test_graph_with_holes(self): def test_graph_without_edges(self): graph = rustworkx.PyGraph() - graph.add_node(1) - graph.add_node(2) + graph.add_node("a") + graph.add_node("b") edge_colors = rustworkx.graph_greedy_edge_color(graph) self.assertEqual({}, edge_colors) def test_graph_multiple_edges(self): """Graph with multiple edges between two nodes.""" graph = rustworkx.PyGraph() - node_a = graph.add_node(1) - node_b = graph.add_node(2) + node_a = graph.add_node("a") + node_b = graph.add_node("b") graph.add_edge(node_a, node_b, 1) graph.add_edge(node_a, node_b, 1) graph.add_edge(node_a, node_b, 1) @@ -106,7 +106,3 @@ def test_cycle_graph(self): graph = rustworkx.generators.cycle_graph(7) edge_colors = rustworkx.graph_greedy_edge_color(graph) self.assertEqual({0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1, 6: 2}, edge_colors) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/rustworkx_tests/graph/test_line_graph.py b/tests/rustworkx_tests/graph/test_line_graph.py new file mode 100644 index 000000000..739b36aa1 --- /dev/null +++ b/tests/rustworkx_tests/graph/test_line_graph.py @@ -0,0 +1,58 @@ +# 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 TestLineGraph(unittest.TestCase): + def test_graph(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + node_d = graph.add_node("d") + edge_ab = graph.add_edge(node_a, node_b, 1) + edge_ac = graph.add_edge(node_a, node_c, 1) + edge_bc = graph.add_edge(node_b, node_c, 1) + edge_ad = graph.add_edge(node_a, node_d, 1) + + out_graph, out_edge_map = rustworkx.graph_line_graph(graph) + expected_nodes = [0, 1, 2, 3] + expected_edge_map = {edge_ab: 0, edge_ac: 1, edge_bc: 2, edge_ad: 3} + expected_edges = [(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)] + self.assertEqual(out_graph.node_indices(), expected_nodes) + self.assertEqual(out_graph.edge_list(), expected_edges) + self.assertEqual(out_edge_map, expected_edge_map) + + def test_graph_with_holes(self): + """Graph with missing node and edge indices.""" + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + node_d = graph.add_node("d") + node_e = graph.add_node("e") + edge_ab = graph.add_edge(node_a, node_b, 1) + edge_bc = graph.add_edge(node_b, node_c, 1) + edge_cd = graph.add_edge(node_c, node_d, 1) + edge_de = graph.add_edge(node_d, node_e, 1) + graph.remove_node(node_c) + out_graph, out_edge_map = rustworkx.graph_line_graph(graph) + + expected_nodes = [0, 1] + expected_edge_map = {edge_ab: 0, edge_de: 1} + expected_edges = [] + self.assertEqual(out_graph.node_indices(), expected_nodes) + self.assertEqual(out_graph.edge_list(), expected_edges) + self.assertEqual(out_edge_map, expected_edge_map) From 14565738b88eaafceffbc07f856499cb85923e0e Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 10 Jun 2023 00:19:10 +0300 Subject: [PATCH 21/26] docs --- src/coloring.rs | 8 ++++---- src/line_graph.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index fcb02b0ad..f902966b4 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -78,11 +78,11 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult Date: Sat, 10 Jun 2023 00:26:43 +0300 Subject: [PATCH 22/26] release notes --- ...ph-greedy-edge-color-7ef35785e5768419.yaml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 releasenotes/notes/add-graph-greedy-edge-color-7ef35785e5768419.yaml diff --git a/releasenotes/notes/add-graph-greedy-edge-color-7ef35785e5768419.yaml b/releasenotes/notes/add-graph-greedy-edge-color-7ef35785e5768419.yaml new file mode 100644 index 000000000..2c7832c23 --- /dev/null +++ b/releasenotes/notes/add-graph-greedy-edge-color-7ef35785e5768419.yaml @@ -0,0 +1,42 @@ +--- +features: + - | + Added a new function, :func:`~.graph_line_graph` to construct a line + graph of a :class:`~.PyGraph` object. + + The line graph `L(G)` of a graph `G` represents the adjacencies between edges of G. + `L(G)` contains a vertex for every edge in `G`, and `L(G)` contains an edge between two + vertices if the corresponding edges in `G` have a vertex in common. + + .. jupyter-execute:: + + import rustworkx as rx + + graph = rx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + node_d = graph.add_node("d") + edge_ab = graph.add_edge(node_a, node_b, 1) + edge_ac = graph.add_edge(node_a, node_c, 1) + edge_bc = graph.add_edge(node_b, node_c, 1) + edge_ad = graph.add_edge(node_a, node_d, 1) + + out_graph, out_edge_map = rx.graph_line_graph(graph) + assert out_graph.node_indices() == [0, 1, 2, 3] + assert out_graph.edge_list() == [(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)] + assert out_edge_map == {edge_ab: 0, edge_ac: 1, edge_bc: 2, edge_ad: 3} + + - | + Added a new function, :func:`~.graph_greedy_edge_color` to color edges + of a :class:`~.PyGraph` object using a greedy approach. + + This function works by greedily coloring the line graph of the given graph. + + .. jupyter-execute:: + + import rustworkx as rx + + graph = rx.generators.cycle_graph(7) + edge_colors = rx.graph_greedy_edge_color(graph) + assert edge_colors == {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1, 6: 2} From bbacc33d21b9f3e145cc5cc8957a6018f2938f8a Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sat, 10 Jun 2023 20:21:44 +0300 Subject: [PATCH 23/26] minor --- tests/rustworkx_tests/graph/test_line_graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rustworkx_tests/graph/test_line_graph.py b/tests/rustworkx_tests/graph/test_line_graph.py index 739b36aa1..695d1e7b4 100644 --- a/tests/rustworkx_tests/graph/test_line_graph.py +++ b/tests/rustworkx_tests/graph/test_line_graph.py @@ -44,8 +44,8 @@ def test_graph_with_holes(self): node_d = graph.add_node("d") node_e = graph.add_node("e") edge_ab = graph.add_edge(node_a, node_b, 1) - edge_bc = graph.add_edge(node_b, node_c, 1) - edge_cd = graph.add_edge(node_c, node_d, 1) + graph.add_edge(node_b, node_c, 1) + graph.add_edge(node_c, node_d, 1) edge_de = graph.add_edge(node_d, node_e, 1) graph.remove_node(node_c) out_graph, out_edge_map = rustworkx.graph_line_graph(graph) From c324acba2633b90ae16f12c839fe964e390f9730 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 25 Jun 2023 16:23:40 +0300 Subject: [PATCH 24/26] implementing greedy_edge_color in rustworkx-core --- rustworkx-core/src/coloring.rs | 111 ++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/rustworkx-core/src/coloring.rs b/rustworkx-core/src/coloring.rs index 0bea3451b..99eb27a9f 100644 --- a/rustworkx-core/src/coloring.rs +++ b/rustworkx-core/src/coloring.rs @@ -14,8 +14,10 @@ use std::cmp::Reverse; use std::hash::Hash; use crate::dictmap::*; +use crate::line_graph::line_graph; use hashbrown::{HashMap, HashSet}; -use petgraph::visit::{EdgeRef, IntoEdges, IntoNodeIdentifiers, NodeCount}; +use petgraph::graph::NodeIndex; +use petgraph::visit::{EdgeCount, EdgeRef, IntoEdges, IntoNodeIdentifiers, NodeCount}; use rayon::prelude::*; /// Color a graph using a greedy graph coloring algorithm. @@ -52,13 +54,12 @@ use rayon::prelude::*; /// assert_eq!(colors, expected_colors); /// ``` /// -/// pub fn greedy_node_color(graph: G) -> DictMap where G: NodeCount + IntoNodeIdentifiers + IntoEdges, G::NodeId: Hash + Eq + Send + Sync, { - let mut colors: DictMap = DictMap::new(); + let mut colors: DictMap = DictMap::with_capacity(graph.node_count()); let mut node_vec: Vec = graph.node_identifiers().collect(); let mut sort_map: HashMap = HashMap::with_capacity(graph.node_count()); @@ -90,6 +91,60 @@ where colors } +/// Color edges of a graph using a greedy approach. +/// +/// This function works by greedily coloring the line graph of the given graph. +/// +/// The coloring problem is NP-hard and this is a heuristic algorithm +/// which may not return an optimal solution. +/// +/// Arguments: +/// +/// * `graph` - The graph object to run the algorithm on +/// +/// # Example +/// ```rust +/// +/// use petgraph::graph::Graph; +/// use petgraph::graph::EdgeIndex; +/// use petgraph::Undirected; +/// use rustworkx_core::dictmap::*; +/// use rustworkx_core::coloring::greedy_edge_color; +/// +/// let g = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)]); +/// let colors = greedy_edge_color(&g); +/// let mut expected_colors = DictMap::new(); +/// expected_colors.insert(EdgeIndex::new(0), 2); +/// expected_colors.insert(EdgeIndex::new(1), 0); +/// expected_colors.insert(EdgeIndex::new(2), 1); +/// expected_colors.insert(EdgeIndex::new(3), 2); +/// assert_eq!(colors, expected_colors); +/// ``` +/// +pub fn greedy_edge_color(graph: G) -> DictMap +where + G: EdgeCount + IntoNodeIdentifiers + IntoEdges, + G::EdgeId: Hash + Eq, +{ + let (new_graph, edge_to_node_map): ( + petgraph::graph::UnGraph<(), ()>, + HashMap, + ) = line_graph(&graph, || (), || ()); + + let colors = greedy_node_color(&new_graph); + + let mut edge_colors: DictMap = DictMap::with_capacity(graph.edge_count()); + + for edge in graph.edge_references() { + let edge_index = edge.id(); + let node_index = edge_to_node_map.get(&edge_index).unwrap(); + let edge_color = colors.get(node_index).unwrap(); + edge_colors.insert(edge_index, *edge_color); + } + + edge_colors +} + #[cfg(test)] mod test_node_coloring { @@ -147,3 +202,53 @@ mod test_node_coloring { assert_eq!(colors, expected_colors); } } + +#[cfg(test)] +mod test_edge_coloring { + use crate::coloring::greedy_edge_color; + use crate::dictmap::DictMap; + use crate::petgraph::Graph; + + use petgraph::graph::{edge_index, EdgeIndex}; + use petgraph::Undirected; + + #[test] + fn test_greedy_edge_color_empty_graph() { + // Empty graph + let graph = Graph::<(), (), Undirected>::new_undirected(); + let colors = greedy_edge_color(&graph); + let expected_colors: DictMap = [].into_iter().collect(); + assert_eq!(colors, expected_colors); + } + + #[test] + fn test_greedy_edge_color_simple_graph() { + // Graph with an edge removed + let graph = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (1, 2), (2, 3)]); + let colors = greedy_edge_color(&graph); + let expected_colors: DictMap = [ + (EdgeIndex::new(0), 1), + (EdgeIndex::new(1), 0), + (EdgeIndex::new(2), 1), + ] + .into_iter() + .collect(); + assert_eq!(colors, expected_colors); + } + + #[test] + fn test_greedy_edge_color_graph_with_removed_edges() { + // Simple graph + let mut graph = Graph::<(), (), Undirected>::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)]); + graph.remove_edge(edge_index(1)); + let colors = greedy_edge_color(&graph); + let expected_colors: DictMap = [ + (EdgeIndex::new(0), 1), + (EdgeIndex::new(1), 0), + (EdgeIndex::new(2), 1), + ] + .into_iter() + .collect(); + assert_eq!(colors, expected_colors); + } +} From 2e9fcf51f3264cabec6b35fb71df642fda381f45 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 25 Jun 2023 16:48:15 +0300 Subject: [PATCH 25/26] changing test to be different from the one in docs --- rustworkx-core/src/line_graph.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rustworkx-core/src/line_graph.rs b/rustworkx-core/src/line_graph.rs index 61ddde4d0..b104fa578 100644 --- a/rustworkx-core/src/line_graph.rs +++ b/rustworkx-core/src/line_graph.rs @@ -114,10 +114,10 @@ mod test_line_graph { use petgraph::Undirected; #[test] - fn test_greedy_node_color_simple_graph() { + fn test_simple_graph() { // Simple graph let input_graph = - Graph::<(), (), Undirected>::from_edges(&[(0, 1), (0, 2), (1, 2), (0, 3)]); + Graph::<(), (), Undirected>::from_edges(&[(0, 1), (2, 3), (3, 4), (4, 5)]); let (output_graph, output_edge_map): ( petgraph::graph::UnGraph<(), ()>, @@ -129,7 +129,7 @@ mod test_line_graph { .map(|edge| (edge.source().index(), edge.target().index())) .collect::>(); - let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; + let expected_edge_list = vec![(2, 1), (3, 2)]; let expected_edge_map: HashMap = [ (EdgeIndex::new(0), NodeIndex::new(0)), (EdgeIndex::new(1), NodeIndex::new(1)), From d0c3ee383e53f7ef753eff5d9d08256408053471 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 25 Jun 2023 16:54:39 +0300 Subject: [PATCH 26/26] using greedy_edge_color from core --- src/coloring.rs | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/coloring.rs b/src/coloring.rs index f902966b4..9440bd9e4 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -10,15 +10,9 @@ // License for the specific language governing permissions and limitations // under the License. -use crate::{graph, StablePyGraph}; +use crate::graph; -use hashbrown::HashMap; - -use petgraph::graph::{EdgeIndex, NodeIndex}; -use petgraph::visit::{EdgeRef, IntoEdgeReferences}; -use petgraph::Undirected; -use rustworkx_core::coloring::greedy_node_color; -use rustworkx_core::line_graph::line_graph; +use rustworkx_core::coloring::{greedy_edge_color, greedy_node_color}; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -87,19 +81,10 @@ pub fn graph_greedy_color(py: Python, graph: &graph::PyGraph) -> PyResult PyResult { - let default_fn = || py.None(); - let (new_graph, edge_to_node_map): (StablePyGraph, HashMap) = - line_graph(&graph.graph, default_fn, default_fn); - - let colors = greedy_node_color(&new_graph); - + let colors = greedy_edge_color(&graph.graph); let out_dict = PyDict::new(py); - for edge in graph.graph.edge_references() { - let edge_index = edge.id(); - let node_index = edge_to_node_map.get(&edge_index).unwrap(); - let edge_color = colors.get(node_index).unwrap(); - out_dict.set_item(edge_index.index(), edge_color)?; + for (node, color) in colors { + out_dict.set_item(node.index(), color)?; } - Ok(out_dict.into()) }