-
Notifications
You must be signed in to change notification settings - Fork 150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
adding line_graph
and greedy_edge_color
for undirected graphs
#870
Changes from all commits
49d8594
cbcc239
6c44a7a
93bad32
38422e1
d3b4238
3cacbe3
7921827
3980a14
07c5ba7
3c4513e
d475d08
e78d069
79b7b4d
7dec989
99f0224
97e32ca
370407c
02e3691
ecdbe16
715f270
1456573
46d1350
bbacc33
c324acb
2e9fcf5
d0c3ee3
71f2de6
5e3af3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More a note for myself when we release 0.14.0 it'll be good to have this visualized by calling |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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<G>(graph: G) -> DictMap<G::NodeId, usize> | ||||||||||||||||||||||||||||||||||||||||
where | ||||||||||||||||||||||||||||||||||||||||
G: NodeCount + IntoNodeIdentifiers + IntoEdges, | ||||||||||||||||||||||||||||||||||||||||
G::NodeId: Hash + Eq + Send + Sync, | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
let mut colors: DictMap<G::NodeId, usize> = DictMap::new(); | ||||||||||||||||||||||||||||||||||||||||
let mut colors: DictMap<G::NodeId, usize> = DictMap::with_capacity(graph.node_count()); | ||||||||||||||||||||||||||||||||||||||||
let mut node_vec: Vec<G::NodeId> = graph.node_identifiers().collect(); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
let mut sort_map: HashMap<G::NodeId, usize> = 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<G>(graph: G) -> DictMap<G::EdgeId, usize> | ||||||||||||||||||||||||||||||||||||||||
where | ||||||||||||||||||||||||||||||||||||||||
G: EdgeCount + IntoNodeIdentifiers + IntoEdges, | ||||||||||||||||||||||||||||||||||||||||
G::EdgeId: Hash + Eq, | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
let (new_graph, edge_to_node_map): ( | ||||||||||||||||||||||||||||||||||||||||
petgraph::graph::UnGraph<(), ()>, | ||||||||||||||||||||||||||||||||||||||||
HashMap<G::EdgeId, NodeIndex>, | ||||||||||||||||||||||||||||||||||||||||
) = line_graph(&graph, || (), || ()); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
let colors = greedy_node_color(&new_graph); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
let mut edge_colors: DictMap<G::EdgeId, usize> = 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 | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+136
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that it matters from a performance perspective, but I think this could be rewritten as:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
#[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<EdgeIndex, usize> = [].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, usize> = [ | ||||||||||||||||||||||||||||||||||||||||
(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, usize> = [ | ||||||||||||||||||||||||||||||||||||||||
(EdgeIndex::new(0), 1), | ||||||||||||||||||||||||||||||||||||||||
(EdgeIndex::new(1), 0), | ||||||||||||||||||||||||||||||||||||||||
(EdgeIndex::new(2), 1), | ||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||
.into_iter() | ||||||||||||||||||||||||||||||||||||||||
.collect(); | ||||||||||||||||||||||||||||||||||||||||
assert_eq!(colors, expected_colors); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// 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::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 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; | ||
/// | ||
/// 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<petgraph::prelude::EdgeIndex, petgraph::prelude::NodeIndex>, | ||
/// ) = line_graph(&input_graph, || (), || ()); | ||
/// | ||
/// let output_edge_list = output_graph | ||
/// .edge_references() | ||
/// .map(|edge| (edge.source().index(), edge.target().index())) | ||
/// .collect::<Vec<(usize, usize)>>(); | ||
/// | ||
/// let expected_edge_list = vec![(3, 1), (3, 0), (1, 0), (2, 0), (2, 1)]; | ||
/// let expected_edge_map: HashMap<EdgeIndex, NodeIndex> = [ | ||
/// (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<K, G, T, F, H, M>( | ||
input_graph: K, | ||
mut default_node_weight: F, | ||
mut default_edge_weight: H, | ||
) -> (G, HashMap<K::EdgeId, G::NodeId>) | ||
where | ||
K: EdgeCount + IntoNodeIdentifiers + IntoEdges, | ||
G: Create + Data<NodeWeight = T, EdgeWeight = M>, | ||
F: FnMut() -> T, | ||
H: FnMut() -> M, | ||
K::EdgeId: Hash + Eq, | ||
{ | ||
let num_edges = input_graph.edge_count(); | ||
let mut output_graph = G::with_capacity(num_edges, 0); | ||
let mut output_edge_map = | ||
HashMap::<K::EdgeId, G::NodeId>::with_capacity(input_graph.edge_count()); | ||
|
||
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); | ||
} | ||
|
||
for node in input_graph.node_identifiers() { | ||
let edges: Vec<K::EdgeRef> = input_graph.edges(node).collect(); | ||
for i in 0..edges.len() { | ||
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()); | ||
} | ||
} | ||
} | ||
(output_graph, output_edge_map) | ||
} | ||
|
||
#[cfg(test)] | ||
|
||
mod test_line_graph { | ||
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; | ||
|
||
#[test] | ||
fn test_simple_graph() { | ||
// Simple graph | ||
let input_graph = | ||
Graph::<(), (), Undirected>::from_edges(&[(0, 1), (2, 3), (3, 4), (4, 5)]); | ||
|
||
let (output_graph, output_edge_map): ( | ||
petgraph::graph::UnGraph<(), ()>, | ||
HashMap<petgraph::prelude::EdgeIndex, petgraph::prelude::NodeIndex>, | ||
) = line_graph(&input_graph, || (), || ()); | ||
|
||
let output_edge_list = output_graph | ||
.edge_references() | ||
.map(|edge| (edge.source().index(), edge.target().index())) | ||
.collect::<Vec<(usize, usize)>>(); | ||
|
||
let expected_edge_list = vec![(2, 1), (3, 2)]; | ||
let expected_edge_map: HashMap<EdgeIndex, NodeIndex> = [ | ||
(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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More a note for myself when we release 0.14.0 it'll be good to have this visualized by calling
mpl_draw()
in the release notes instead of asserts. But I'll just do this as part of the release prep.