-
Notifications
You must be signed in to change notification settings - Fork 193
Implement condensation graph generation #1337
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
Implement condensation graph generation #1337
Conversation
eed8af1
to
fada9a1
Compare
Pull Request Test Coverage Report for Build 14777358700Details
💛 - Coveralls |
8754ccd
to
9c026b2
Compare
@IvanIsCoding can anyone review this code? |
57dc3fe
to
5045623
Compare
Sorry I haven’t acknowledged the PR. I haven’t had time to review it. I will do the review after Christmas, it might still make the cut for rustworkx 0.16. |
Thank you for your quick response. |
src/connectivity/mod.rs
Outdated
g: Graph<N, E, Ty, Ix>, | ||
make_acyclic: bool, | ||
sccs: Option<Vec<Vec<usize>>>, | ||
) -> StableGraph<PyObject, PyObject, Ty, Ix> |
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 am wondering if it would make sense to also return a map between the nodes of the condensed graph and the (sets of) nodes of the original graph, so that information computed for the condensed graph could be lifted to the original graph as well?
self.graph.add_edge(self.node_h, self.node_e, "h->e") # サイクル: e -> f -> g -> h -> e | ||
|
||
def test_condensation(self): | ||
# condensation関数を呼び出し |
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.
It would be nice to translate all comments to English.
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 is not a review, but I was wondering: would it make sense to move this functionality to rustworkx-core? would it make sense to implement this both for directed and undirected graphs? would it make sense to return additional information relating the nodes of the original and the condensed graphs (see in-place comment).
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 the condensation logic looks good to me. I left comments that are mostly to simplify the existing code and make it more maintainable in the long run. I don't think you'll need to change much for the algorithm
I think the only debate left is how to return the node mappings. I personally like the short return type signature, so for me a graph attribute with the mapping seems like a good compromise.
src/connectivity/mod.rs
Outdated
check_cycle: false, | ||
node_removed: false, | ||
multigraph: true, | ||
attrs: py.None(), |
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.
Either the attrs
should contain the node mapping like in NetworkX. Or we need to update the signature to return a mapping
src/connectivity/mod.rs
Outdated
py: Python, | ||
graph: &digraph::PyDiGraph, | ||
sccs: Option<Vec<Vec<usize>>>, | ||
) -> digraph::PyDiGraph { |
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.
Sibling comment to either update the signature or return the node mapping somewhere else
Some notes to myself:
Feel free to do those on your own too. But I don't mind doing the documentation for you, it is fine to focus on the code. Again, thanks for your repeated contributions to rustworkx |
162ef11
to
dc8b154
Compare
@IvanIsCoding Now ready to review. I also added Rust doc and release notes. Please check them too. |
thanks @IvanIsCoding Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>
thanks @IvanIsCoding Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>
Update mod.rs
Update mod.rs Update mod.rs Update mod.rs
Update mod.rs ok ok wip Update mod.rs Reformat Reformat
c8feb98
to
571cd81
Compare
I plan to take a look at this soon, sorry for the delay |
Thank you for your response. |
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.
Some comments. Overall this works but there are some things that can be improved.
The tests look good though. You can keep them as they are.
src/connectivity/mod.rs
Outdated
#[pyo3(text_signature = "(graph, /, sccs=None)", signature=(graph, sccs=None))] | ||
pub fn condensation( | ||
py: Python, | ||
graph: PyObject, |
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.
We generally have the split at __init__.py
with a Python function with @_rustworkx_dispatch
and then two Rust functions with digraph_*
and graph_*
:
rustworkx/rustworkx/__init__.py
Line 148 in 217d58d
def distance_matrix(graph, parallel_threshold=300, as_undirected=False, null_value=0.0): |
I'd rather keep the existing pattern, of course this one works but consistency in the library is important
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.
We generally have the split at
__init__.py
with a Python function with@_rustworkx_dispatch
and then two Rust functions withdigraph_*
andgraph_*
:rustworkx/rustworkx/__init__.py
Line 148 in 217d58d
def distance_matrix(graph, parallel_threshold=300, as_undirected=False, null_value=0.0): I'd rather keep the existing pattern, of course this one works but consistency in the library is important
@IvanIsCoding I got it, and where do we place two Rust functions with digraph_*
and graph_*
, and a new python function with decorator?
Rust functions with digraph_*
and graph_*
: maybe 'src/connectivity/mod.rs', replacing current 'condensation' function
Python function with decorator: sorry but no idea
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 Python function goes here:
rustworkx/rustworkx/__init__.py
Line 852 in 30f2907
def complement(graph): |
It’s in __init__.py
, there’s not a lot of code there just the doc string essentially.
The Rust code can stay where it is right now
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 just tried to address this issue following other code.
src/connectivity/mod.rs
Outdated
let node_map_py = node_map.into_pyobject(py)?; | ||
let attrs = PyDict::new(py); |
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 believe you can use a DictMap
instead of a PyDict
and just call into_pyobject()
at the end. In general, working with Rust primitives might be easier. In general the Python APIs force you to handle errors that don't happen very often. For Rust, putting an item in the hashmap would "just work"
src/connectivity/mod.rs
Outdated
let attrs = PyDict::new(py); | ||
attrs.set_item("node_map", node_map_py)?; | ||
result.attrs = attrs.into(); | ||
Ok(result.into_pyobject(py)?.into()) |
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 you split the Rust methods in digraph_condensation
and graph_condensation
, you'll be able to just return the PyDiGraph
or PyGraph
object without needing this conversion
src/connectivity/mod.rs
Outdated
StableGraph::with_capacity(components_usize.len(), g.edge_count()); | ||
|
||
// Build a map from old indices to new ones. | ||
let mut node_map = vec![usize::MAX; g.node_count()]; |
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.
Prefer using None
instead of usize::MAX
to represent missing objects
src/connectivity/mod.rs
Outdated
let source = node_map | ||
.get(edge.source().index()) | ||
.copied() | ||
.unwrap_or(usize::MAX); |
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 comment about prefering Option<usize>
instead of using usize::MAX
as a replacement for None
Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>
8e7beb2
to
2f7f62a
Compare
Update lib.rs
2f7f62a
to
bc221d6
Compare
Reformat
bc221d6
to
3e5b320
Compare
Reformat Update __init__.py
3e5b320
to
3da19fe
Compare
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 addressing the comments
* Implement condensation tentatively * Update test_strongly_connected.py * Update src/connectivity/mod.rs thanks @IvanIsCoding Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> * Update rustworkx/rustworkx.pyi thanks @IvanIsCoding Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> * Update mod.rs Update mod.rs * Update mod.rs Update mod.rs Update mod.rs Update mod.rs * Update test_strongly_connected.py * Create condensation-undirected-support-apr2025.yaml * Use pyobject and bound Update mod.rs ok ok wip Update mod.rs Reformat Reformat * Update rustworkx.pyi * Update test_strongly_connected.py * Update test_strongly_connected.py * Replace MAX with None Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> * Separate digraph and graph function Update lib.rs * Add stub definition Reformat * Update __init__.py * Replace MAX with None (2) Reformat Update __init__.py * Reformat --------- Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com>
This pull request adds functionality to generate condensation graphs. The Rust implementation is copied and slightly modified from https://github.com/petgraph/petgraph because the original
petgraph
implementation cannot be applied as it is. Condensation graphs represent strongly connected components of a directed graph as single nodes. The update includes a new implementation and its test to support this feature.