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
fix: Include singleton/trivial paths in all_simple_paths & other functions #6694
fix: Include singleton/trivial paths in all_simple_paths & other functions #6694
Conversation
4df2485
to
b7aefce
Compare
b7aefce
to
d75e92d
Compare
While fixing some of the newly tested issues, I found it more convenient to refactor the existing code. Now both While this PR is relatively large, I think all of these changes belong together and that it would be quite difficult to break it down into completely independent, self-coherent PRs. |
This last commit (0adbfc6) is optional. It's so that the size of current_path and the stack are always the same, to avoid having to constantly check |
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 this!
- I have two minor suggestions for readability of tests below.
- We don't use typing in NetworkX, so could that be removed?
- I'm still working through the code itself. Can you explain how
current_path
works. Is the incoming edge tonode
an edge between the previous node in the dict's order andnode
? I get that you are storing it as the value in this dict. But I'm trying to understand how the dict structure stores the path. And perhaps a related point: I think of finding/storing the path to be more efficient than storing the edge-path. Is there a reason you are forming the edge-path and then extracting the path, rather than constructing the path and then extracting the edge-path? (perhaps related to keys?)
c0d9dbd
to
e365e40
Compare
Done!
It's a dictionary which maps the node in the path to the edge that was used to enter that node in the path. Moreover, since dictionaries preserve insertion order, it is also contains the sequence of edges that forms the path. For instance, in the path graph with two nodes, 0 and 1, the path from 0 to 1 would be represented in this dictionary as
Namely it allows the trivial path to be considered as part of the general loop instead of having to deal with it separately as a special case (a separate So the path mentioned above would be represented as By the way, I added this dummy value in d7bd2f5 as an optional refactor, you can look at the commit diff to see the alternative (how it was before). I made this as a separate commit so that it can easily be dropped if you prefer it the other way.
Exactly.
I assume that you're asking why networkx/networkx/algorithms/simple_paths.py Lines 255 to 256 in e365e40
The reason for extracting the node path from the edge path rather than the other way around is, as you suspected, multigraphs: you can always extract the node path from an edge path, but you lose information if you try to extract the edge path from a node path because there might be more than one edge between nodes in the case of a multigraph. For instance, consider the multigraph with edges By doing it this way around it is possible to keep a single implementation for all cases, including multigraphs. The reason for using a dictionary with nodes as keys (instead of e.g. storing just a list of edges), is so that when we're considering a new node to add to the path, we can check in O(1) time whether that node is already in the path. Does this explanation make it clearer? Feel free to ask again if there is anything else that you'd like me to explain better. I'm open to suggestions if you think the code or the comments can be made clearer :) |
9a8d8c2
to
997a9e3
Compare
There are some unrelated tests that are failing in CI that I don't know how to fix. I already rebased onto the latest master. |
I suspect this is due to the latest release of scipy (which came out yesterday). No need to worry about it in this PR, thanks for trying a rebase though! |
So that the size of current_path and the stack are always the same, to avoid having to check `if current_path` is non-empty before popping.K
Co-authored-by: Dan Schult <dschult@colgate.edu>
90dd5f9
to
56750af
Compare
@dschult Any updates on this? I've rebased again to see if the CI fails disappear. |
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.
Sorry for the delay/silence. I've looked through this again and I think we're all set to merge. I approve this PR.
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 @plammens !
The source being one of the targets shouldn't automatically exclude all other solutions.
Closes #6690, closes #6732
all_simple_edge_paths
(pending discussion, see all_simple_paths returns empty generator when the source is one of the possible targets #6690 (comment))