Skip to content

Implementation of cycle_basis_edges#885

Open
raynelfss wants to merge 31 commits into
Qiskit:mainfrom
raynelfss:cycle_basis_edges
Open

Implementation of cycle_basis_edges#885
raynelfss wants to merge 31 commits into
Qiskit:mainfrom
raynelfss:cycle_basis_edges

Conversation

@raynelfss

@raynelfss raynelfss commented May 31, 2023

Copy link
Copy Markdown
Contributor

Fixes #551

Summary

The following commits implement the function cycle_basis_edges which draws inspiration from the existent cycle_basis function. This version modifies the existing pipeline to return a List containing a vector of EdgeId in the rustworx-core version of the function.

@CLAassistant

CLAassistant commented May 31, 2023

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@raynelfss

Copy link
Copy Markdown
Contributor Author

Results from testing in Python:

image

@coveralls

coveralls commented May 31, 2023

Copy link
Copy Markdown

Coverage Report for CI Build 27038822808

Coverage decreased (-0.03%) to 94.695%

Details

  • Coverage decreased (-0.03%) from the base build.
  • Patch coverage: 7 uncovered changes across 1 file (76 of 83 lines covered, 91.57%).
  • 3 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
rustworkx-core/src/connectivity/cycle_basis.rs 76 69 90.79%
Total (3 files) 83 76 91.57%

Coverage Regressions

3 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
rustworkx-core/src/generators/random_graph.rs 3 86.73%

Coverage Stats

Coverage Status
Relevant Lines: 20281
Covered Lines: 19205
Line Coverage: 94.69%
Coverage Strength: 914065.53 hits per line

💛 - Coveralls

@mtreinish mtreinish left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So overall this looks like a good start. The big things that I think are missing are python side tests to validate the python api is working as expected and also a release note (https://github.com/Qiskit/rustworkx/blob/main/CONTRIBUTING.md#release-notes).

The other small concern I have is the amount of duplication between cycle_basis_edges and cycle_basis. The code is mostly the same between the function and I'm thinking it would be good to combine them into a single code path.

As for the edge weight for the python side you can call weight.clone_ref(py) as it's a PyObject type and we can clone it with a GIL handle (which is what the Python type gives us).

/// let graph = UnGraph::<i32, i32>::from_edges(&edge_list);
/// let mut res: Vec<Vec<(NodeIndex, NodeIndex)>> = cycle_basis_edges(&graph, Some(NodeIndex::new(0)));
/// ```
pub fn cycle_basis_edges<G>(graph: G, root: Option<G::NodeId>) -> Vec<Vec<(G::NodeId, G::NodeId)>>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of returning Vec<(G::NodeId, G::NodeId)> here it might be better to return Vec<G::EdgeId>. Then if we want to return the edge endpoint or weight we can just look it up from the index.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's funny you mention this, as in an earlier version, I had the function return the EdgeId attribute of each EdgeRef but replaced it with the tuple thinking that it should instead return the edge objects.

Also, I'm assuming that by returning the edge endpoint you mean returning it using the wrapper located in rustworkx/src/connectivity/mod.rs lines 100 to 110. Should I use the graph.edges() method fetch the edges by index?

@mtreinish mtreinish Jun 1, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

By edge endpoints I meant what you're doing now. For each edge you find you're mapping to (edge.source(), edge.target()) (the endpoints being the tuple of nodes for the edge), but instead if you did edge.id() that would give you the unique G::EdgeId for the edge. Then if you returned a Vec<G::EdgeId> callers of this function could look up any attributes of the edge they needed from that list. For examples if you wanted a list of endpoint tuples you could do something like:

let edge_endpoints = cycle_basis(graph, None)
    .into_iter()
    .filter_map(|e| graph.edge_endpoints(e))
    .collect()

but it would also give you the flexibility to get the weights, or other details. It basically is just returning a reference to the edge in graph instead of one of properties of the edge.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see, I will make those changes accordingly.

@mtreinish mtreinish added this to the 0.14.0 milestone Jun 1, 2023
@danielleodigie

Copy link
Copy Markdown
Contributor

This looks pretty good to me, just a quick question: if there a reason to declare both a cycles_edges and a cycles_nodes if from what I understand we only return one or the other? Is it possible in Rust to declare variables like:
if want_edge: let mut cycles: Vec<Vec<G::EdgeId>> = Vec::new(); else: let mut cycles: Vec<Vec<G::NodeId>> = Vec::new();. This is low-key just for me to learn

@raynelfss

Copy link
Copy Markdown
Contributor Author

if there a reason to declare both a cycles_edges and a cycles_nodes if from what I understand we only return one or the other? Is it possible in Rust to declare variables like:
if want_edge: let mut cycles: Vec<Vec<G::EdgeId>> = Vec::new(); else: let mut cycles: Vec<Vec<G::NodeId>> = Vec::new();

Well, there might be a better way of doing so that doesn't involve two vectors. The reason I did it this way is that I couldn't conditionally assign this variable with different types (G::EdgeId || G::NodeId) without having to wrap two similar processes under if and else statements, which could be a bit redundant.

I could, however, change the enum so that it represents the individual nodes/edges (instead of Vec<Vec<T>> for each type) and make the function return an instance of Vec<Vec<EdgeOrNodes<E, N>>> and then unwrap the values of said Vec individually.

The problem happens when importing this enum into the wrapper function, which complains about the types in use. I could try and get this to work with enough time.

@danielleodigie

Copy link
Copy Markdown
Contributor

if there a reason to declare both a cycles_edges and a cycles_nodes if from what I understand we only return one or the other? Is it possible in Rust to declare variables like:
if want_edge: let mut cycles: Vec<Vec<G::EdgeId>> = Vec::new(); else: let mut cycles: Vec<Vec<G::NodeId>> = Vec::new();

Well, there might be a better way of doing so that doesn't involve two vectors. The reason I did it this way is that I couldn't conditionally assign this variable with different types (G::EdgeId || G::NodeId) without having to wrap two similar processes under if and else statements, which could be a bit redundant.

I could, however, change the enum so that it represents the individual nodes/edges (instead of Vec<Vec<T>> for each type) and make the function return an instance of Vec<Vec<EdgeOrNodes<E, N>>> and then unwrap the values of said Vec individually.

The problem happens when importing this enum into the wrapper function, which complains about the types in use. I could try and get this to work with enough time.

I mean if it works fine, I don't see any reason to change it, I was just asking. Maybe ask @mtreinish. Otherwise, looks good

@danielleodigie danielleodigie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@raynelfss raynelfss requested a review from mtreinish June 7, 2023 14:10
@mtreinish mtreinish marked this pull request as ready for review June 7, 2023 19:30

@mtreinish mtreinish left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fast update. This is look better from a code duplication standpoint and is making good progress. I left some inline comments mainly about the interface split. I have some concerns that the current version of the code has some breaking API changes to rustworkx-core that we can't make without potentially impacting users. I left some inline suggestions on how we can tweak this a bit to avoid that kind of impact.

Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread src/connectivity/mod.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated

@mtreinish mtreinish left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is looking good, thanks for the fast updates on this, I have a few more comments inline.

Comment on lines +53 to +56
fixes:
- |
Support for edges when getting the cycle basis. Refer to
`#551 <https://github.com/Qiskit/rustworkx/issues/551>`__ for more details. No newline at end of file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You don't actually need this release note what you have for the feature note is more than sufficient to document the new feature for this release. This is really just a new feature there isn't any bug being fixed here. The github "issues" tracker is really both for tracking bugs and also for feature requests.

Comment thread releasenotes/notes/add-cycle-basis-edges-5cb31eac7e41096d.yaml
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
@raynelfss raynelfss requested a review from mtreinish June 13, 2023 19:22
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
Comment thread rustworkx-core/src/connectivity/cycle_basis.rs Outdated
- Inspired from the existing procedure `cycle_basis`.
- Modified to return a tuple of `NodeId` in format `(source, target)`.
- Added API calls so that method works in Python.
- Cycle_basis_edges now returns a list of edgeIDs.
- Added `TestCycleBasisEdges` class for unit testing.
- Added similar tests to those present on `TestCycleBasis`.
    - Tests are similar due to the same methodology.
    - Graphs used for testing are different.
- Modified get_edge_between to only check target when equiv == false.
- Cycle basis edges is now within the codebase of cycle basis.
- The `EdgesOrNodes` enum handles the different return data types.
- Release notes now include an example with a jupyter notebook.
- A previous commit inadvertedly made cycle_basis prublic.
    - To address this, two new public-facing functions were added:
        - `cycle_basis` for NodeId returns.
        - `cycle_basis_edges` for EdgeId returns.
- When customizing cycle_basis a new enum EdgesOrNodes was made.
    - Enum should not be have been shared by definition.
        - Correction makes it private.
    - Removed redundant import from unwrap methods.
- Tests have been adjusted accordingly.
- The method returns the first edge found between nodes.
- The docstring for this method was corrected.
@IvanIsCoding IvanIsCoding modified the milestones: 0.14.0, 0.15.0 Oct 18, 2023
@IvanIsCoding IvanIsCoding removed this from the 0.15.0 milestone Jun 22, 2024
raynelfss and others added 10 commits July 16, 2024 14:21
The following commits repackage our approach into an easier to digest version of the previous code by:
- Remove`EdgeOrNodes<N, E>` in favor of having the function `inner_cycle_basis` use generics.
- Modify `inner_cycle_basis` to use function arguments to define behaviors of the different cycle types.
    - Since node cycles and edge cycles behave differring behavior specifically on what is done when finding a cycle, we can just send in a filter function to make things work better.
- Use `filter_map` instead of `filter -> map` for `get_edge_between`.
The following commits restore original trait bounds for the `cycle_basis` function, as well as relaxing some of the traits for `inner_cycle_basis`.
- Remove upgrade note.
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.

Add equivalent of cycle_basis that returns an edge list

6 participants