simple_paths.py 30 KB

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