-
Notifications
You must be signed in to change notification settings - Fork 153
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 layers
function to rustworkx-core
#1194
Conversation
- Modify the `layers` python interface to use the `rustworkx-core` equivalent.
Pull Request Test Coverage Report for Build 9685577238Details
💛 - Coveralls |
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.
Thanks for doing this, just some quick high level comments from a first skim over the code.
The other things are can you add a couple of other rust space tests so we have some more dedicated coverage of the rust api (more than just the doctest) and also a release note?
- Move `layers` to `dag_algo.rs`. - Add check for cycles, if a cycle is found throw an error. - Refactor `LayersIndexError` to `LayersError`. - Move `LayersError` to `err.rs`. - Other small tweaks and fixes.
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 some quick comments from a first skim through the PR.
releasenotes/notes/add-layers-rustworkx-core-1caaccb1ca292ca8.yaml
Outdated
Show resolved
Hide resolved
releasenotes/notes/add-layers-rustworkx-core-1caaccb1ca292ca8.yaml
Outdated
Show resolved
Hide resolved
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.
Thanks for doing this, it's a good start. I think in this case we should make layers()
a true iterator because it doesn't need to do a complete traversal to compute the final output. We can and should return this layer by layer. The reason we couldn't do that in python is we can't put references in a pyclass for the iteration, but in a Rust API we don't have that limitation. I left an inline code suggestion to start you down that path.
The other thing is I'm thinking we may not need to return a Result<>
for this function, the only error condition we returned before was input checking we needed for Python interoperability, but doesn't really feel right in a rust API. You added a cycle check in this case which we could error for, but I'd also be fine just panicking in that case because this function doesn't work if there are cycles. Or alternatively just stop iterating when we reach a cycle. In either case we could just document that behavior and avoid the Result
and I think either would probably be acceptable because this function is really only for DAGs so you shouldn't be passing a graph with cycles.
- Use panic exceptions for specific cases. - Other tweaks and fixes.
- Use `panic!` when a programming error is made. - Verify cycles by checkng repeating layers, call `panic!` if one is found. - Adapt python side function to use check for nodes to avoid panic. - Adapt tests.
- Small fix in docstring.
Thank you for the reviews @mtreinish, I believe this PR should be in a better position after the latest commtis. I managed to remove some of the overhead of iterating through the entire graph to find cycles, instead I keep track of all the layers being found, if one of them repeats, then a cycle is present. I thought of maybe doing it by index but, in some cases, the indices might repeat without it necessarily being a cycle. let edge_list = vec![
(0, 1),
(1, 2),
(2, 3),
(3, 4),
];
let graph = DiGraph::<u32, u32>::from_edges(&edge_list);
let layers: Vec<Vec<NodeIndex>> = layers(&graph, vec![0.into(),1.into()]).collect();
let expected_layers: Vec<Vec<NodeIndex>> = vec![
vec![0.into(),1.into()],
vec![1.into(),2.into()],
vec![2.into(),3.into()],
vec![3.into(),4.into()],
vec![4.into()]
];
assert_eq!(layers, expected_layers) In this case, while the layers show repeated indices, they don't represent a cycle. Also due to the nature of the graph being that when a node is removed the indices shift, therefore keeping the nodes within a range, if a node is provided outside that range (which can be found using As always there is room for improvement. If there's any other inconsitencies that anyone might find feel free to leave it as a review comment. Thanks! |
releasenotes/notes/add-layers-rustworkx-core-1caaccb1ca292ca8.yaml
Outdated
Show resolved
Hide resolved
releasenotes/notes/add-layers-rustworkx-core-1caaccb1ca292ca8.yaml
Outdated
Show resolved
Hide resolved
- Add result handling in the python version of the function. - Use indices to keep track of cycles. - Revert deletion of `LayersError`. - Update tests. - Other tweaks and fixes.
- Fix docstring test and regular test. - Add extra check for missing nodes.
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.
This LGTM now thanks for the updates and thoroughness on this PR. I just have a few minor nits and questions inline. But otherwise I think this is ready to merge.
if self.graph.to_index(*node) >= self.graph.node_bound() { | ||
panic!("Node {:#?} is not present in the graph.", node); | ||
} |
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.
Do we need this explicitly, won't we just get a panic if we try to access node
in neighbors_directed()
below?
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.
Not really. From the docs:
Produces an empty iterator if the node doesn’t exist.
Iterator element type is NodeIndex.
It only produces an empty iterator, therefore I had to introduce a way to make it panic if the index does not belong.
rustworkx-core/src/dag_algo.rs
Outdated
if self.cycle_check.contains(node) { | ||
return Some(Err(LayersError( | ||
"An invalid first layer was provided.".to_owned(), | ||
))); |
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.
Is it possible to have a cycle here? Or are you protecting against duplicate entries in first_layer
. If it's duplicates we should put that in the error message.
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 protecting against duplicates. I can make the warning more explicit though.
if self.graph.to_index(*node) >= self.graph.node_bound() { | ||
panic!("Node {:#?} is not present in the graph.", node); | ||
} |
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.
Same question here
- Remove calls to `to_owned()`.
Attempts to resolve #1167
Summary
These commits bring the
layers
function down torustworkx-core
so that it can be used within the rust interface. This function was only available through a python interface previously.Changes
rustworkx.rustworkx-core.layers
layers()
: This function returns a list of subgraphs whose nodes are disjointed.NodeId
instances. To get the weights/data stored in these nodes, the user can iterate through the graph accessing the weights based on the instance ofNodeId
.rustworkx.dag_algo
layers()
: This function is similar to the one inrustworkx-core
, but allows the user to choose between getting node indices and node weights/data.rustworkx-core
version, but its behavior is identical to what it was previously.Future directions:
To return anAdded!Iterator
instance fromrustworkx-core
, instead of aVec<Vec<G::NodeId>>
to prevent from unnecessary allocations.