-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Consistent dijkstra api #3923
base: main
Are you sure you want to change the base?
Consistent dijkstra api #3923
Conversation
Previously only single_source_dijkstra had a target parameter, but not single_source_dijkstra_path or single_source_dijkstra_path_length
write variants of existing testcases to make use of the target parameter
This seems like a good idea. Unifying the API for those 3 functions is a good goal. |
I couldn't find an easy way: Additionally, a I'm not 100% happy with the current |
Here's an approach that would make that work smoothly (and other things):
I think this will take care of the capture_paths argument in the API. And it'll make the rest of the code easier to parse for people trying to figure out how this code works. |
Move `capture_paths` and argument checking to a "middle-layer" function `_dijkstra_multisource_checked`, before calling the "inner-layer" `_dijkstra_multisource` implementation. This prevents adding `capture_paths` to the public API, but maintains a single place for argument checking/return value handling without code duplicates.
How about the following compromise, that moves To reply to some of your comments:
Aren't we already in that situation?
Is the performance-penalty worth the code-duplication? In terms of performance penalty, I did a quick benchmark using one of the small graphs from the test-suite: import timeit
import networkx as nx
from networkx.algorithms.shortest_paths.weighted import _dijkstra_multisource_checked
def main():
N = 100000
XG = nx.DiGraph()
XG.add_weighted_edges_from([('s', 'u', 10), ('s', 'x', 5),
('u', 'v', 1), ('u', 'x', 2),
('v', 'y', 1), ('x', 'u', 3),
('x', 'v', 5), ('x', 'y', 2),
('y', 's', 7), ('y', 'v', 6)])
print("dijkstra_path: ", end='\t')
print(timeit.timeit(lambda: nx.dijkstra_path(XG, 's', 'v'), number=N))
print("single_source_dijkstra_path: ", end='\t')
print(timeit.timeit(lambda: nx.single_source_dijkstra_path(XG, 's', 'v'), number=N))
print("multi_source_dijkstra_path: ", end='\t')
print(timeit.timeit(lambda: nx.multi_source_dijkstra_path(XG, {'s'}, 'v'), number=N))
print("_dijkstra_multisource_checked:", end='\t')
print(timeit.timeit(lambda: _dijkstra_multisource_checked(XG, {'s'}, 'v'), number=N))
if __name__ == '__main__':
main() With this I get:
Which is an overhead of about 7% introduced by the To be honest, I did not expect the function call overhead to be that high. Sorry for being such a nuisance, but I wanted to double-check before violating DRY. |
In Python, function calls are relatively expensive. Compiled languages are very different that way. And from my perspective, wrapping the API into knots (e.g. lots of layers of functions) to avoid DRY makes the code much less readable and harder to maintain which was the goal of DRY in the first place. And besides, input checking is usually simple code that should be easy to read and not a bother to repeat. We want to avoid repeating the hard work (algorithm logic). What do you think? (I notice there are now conflicts with base due to a change in exception raising. If this is getting too much to deal with, let me know and I'll sort it out.) Thanks!! |
The API for weighted shortest paths has two triples of functions of the form
*_source_dijkstra
,*_source_dijkstra_path
,*_source_dijkstra_path_length
, where the first returns both lengths and paths, the second only the path, and the third only the length.While intuitively all three should function roughly the same, only the first version allows to specify a
target
parameter.In short, this PR
target
parameter to the*_source_dijkstra_path
and*_source_dijkstra_path_length
variantsmulti_source_dijkstra
the effective "backend" of all versions, with a newcapture_paths
flag to prevent full path computation when they are not needed.