-
Notifications
You must be signed in to change notification settings - Fork 161
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
Add Misra-Gries edge coloring method #902
Conversation
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.
I left a few quick initial comments from a first scan through the PR. The other question I have is whether you want to add this to rustworkx-core or not? I think it's probably generally useful enough (and you did just add the node coloring function to the core crate) that it might make sense to put this in rustworkx-core from the start.
src/coloring.rs
Outdated
let used_colors = self.get_used_colors(u); | ||
let mut c: usize = 0; | ||
while used_colors.contains(&c) { | ||
c += 1; | ||
} | ||
c |
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.
Just for fun you could do this as an iterator too, but not sure if is any faster. Something like:
let used_colors = self.get_used_colors(u); | |
let mut c: usize = 0; | |
while used_colors.contains(&c) { | |
c += 1; | |
} | |
c | |
let used_colors = self.get_used_colors(u); | |
(0..) | |
.position(|color| used_colors.contains(&color)) | |
.unwrap() |
src/coloring.rs
Outdated
fan_extended = true; | ||
last_node = *z; | ||
fan.push((*edge_index, *z)); | ||
let position_z = neighbors.iter().position(|x| x.1 == *z).unwrap(); |
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.
Instead of doing this it'd probably be more efficient to use neighbors.iter().enumerate()
for the for loop. Then we'd have the index for every step and position_z
would have no lookup overhead. Right now this is O(n) for this lookup as you have to re-traverse neighbors
on each iteration.
@mtreinish, thanks for the detailed feedback. I guess putting the functionality in rustworkx-core makes sense. And I really like the iterator-based way of implementing things. |
I have made another round of changes, moving the new edge coloring function to rustworkx-core and changing the |
Pull Request Test Coverage Report for Build 7479009283
💛 - Coveralls |
I think the ci failure was a temporary issue related to the release of a new version of something. I just updated the pr branch and it seemed to have work fine. |
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.
Overall this LGTM, I just had a few small questions and suggestions inline.
I am a bit worried about the complexity of some of the inner loops in the helper methods as there is a lot of nested looping and those all get called in an outer loop from run_algorithm()
. I'll need to re-read the paper to see if that's just inherit to the algorithm. But either way for an initial implementation this is fine anyway, if it becomes a performance bottleneck we can always make improvements down the road.
rustworkx-core/src/coloring.rs
Outdated
// Returns the smallest free (aka unused) color at node u | ||
fn get_free_color(&self, u: G::NodeId) -> usize { | ||
let used_colors = self.get_used_colors(u); | ||
let free_color: usize = (0..) |
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.
Just to avoid unbound iteration as a potential source of bugs:
let free_color: usize = (0..) | |
let free_color: usize = (0..self.colors.len()) |
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.
Fixed in 4e22ad7 using the stricter upper bound of max_node_degree + 1
.
rustworkx-core/src/coloring.rs
Outdated
let used_colors = self.get_used_colors(u); | ||
!used_colors.contains(&c) |
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.
Could you just do self.colors.contains(&Some(c))
(or something like this, I might have got the position of &
incorrect)? You'd have the same linear overhead.
More generally though I'm wondering if it makes more sense to store a HashSet
of used colors in the MisraGries
struct so you don't have to generate it on the fly every time. Then you could make this a O(1) lookup every time. It would require doing book keeping on adding or changing colors but it should be manageable.
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.
I have followed the above suggestion in 4e22ad7, and indeed it gives a significant speedup on medium/large-sized complete graphs. E.g., on my laptop previously it took 16.95
seconds to color a complete graph over 90 vertices, and now it takes 0.58
seconds. For heavy-hex graphs the difference is less pronounced, but the new implementation is still a bit faster.
rustworkx-core/src/coloring.rs
Outdated
let mut fan: Vec<(G::EdgeId, G::NodeId)> = Vec::new(); | ||
fan.push((eid, v)); |
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.
let mut fan: Vec<(G::EdgeId, G::NodeId)> = Vec::new(); | |
fan.push((eid, v)); | |
let mut fan: Vec<(G::EdgeId, G::NodeId)> = vec![(eid, v)]; |
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.
Done in 7900916. Also the code has been updated to use G::EdgeRef
instead of G::EdgeId
, so it's enough to only keep the edge reference.
rustworkx-core/src/coloring.rs
Outdated
// Returns the longest path starting at node u with alternating colors c, d, c, d, c, etc. | ||
fn get_cdu_path(&self, u: G::NodeId, c: usize, d: usize) -> Vec<(G::EdgeId, usize)> { | ||
let mut path: Vec<(G::EdgeId, usize)> = Vec::new(); | ||
let mut cur_node: G::NodeId = u; |
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.
The compiler should be able to infer the type here because it's defined on u
.
let mut cur_node: G::NodeId = u; | |
let mut cur_node = u; |
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.
Removed in 7e6470d.
Oh, the other thing is the new function needs to be added to the api docs index here: https://github.com/Qiskit/rustworkx/blob/main/docs/source/api/algorithm_functions/other.rst (although maybe we should add a graph coloring page at this point) |
Agreed, it would be great if we could remove the extra loop in the |
Yeah, I don't have any concrete suggestions off the top of my head yet either. It was more just my impressions as I was reviewing it. But like I said we can leave it like this and if performance becomes an issue or one of us comes up with something it's easy enough to change and improve the internal logic later. |
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.
LGTM, thanks for doing this.
This is an attempt to add the Misra-Gries edge coloring algorithm to Rustworkx, following @ihincks's python code in #854 and wiki page https://en.wikipedia.org/wiki/Misra_%26_Gries_edge_coloring_algorithm. This is still raw, but I would be happy to get some initial feedback on which parts of the code could be implemented in more rust-friendly way, whether the names and inputs/outputs of different functions look ok, etc.