Skip to content

Conversation

Krishn1412
Copy link
Contributor

Feat: Add from_dot parser support

  • Implement DOT file parsing to load graph structures
  • Support reading DOT content from string input
  • Added grammar using pest
  • I ran the rust formatter and clippy
  • I have added test coverage for the changes

Krishn1412 and others added 4 commits August 2, 2025 17:02
Built a basic grammar and wired it up with functions to parse it and added it in the pymodule.

Next steps:
1) Fix the grammar, make it robust.

2) Add tests and documentations
Copy link
Collaborator

@IvanIsCoding IvanIsCoding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent start!

I will give it a deeper review, specially about graphviz's dot grammar which I need to understand more deeply.

I left some comments about tests and the grammar to get started

}

Rule::subgraph => {
// TODO: subgraph handling
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok to return an error for subgraphs now. dot might be too expresive. We just want to make sure the output from a rustworkx entry can be re-imported as the original goal.

Once we have that, we can support more advanced features

});
} else {
let graph = StablePyGraph::<Undirected>::with_capacity(0, 0);
undi_graph = Some(PyGraph {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts about this handling:

  • I'd make a function that is parametrized on StablePyGraph e.g.
    fn union<Ty: EdgeType>(
  • Upon detecting the type, call the parametrization on the if/else

If you need to detect the direction of the graph, call https://docs.rs/petgraph/latest/petgraph/stable_graph/struct.StableGraph.html#method.is_directed within the parametrized function.

I think that choice will simplify your code, especially for handling both di_graph and undi_graph on every statement

let src_idx = *node_map.entry(src.clone()).or_insert_with(|| {
let py_node = PyString::new(py, &src).into();
if is_directed {
di_graph.as_mut().unwrap().graph.add_node(py_node)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you follow the previous recommendation, this becomes graph.add_node which arguably is simpler that handling the Option<PyGraph> each time

1) Added a new function to use stable graph with is_directed as a parameter to avoid creating python object every time.

2) Added more tests to include round trip from to_dot and from_dot for graph and digraph.
@Krishn1412
Copy link
Contributor Author

Hey @IvanIsCoding, could you take a look again.

Copy link
Collaborator

@IvanIsCoding IvanIsCoding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some minor comments, but this is very close to getting merged:

  • Use the hashmap from hashbrown for consistency. It should be slightly faster because of the hasher it uses vs std which uses a more conservative hasher
  • Avoid panicking, be careful with unwrap(). Python users cannot recover easily from panic, they'd rather deal with exceptions.

And last but not least, don't forget to add a release note: https://github.com/Qiskit/rustworkx/blob/main/CONTRIBUTING.md#release-notes. I am planning on promoting this as one of the key new features in the 0.18.0 launch. People will want to read about it, it's handy!

use crate::StablePyGraph;

use rustworkx_core::petgraph::prelude::{Directed, NodeIndex, Undirected};
use std::collections::HashMap;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the hashmap from hashbrown: https://docs.rs/hashbrown/latest/hashbrown/struct.HashMap.html

That's what we use everywhere

continue;
}
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid unwrapping if possible, do inner.next()?.

match stmt.as_rule() {
Rule::node_stmt => {
let mut it = stmt.into_inner();
let nid = it.next().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here. If you keep needing a conversion from pest errors to PyResult, make a helper closure and do a .map_err or something along those lines


Rule::assignment => {
let mut parts = stmt.into_inner();
let key = parts.next().map(|p| p.as_str()).unwrap_or("");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unwrap_or is fine. Keep it as is.

Copy link
Collaborator

@IvanIsCoding IvanIsCoding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think you'll need to downgrade pest to compile with our MSRV.

I can also bump the MSRV in a separate PR

Cargo.toml Outdated
@@ -64,6 +64,8 @@ serde_json = "1.0"
smallvec = { version = "1.0", features = ["union"] }
rustworkx-core = { path = "rustworkx-core", version = "=0.17.0" }
flate2 = "1.0.35"
pest = "2.7"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, changes to Cargo.toml should also have a corresponding Cargo.lock change. Did you forget to commit Cargo.lock?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants