-
Notifications
You must be signed in to change notification settings - Fork 151
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 graph
module, port contract_nodes
and has_parallel_edges
to core.
#1143
Conversation
This commit bumps the MSRV for rustworkx in 0.15.0 to 1.70.0. This enables us to use GATs (see Qiskit#1143) and also enables to update some of our dependencies which require newer versions of rust to compile.
* Bump MSRV to 1.70 This commit bumps the MSRV for rustworkx in 0.15.0 to 1.70.0. This enables us to use GATs (see #1143) and also enables to update some of our dependencies which require newer versions of rust to compile. * Use 1.70 instead of 1.7 * Update .github/workflows/main.yml --------- Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>
Pull Request Test Coverage Report for Build 9190954784Details
💛 - 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.
Overall this LGTM. I'm happy with a lot of the patterns you're introducing here as I feel like they're easily extendable for additional use cases. I just had a couple of small inline questions and comments.
The other though was maybe we should add a prelude
so that users can do use rustworkx_core::prelude::*
and get all the extension traits and error structs from a single place. I think we should probably leave the algorithm functions as standalone things, but putting the basics for interacting with the library in a prelude seems like the normal rust pattern.
rustworkx-core/src/err.rs
Outdated
#[allow(dead_code)] | ||
fn fmt_node_not_found<N: Debug>(f: &mut Formatter<'_>, node: &N) -> std::fmt::Result { | ||
write!(f, "Node not found in graph: {:?}", node) | ||
} | ||
|
||
#[allow(dead_code)] | ||
fn fmt_edge_not_found<E: Debug>(f: &mut Formatter<'_>, edge: &E) -> std::fmt::Result { | ||
write!(f, "Edge not found in graph: {:?}", edge) | ||
} |
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.
If these aren't used is there a reason to keep them around?
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.
They're here mostly because I'm hoping to establish a pattern, and I'm not confident there's enough here otherwise for it to stick.
I can remove if it's your preference, let me know!
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.
Personally I'd rather remove the dead code or leave it as a comment or something. To me it's more confusing to leave this around I didn't see the pattern. I'm thinking if you want to make this a pattern I'd put this as a comment at the top using these as an example and explaining how they should be used.
This should be ready for another look. Thanks for the review @mtreinish! EDIT: On the topic of a |
Sure, I agree. We can think about a rustworkx core prelude in a follow up for 0.16.0. I'm not sure exactly what we'd want to put it in it. |
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, but it needs a release note (unless I missed it) to document the additions to rustworkx-core.
Important
In the original PR description, I'd suggested we use blanket implementations for all graph types, constrained to apply to compatible graph types using traits. However, this proved not to be ideal, since
petgraph
does not provide a trait to identify stable versus unstable graphs, yet most algorithms would need to rely on this property to be written efficiently.Instead, the PR now recommends that we provide explicit implementations per
petgraph
graph type for ported algorithms.As part of #1121, this PR is meant as a proposal for a few patterns we might follow to bring
rustworkx-core
up to parity with our Python API.To demonstrate these patterns, I've ported
contract_nodes
andhas_parallel_edges
fromPyGraph
andPyDiGraph
torustworkx-core
, making the functions available for all stablepetgraph
graphs (chosen as "complex" and "simple" examples, respectively).Note
Many of the methods that currently exist on
Py(Di|)Graph
are there only to make the Python API more ergonomic (i.e. to side-step PyO3 limitations with iterators and data ownership). These methods should probably not be ported to core. In general,rustworkx-core
's API should feel like a Rust API.Extension traits
The primary pattern proposed here is to provide traits for the various graph methods of
Py(Di|)Graph
worth porting to core, along with implementations for applicablepetgraph
graph types.To aid in discoverability, we can also expose modules intended for import via
*
that bring all supported extension methods into scope for the graph type. For example, we can import all graph extensions as follows (which, at least in the CLion IDE, adds per-graph compatible methods to code completion):Node contraction
To see how this idea works with a complex example, I've ported
contract_nodes
fromPy(Di|)Graph
. To do this, we introduce 4 different traits:ContractNodesUndirected
,ContractNodesSimpleUndirected
,ContractNodesDirected
, andContractNodesSimpleUndirected
.We need this many because each of the methods between
ContractNodes(Un|Di)rected
andContractNodesSimple(Un|Di)rected
has a different signature and return type (since each has a different set of possible errors).Error interface
In #1124 we started discussing how the core's error interface ought to look. Originally, I proposed that we should aim to have a single error type returned by all core functions which may fail. However, I see now that this would have been somewhat of a Rust anti-pattern: it's generally encouraged to have error types which can indicate multiple failure cases, but it's not proper for functions to return error types with variants that can never happen in the given function's execution.
Instead, we define unique error enum types for each function to encapsulate exactly the error set it may return. For example, we introduce two error types
ContractError
andContractSimpleError
for node contraction, since only simple contractions can fail due to an edge merge issue.For errors, I'd propose putting all error types within
src/err.rs
. These can then be imported internally by extension trait implementations in thegraph
module, as well as by top-level functions outside it.On the Python-side, we can implement
From<...>
forRxPyErr
to automatically map each error type to a Python exception. This is done for bothContractError
andContractSimpleError
. See the docstring forRxPyErr
for more detail.Integration testing
I've added a new folder
tests/graph_ext
which contains amain.rs
file. Any modules registered there get automatically picked up and included in the same integration testing executable. For now, there's a filecontraction.rs
which contains modules for testing againstStableGraph
andGraphMap
(the two stable graph types inpetgraph
). Additional algorithms ported tograph_ext
should be added to this folder.Other
NodeRemovable
, which we've been needing for a while. It's similar to theBuild
trait provided bypetgraph
, but provides node removal.graph
module has been added to house our extension methods. Top-level functions can still make sense, and I don't propose we move existing ones. But perhaps for some, we ought to consider exposing them as extensions in parallel for user convenience.Todo
contract_nodes
andhas_parallel_edges
to core.