1from heapq import heappop, heappush
2from itertools import count
3
4import networkx as nx
5from networkx.algorithms.shortest_paths.weighted import _weight_function
6from networkx.utils import not_implemented_for, pairwise
7
8__all__ = [
9 "all_simple_paths",
10 "is_simple_path",
11 "shortest_simple_paths",
12 "all_simple_edge_paths",
13]
14
15
16@nx._dispatchable
17def is_simple_path(G, nodes):
18 """Returns True if and only if `nodes` form a simple path in `G`.
19
20 A *simple path* in a graph is a nonempty sequence of nodes in which
21 no node appears more than once in the sequence, and each adjacent
22 pair of nodes in the sequence is adjacent in the graph.
23
24 Parameters
25 ----------
26 G : graph
27 A NetworkX graph.
28 nodes : list
29 A list of one or more nodes in the graph `G`.
30
31 Returns
32 -------
33 bool
34 Whether the given list of nodes represents a simple path in `G`.
35
36 Notes
37 -----
38 An empty list of nodes is not a path but a list of one node is a
39 path. Here's an explanation why.
40
41 This function operates on *node paths*. One could also consider
42 *edge paths*. There is a bijection between node paths and edge
43 paths.
44
45 The *length of a path* is the number of edges in the path, so a list
46 of nodes of length *n* corresponds to a path of length *n* - 1.
47 Thus the smallest edge path would be a list of zero edges, the empty
48 path. This corresponds to a list of one node.
49
50 To convert between a node path and an edge path, you can use code
51 like the following::
52
53 >>> from networkx.utils import pairwise
54 >>> nodes = [0, 1, 2, 3]
55 >>> edges = list(pairwise(nodes))
56 >>> edges
57 [(0, 1), (1, 2), (2, 3)]
58 >>> nodes = [edges[0][0]] + [v for u, v in edges]
59 >>> nodes
60 [0, 1, 2, 3]
61
62 Examples
63 --------
64 >>> G = nx.cycle_graph(4)
65 >>> nx.is_simple_path(G, [2, 3, 0])
66 True
67 >>> nx.is_simple_path(G, [0, 2])
68 False
69
70 """
71 # The empty list is not a valid path. Could also return
72 # NetworkXPointlessConcept here.
73 if len(nodes) == 0:
74 return False
75
76 # If the list is a single node, just check that the node is actually
77 # in the graph.
78 if len(nodes) == 1:
79 return nodes[0] in G
80
81 # check that all nodes in the list are in the graph, if at least one
82 # is not in the graph, then this is not a simple path
83 if not all(n in G for n in nodes):
84 return False
85
86 # If the list contains repeated nodes, then it's not a simple path
87 if len(set(nodes)) != len(nodes):
88 return False
89
90 # Test that each adjacent pair of nodes is adjacent.
91 return all(v in G[u] for u, v in pairwise(nodes))
92
93
94@nx._dispatchable
95def all_simple_paths(G, source, target, cutoff=None):
96 """Generate all simple paths in the graph G from source to target.
97
98 A simple path is a path with no repeated nodes.
99
100 Parameters
101 ----------
102 G : NetworkX graph
103
104 source : node
105 Starting node for path
106
107 target : nodes
108 Single node or iterable of nodes at which to end path
109
110 cutoff : integer, optional
111 Depth to stop the search. Only paths of length <= cutoff are returned.
112
113 Returns
114 -------
115 path_generator: generator
116 A generator that produces lists of simple paths. If there are no paths
117 between the source and target within the given cutoff the generator
118 produces no output. If it is possible to traverse the same sequence of
119 nodes in multiple ways, namely through parallel edges, then it will be
120 returned multiple times (once for each viable edge combination).
121
122 Examples
123 --------
124 This iterator generates lists of nodes::
125
126 >>> G = nx.complete_graph(4)
127 >>> for path in nx.all_simple_paths(G, source=0, target=3):
128 ... print(path)
129 ...
130 [0, 1, 2, 3]
131 [0, 1, 3]
132 [0, 2, 1, 3]
133 [0, 2, 3]
134 [0, 3]
135
136 You can generate only those paths that are shorter than a certain
137 length by using the `cutoff` keyword argument::
138
139 >>> paths = nx.all_simple_paths(G, source=0, target=3, cutoff=2)
140 >>> print(list(paths))
141 [[0, 1, 3], [0, 2, 3], [0, 3]]
142
143 To get each path as the corresponding list of edges, you can use the
144 :func:`networkx.utils.pairwise` helper function::
145
146 >>> paths = nx.all_simple_paths(G, source=0, target=3)
147 >>> for path in map(nx.utils.pairwise, paths):
148 ... print(list(path))
149 [(0, 1), (1, 2), (2, 3)]
150 [(0, 1), (1, 3)]
151 [(0, 2), (2, 1), (1, 3)]
152 [(0, 2), (2, 3)]
153 [(0, 3)]
154
155 Pass an iterable of nodes as target to generate all paths ending in any of several nodes::
156
157 >>> G = nx.complete_graph(4)
158 >>> for path in nx.all_simple_paths(G, source=0, target=[3, 2]):
159 ... print(path)
160 ...
161 [0, 1, 2]
162 [0, 1, 2, 3]
163 [0, 1, 3]
164 [0, 1, 3, 2]
165 [0, 2]
166 [0, 2, 1, 3]
167 [0, 2, 3]
168 [0, 3]
169 [0, 3, 1, 2]
170 [0, 3, 2]
171
172 The singleton path from ``source`` to itself is considered a simple path and is
173 included in the results:
174
175 >>> G = nx.empty_graph(5)
176 >>> list(nx.all_simple_paths(G, source=0, target=0))
177 [[0]]
178
179 >>> G = nx.path_graph(3)
180 >>> list(nx.all_simple_paths(G, source=0, target={0, 1, 2}))
181 [[0], [0, 1], [0, 1, 2]]
182
183 Iterate over each path from the root nodes to the leaf nodes in a
184 directed acyclic graph using a functional programming approach::
185
186 >>> from itertools import chain
187 >>> from itertools import product
188 >>> from itertools import starmap
189 >>> from functools import partial
190 >>>
191 >>> chaini = chain.from_iterable
192 >>>
193 >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
194 >>> roots = (v for v, d in G.in_degree() if d == 0)
195 >>> leaves = (v for v, d in G.out_degree() if d == 0)
196 >>> all_paths = partial(nx.all_simple_paths, G)
197 >>> list(chaini(starmap(all_paths, product(roots, leaves))))
198 [[0, 1, 2], [0, 3, 2]]
199
200 The same list computed using an iterative approach::
201
202 >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
203 >>> roots = (v for v, d in G.in_degree() if d == 0)
204 >>> leaves = (v for v, d in G.out_degree() if d == 0)
205 >>> all_paths = []
206 >>> for root in roots:
207 ... for leaf in leaves:
208 ... paths = nx.all_simple_paths(G, root, leaf)
209 ... all_paths.extend(paths)
210 >>> all_paths
211 [[0, 1, 2], [0, 3, 2]]
212
213 Iterate over each path from the root nodes to the leaf nodes in a
214 directed acyclic graph passing all leaves together to avoid unnecessary
215 compute::
216
217 >>> G = nx.DiGraph([(0, 1), (2, 1), (1, 3), (1, 4)])
218 >>> roots = (v for v, d in G.in_degree() if d == 0)
219 >>> leaves = [v for v, d in G.out_degree() if d == 0]
220 >>> all_paths = []
221 >>> for root in roots:
222 ... paths = nx.all_simple_paths(G, root, leaves)
223 ... all_paths.extend(paths)
224 >>> all_paths
225 [[0, 1, 3], [0, 1, 4], [2, 1, 3], [2, 1, 4]]
226
227 If parallel edges offer multiple ways to traverse a given sequence of
228 nodes, this sequence of nodes will be returned multiple times:
229
230 >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (1, 2)])
231 >>> list(nx.all_simple_paths(G, 0, 2))
232 [[0, 1, 2], [0, 1, 2]]
233
234 Notes
235 -----
236 This algorithm uses a modified depth-first search to generate the
237 paths [1]_. A single path can be found in $O(V+E)$ time but the
238 number of simple paths in a graph can be very large, e.g. $O(n!)$ in
239 the complete graph of order $n$.
240
241 This function does not check that a path exists between `source` and
242 `target`. For large graphs, this may result in very long runtimes.
243 Consider using `has_path` to check that a path exists between `source` and
244 `target` before calling this function on large graphs.
245
246 References
247 ----------
248 .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
249 Addison Wesley Professional, 3rd ed., 2001.
250
251 See Also
252 --------
253 all_shortest_paths, shortest_path, has_path
254
255 """
256 for edge_path in all_simple_edge_paths(G, source, target, cutoff):
257 yield [source] + [edge[1] for edge in edge_path]
258
259
260@nx._dispatchable
261def all_simple_edge_paths(G, source, target, cutoff=None):
262 """Generate lists of edges for all simple paths in G from source to target.
263
264 A simple path is a path with no repeated nodes.
265
266 Parameters
267 ----------
268 G : NetworkX graph
269
270 source : node
271 Starting node for path
272
273 target : nodes
274 Single node or iterable of nodes at which to end path
275
276 cutoff : integer, optional
277 Depth to stop the search. Only paths of length <= cutoff are returned.
278
279 Returns
280 -------
281 path_generator: generator
282 A generator that produces lists of simple paths. If there are no paths
283 between the source and target within the given cutoff the generator
284 produces no output.
285 For multigraphs, the list of edges have elements of the form `(u,v,k)`.
286 Where `k` corresponds to the edge key.
287
288 Examples
289 --------
290
291 Print the simple path edges of a Graph::
292
293 >>> g = nx.Graph([(1, 2), (2, 4), (1, 3), (3, 4)])
294 >>> for path in sorted(nx.all_simple_edge_paths(g, 1, 4)):
295 ... print(path)
296 [(1, 2), (2, 4)]
297 [(1, 3), (3, 4)]
298
299 Print the simple path edges of a MultiGraph. Returned edges come with
300 their associated keys::
301
302 >>> mg = nx.MultiGraph()
303 >>> mg.add_edge(1, 2, key="k0")
304 'k0'
305 >>> mg.add_edge(1, 2, key="k1")
306 'k1'
307 >>> mg.add_edge(2, 3, key="k0")
308 'k0'
309 >>> for path in sorted(nx.all_simple_edge_paths(mg, 1, 3)):
310 ... print(path)
311 [(1, 2, 'k0'), (2, 3, 'k0')]
312 [(1, 2, 'k1'), (2, 3, 'k0')]
313
314 When ``source`` is one of the targets, the empty path starting and ending at
315 ``source`` without traversing any edge is considered a valid simple edge path
316 and is included in the results:
317
318 >>> G = nx.Graph()
319 >>> G.add_node(0)
320 >>> paths = list(nx.all_simple_edge_paths(G, 0, 0))
321 >>> for path in paths:
322 ... print(path)
323 []
324 >>> len(paths)
325 1
326
327
328 Notes
329 -----
330 This algorithm uses a modified depth-first search to generate the
331 paths [1]_. A single path can be found in $O(V+E)$ time but the
332 number of simple paths in a graph can be very large, e.g. $O(n!)$ in
333 the complete graph of order $n$.
334
335 References
336 ----------
337 .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
338 Addison Wesley Professional, 3rd ed., 2001.
339
340 See Also
341 --------
342 all_shortest_paths, shortest_path, all_simple_paths
343
344 """
345 if source not in G:
346 raise nx.NodeNotFound(f"source node {source} not in graph")
347
348 if target in G:
349 targets = {target}
350 else:
351 try:
352 targets = set(target)
353 except TypeError as err:
354 raise nx.NodeNotFound(f"target node {target} not in graph") from err
355
356 cutoff = cutoff if cutoff is not None else len(G) - 1
357
358 if cutoff >= 0 and targets:
359 yield from _all_simple_edge_paths(G, source, targets, cutoff)
360
361
362def _all_simple_edge_paths(G, source, targets, cutoff):
363 # We simulate recursion with a stack, keeping the current path being explored
364 # and the outgoing edge iterators at each point in the stack.
365 # To avoid unnecessary checks, the loop is structured in a way such that a path
366 # is considered for yielding only after a new node/edge is added.
367 # We bootstrap the search by adding a dummy iterator to the stack that only yields
368 # a dummy edge to source (so that the trivial path has a chance of being included).
369
370 get_edges = (
371 (lambda node: G.edges(node, keys=True))
372 if G.is_multigraph()
373 else (lambda node: G.edges(node))
374 )
375
376 # The current_path is a dictionary that maps nodes in the path to the edge that was
377 # used to enter that node (instead of a list of edges) because we want both a fast
378 # membership test for nodes in the path and the preservation of insertion order.
379 current_path = {None: None}
380 stack = [iter([(None, source)])]
381
382 while stack:
383 # 1. Try to extend the current path.
384 next_edge = next((e for e in stack[-1] if e[1] not in current_path), None)
385 if next_edge is None:
386 # All edges of the last node in the current path have been explored.
387 stack.pop()
388 current_path.popitem()
389 continue
390 previous_node, next_node, *_ = next_edge
391
392 # 2. Check if we've reached a target.
393 if next_node in targets:
394 yield (list(current_path.values()) + [next_edge])[2:] # remove dummy edge
395
396 # 3. Only expand the search through the next node if it makes sense.
397 if len(current_path) - 1 < cutoff and (
398 targets - current_path.keys() - {next_node}
399 ):
400 current_path[next_node] = next_edge
401 stack.append(iter(get_edges(next_node)))
402
403
404@not_implemented_for("multigraph")
405@nx._dispatchable(edge_attrs="weight")
406def shortest_simple_paths(G, source, target, weight=None):
407 """Generate all simple paths in the graph G from source to target,
408 starting from shortest ones.
409
410 A simple path is a path with no repeated nodes.
411
412 If a weighted shortest path search is to be used, no negative weights
413 are allowed.
414
415 Parameters
416 ----------
417 G : NetworkX graph
418
419 source : node
420 Starting node for path
421
422 target : node
423 Ending node for path
424
425 weight : string or function
426 If it is a string, it is the name of the edge attribute to be
427 used as a weight.
428
429 If it is a function, the weight of an edge is the value returned
430 by the function. The function must accept exactly three positional
431 arguments: the two endpoints of an edge and the dictionary of edge
432 attributes for that edge. The function must return a number or None.
433 The weight function can be used to hide edges by returning None.
434 So ``weight = lambda u, v, d: 1 if d['color']=="red" else None``
435 will find the shortest red path.
436
437 If None all edges are considered to have unit weight. Default
438 value None.
439
440 Returns
441 -------
442 path_generator: generator
443 A generator that produces lists of simple paths, in order from
444 shortest to longest.
445
446 Raises
447 ------
448 NetworkXNoPath
449 If no path exists between source and target.
450
451 NetworkXError
452 If source or target nodes are not in the input graph.
453
454 NetworkXNotImplemented
455 If the input graph is a Multi[Di]Graph.
456
457 Examples
458 --------
459
460 >>> G = nx.cycle_graph(7)
461 >>> paths = list(nx.shortest_simple_paths(G, 0, 3))
462 >>> print(paths)
463 [[0, 1, 2, 3], [0, 6, 5, 4, 3]]
464
465 You can use this function to efficiently compute the k shortest/best
466 paths between two nodes.
467
468 >>> from itertools import islice
469 >>> def k_shortest_paths(G, source, target, k, weight=None):
470 ... return list(
471 ... islice(nx.shortest_simple_paths(G, source, target, weight=weight), k)
472 ... )
473 >>> for path in k_shortest_paths(G, 0, 3, 2):
474 ... print(path)
475 [0, 1, 2, 3]
476 [0, 6, 5, 4, 3]
477
478 Notes
479 -----
480 This procedure is based on algorithm by Jin Y. Yen [1]_. Finding
481 the first $K$ paths requires $O(KN^3)$ operations.
482
483 See Also
484 --------
485 all_shortest_paths
486 shortest_path
487 all_simple_paths
488
489 References
490 ----------
491 .. [1] Jin Y. Yen, "Finding the K Shortest Loopless Paths in a
492 Network", Management Science, Vol. 17, No. 11, Theory Series
493 (Jul., 1971), pp. 712-716.
494
495 """
496 if source not in G:
497 raise nx.NodeNotFound(f"source node {source} not in graph")
498
499 if target not in G:
500 raise nx.NodeNotFound(f"target node {target} not in graph")
501
502 if weight is None:
503 length_func = len
504 shortest_path_func = _bidirectional_shortest_path
505 else:
506 wt = _weight_function(G, weight)
507
508 def length_func(path):
509 return sum(
510 wt(u, v, G.get_edge_data(u, v)) for (u, v) in zip(path, path[1:])
511 )
512
513 shortest_path_func = _bidirectional_dijkstra
514
515 listA = []
516 listB = PathBuffer()
517 prev_path = None
518 while True:
519 if not prev_path:
520 length, path = shortest_path_func(G, source, target, weight=weight)
521 listB.push(length, path)
522 else:
523 ignore_nodes = set()
524 ignore_edges = set()
525 for i in range(1, len(prev_path)):
526 root = prev_path[:i]
527 root_length = length_func(root)
528 for path in listA:
529 if path[:i] == root:
530 ignore_edges.add((path[i - 1], path[i]))
531 try:
532 length, spur = shortest_path_func(
533 G,
534 root[-1],
535 target,
536 ignore_nodes=ignore_nodes,
537 ignore_edges=ignore_edges,
538 weight=weight,
539 )
540 path = root[:-1] + spur
541 listB.push(root_length + length, path)
542 except nx.NetworkXNoPath:
543 pass
544 ignore_nodes.add(root[-1])
545
546 if listB:
547 path = listB.pop()
548 yield path
549 listA.append(path)
550 prev_path = path
551 else:
552 break
553
554
555class PathBuffer:
556 def __init__(self):
557 self.paths = set()
558 self.sortedpaths = []
559 self.counter = count()
560
561 def __len__(self):
562 return len(self.sortedpaths)
563
564 def push(self, cost, path):
565 hashable_path = tuple(path)
566 if hashable_path not in self.paths:
567 heappush(self.sortedpaths, (cost, next(self.counter), path))
568 self.paths.add(hashable_path)
569
570 def pop(self):
571 (cost, num, path) = heappop(self.sortedpaths)
572 hashable_path = tuple(path)
573 self.paths.remove(hashable_path)
574 return path
575
576
577def _bidirectional_shortest_path(
578 G, source, target, ignore_nodes=None, ignore_edges=None, weight=None
579):
580 """Returns the shortest path between source and target ignoring
581 nodes and edges in the containers ignore_nodes and ignore_edges.
582
583 This is a custom modification of the standard bidirectional shortest
584 path implementation at networkx.algorithms.unweighted
585
586 Parameters
587 ----------
588 G : NetworkX graph
589
590 source : node
591 starting node for path
592
593 target : node
594 ending node for path
595
596 ignore_nodes : container of nodes
597 nodes to ignore, optional
598
599 ignore_edges : container of edges
600 edges to ignore, optional
601
602 weight : None
603 This function accepts a weight argument for convenience of
604 shortest_simple_paths function. It will be ignored.
605
606 Returns
607 -------
608 path: list
609 List of nodes in a path from source to target.
610
611 Raises
612 ------
613 NetworkXNoPath
614 If no path exists between source and target.
615
616 See Also
617 --------
618 shortest_path
619
620 """
621 # call helper to do the real work
622 results = _bidirectional_pred_succ(G, source, target, ignore_nodes, ignore_edges)
623 pred, succ, w = results
624
625 # build path from pred+w+succ
626 path = []
627 # from w to target
628 while w is not None:
629 path.append(w)
630 w = succ[w]
631 # from source to w
632 w = pred[path[0]]
633 while w is not None:
634 path.insert(0, w)
635 w = pred[w]
636
637 return len(path), path
638
639
640def _bidirectional_pred_succ(G, source, target, ignore_nodes=None, ignore_edges=None):
641 """Bidirectional shortest path helper.
642 Returns (pred,succ,w) where
643 pred is a dictionary of predecessors from w to the source, and
644 succ is a dictionary of successors from w to the target.
645 """
646 # does BFS from both source and target and meets in the middle
647 if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
648 raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
649 if target == source:
650 return ({target: None}, {source: None}, source)
651
652 # handle either directed or undirected
653 if G.is_directed():
654 Gpred = G.predecessors
655 Gsucc = G.successors
656 else:
657 Gpred = G.neighbors
658 Gsucc = G.neighbors
659
660 # support optional nodes filter
661 if ignore_nodes:
662
663 def filter_iter(nodes):
664 def iterate(v):
665 for w in nodes(v):
666 if w not in ignore_nodes:
667 yield w
668
669 return iterate
670
671 Gpred = filter_iter(Gpred)
672 Gsucc = filter_iter(Gsucc)
673
674 # support optional edges filter
675 if ignore_edges:
676 if G.is_directed():
677
678 def filter_pred_iter(pred_iter):
679 def iterate(v):
680 for w in pred_iter(v):
681 if (w, v) not in ignore_edges:
682 yield w
683
684 return iterate
685
686 def filter_succ_iter(succ_iter):
687 def iterate(v):
688 for w in succ_iter(v):
689 if (v, w) not in ignore_edges:
690 yield w
691
692 return iterate
693
694 Gpred = filter_pred_iter(Gpred)
695 Gsucc = filter_succ_iter(Gsucc)
696
697 else:
698
699 def filter_iter(nodes):
700 def iterate(v):
701 for w in nodes(v):
702 if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
703 yield w
704
705 return iterate
706
707 Gpred = filter_iter(Gpred)
708 Gsucc = filter_iter(Gsucc)
709
710 # predecessor and successors in search
711 pred = {source: None}
712 succ = {target: None}
713
714 # initialize fringes, start with forward
715 forward_fringe = [source]
716 reverse_fringe = [target]
717
718 while forward_fringe and reverse_fringe:
719 if len(forward_fringe) <= len(reverse_fringe):
720 this_level = forward_fringe
721 forward_fringe = []
722 for v in this_level:
723 for w in Gsucc(v):
724 if w not in pred:
725 forward_fringe.append(w)
726 pred[w] = v
727 if w in succ:
728 # found path
729 return pred, succ, w
730 else:
731 this_level = reverse_fringe
732 reverse_fringe = []
733 for v in this_level:
734 for w in Gpred(v):
735 if w not in succ:
736 succ[w] = v
737 reverse_fringe.append(w)
738 if w in pred:
739 # found path
740 return pred, succ, w
741
742 raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
743
744
745def _bidirectional_dijkstra(
746 G, source, target, weight="weight", ignore_nodes=None, ignore_edges=None
747):
748 """Dijkstra's algorithm for shortest paths using bidirectional search.
749
750 This function returns the shortest path between source and target
751 ignoring nodes and edges in the containers ignore_nodes and
752 ignore_edges.
753
754 This is a custom modification of the standard Dijkstra bidirectional
755 shortest path implementation at networkx.algorithms.weighted
756
757 Parameters
758 ----------
759 G : NetworkX graph
760
761 source : node
762 Starting node.
763
764 target : node
765 Ending node.
766
767 weight: string, function, optional (default='weight')
768 Edge data key or weight function corresponding to the edge weight
769 If this is a function, the weight of an edge is the value
770 returned by the function. The function must accept exactly three
771 positional arguments: the two endpoints of an edge and the
772 dictionary of edge attributes for that edge. The function must
773 return a number or None to indicate a hidden edge.
774
775 ignore_nodes : container of nodes
776 nodes to ignore, optional
777
778 ignore_edges : container of edges
779 edges to ignore, optional
780
781 Returns
782 -------
783 length : number
784 Shortest path length.
785
786 Returns a tuple of two dictionaries keyed by node.
787 The first dictionary stores distance from the source.
788 The second stores the path from the source to that node.
789
790 Raises
791 ------
792 NetworkXNoPath
793 If no path exists between source and target.
794
795 Notes
796 -----
797 Edge weight attributes must be numerical.
798 Distances are calculated as sums of weighted edges traversed.
799
800 The weight function can be used to hide edges by returning None.
801 So ``weight = lambda u, v, d: 1 if d['color']=="red" else None``
802 will find the shortest red path.
803
804 In practice bidirectional Dijkstra is much more than twice as fast as
805 ordinary Dijkstra.
806
807 Ordinary Dijkstra expands nodes in a sphere-like manner from the
808 source. The radius of this sphere will eventually be the length
809 of the shortest path. Bidirectional Dijkstra will expand nodes
810 from both the source and the target, making two spheres of half
811 this radius. Volume of the first sphere is pi*r*r while the
812 others are 2*pi*r/2*r/2, making up half the volume.
813
814 This algorithm is not guaranteed to work if edge weights
815 are negative or are floating point numbers
816 (overflows and roundoff errors can cause problems).
817
818 See Also
819 --------
820 shortest_path
821 shortest_path_length
822 """
823 if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
824 raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
825 if source == target:
826 if source not in G:
827 raise nx.NodeNotFound(f"Node {source} not in graph")
828 return (0, [source])
829
830 # handle either directed or undirected
831 if G.is_directed():
832 Gpred = G.predecessors
833 Gsucc = G.successors
834 else:
835 Gpred = G.neighbors
836 Gsucc = G.neighbors
837
838 # support optional nodes filter
839 if ignore_nodes:
840
841 def filter_iter(nodes):
842 def iterate(v):
843 for w in nodes(v):
844 if w not in ignore_nodes:
845 yield w
846
847 return iterate
848
849 Gpred = filter_iter(Gpred)
850 Gsucc = filter_iter(Gsucc)
851
852 # support optional edges filter
853 if ignore_edges:
854 if G.is_directed():
855
856 def filter_pred_iter(pred_iter):
857 def iterate(v):
858 for w in pred_iter(v):
859 if (w, v) not in ignore_edges:
860 yield w
861
862 return iterate
863
864 def filter_succ_iter(succ_iter):
865 def iterate(v):
866 for w in succ_iter(v):
867 if (v, w) not in ignore_edges:
868 yield w
869
870 return iterate
871
872 Gpred = filter_pred_iter(Gpred)
873 Gsucc = filter_succ_iter(Gsucc)
874
875 else:
876
877 def filter_iter(nodes):
878 def iterate(v):
879 for w in nodes(v):
880 if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
881 yield w
882
883 return iterate
884
885 Gpred = filter_iter(Gpred)
886 Gsucc = filter_iter(Gsucc)
887
888 wt = _weight_function(G, weight)
889 # Init: Forward Backward
890 dists = [{}, {}] # dictionary of final distances
891 paths = [{source: [source]}, {target: [target]}] # dictionary of paths
892 fringe = [[], []] # heap of (distance, node) tuples for
893 # extracting next node to expand
894 seen = [{source: 0}, {target: 0}] # dictionary of distances to
895 # nodes seen
896 c = count()
897 # initialize fringe heap
898 heappush(fringe[0], (0, next(c), source))
899 heappush(fringe[1], (0, next(c), target))
900 # neighs for extracting correct neighbor information
901 neighs = [Gsucc, Gpred]
902 # variables to hold shortest discovered path
903 # finaldist = 1e30000
904 finalpath = []
905 dir = 1
906 while fringe[0] and fringe[1]:
907 # choose direction
908 # dir == 0 is forward direction and dir == 1 is back
909 dir = 1 - dir
910 # extract closest to expand
911 (dist, _, v) = heappop(fringe[dir])
912 if v in dists[dir]:
913 # Shortest path to v has already been found
914 continue
915 # update distance
916 dists[dir][v] = dist # equal to seen[dir][v]
917 if v in dists[1 - dir]:
918 # if we have scanned v in both directions we are done
919 # we have now discovered the shortest path
920 return (finaldist, finalpath)
921
922 for w in neighs[dir](v):
923 if dir == 0: # forward
924 minweight = wt(v, w, G.get_edge_data(v, w))
925 else: # back, must remember to change v,w->w,v
926 minweight = wt(w, v, G.get_edge_data(w, v))
927 if minweight is None:
928 continue
929 vwLength = dists[dir][v] + minweight
930
931 if w in dists[dir]:
932 if vwLength < dists[dir][w]:
933 raise ValueError("Contradictory paths found: negative weights?")
934 elif w not in seen[dir] or vwLength < seen[dir][w]:
935 # relaxing
936 seen[dir][w] = vwLength
937 heappush(fringe[dir], (vwLength, next(c), w))
938 paths[dir][w] = paths[dir][v] + [w]
939 if w in seen[0] and w in seen[1]:
940 # see if this path is better than the already
941 # discovered shortest path
942 totaldist = seen[0][w] + seen[1][w]
943 if finalpath == [] or finaldist > totaldist:
944 finaldist = totaldist
945 revpath = paths[1][w][:]
946 revpath.reverse()
947 finalpath = paths[0][w] + revpath[1:]
948 raise nx.NetworkXNoPath(f"No path between {source} and {target}.")