generic.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. """
  2. Compute the shortest paths and path lengths between nodes in the graph.
  3. These algorithms work with undirected and directed graphs.
  4. """
  5. import warnings
  6. import networkx as nx
  7. __all__ = [
  8. "shortest_path",
  9. "all_shortest_paths",
  10. "shortest_path_length",
  11. "average_shortest_path_length",
  12. "has_path",
  13. ]
  14. @nx._dispatch
  15. def has_path(G, source, target):
  16. """Returns *True* if *G* has a path from *source* to *target*.
  17. Parameters
  18. ----------
  19. G : NetworkX graph
  20. source : node
  21. Starting node for path
  22. target : node
  23. Ending node for path
  24. """
  25. try:
  26. nx.shortest_path(G, source, target)
  27. except nx.NetworkXNoPath:
  28. return False
  29. return True
  30. @nx._dispatch
  31. def shortest_path(G, source=None, target=None, weight=None, method="dijkstra"):
  32. """Compute shortest paths in the graph.
  33. Parameters
  34. ----------
  35. G : NetworkX graph
  36. source : node, optional
  37. Starting node for path. If not specified, compute shortest
  38. paths for each possible starting node.
  39. target : node, optional
  40. Ending node for path. If not specified, compute shortest
  41. paths to all possible nodes.
  42. weight : None, string or function, optional (default = None)
  43. If None, every edge has weight/distance/cost 1.
  44. If a string, use this edge attribute as the edge weight.
  45. Any edge attribute not present defaults to 1.
  46. If this is a function, the weight of an edge is the value
  47. returned by the function. The function must accept exactly
  48. three positional arguments: the two endpoints of an edge and
  49. the dictionary of edge attributes for that edge.
  50. The function must return a number.
  51. method : string, optional (default = 'dijkstra')
  52. The algorithm to use to compute the path.
  53. Supported options: 'dijkstra', 'bellman-ford'.
  54. Other inputs produce a ValueError.
  55. If `weight` is None, unweighted graph methods are used, and this
  56. suggestion is ignored.
  57. Returns
  58. -------
  59. path: list or dictionary
  60. All returned paths include both the source and target in the path.
  61. If the source and target are both specified, return a single list
  62. of nodes in a shortest path from the source to the target.
  63. If only the source is specified, return a dictionary keyed by
  64. targets with a list of nodes in a shortest path from the source
  65. to one of the targets.
  66. If only the target is specified, return a dictionary keyed by
  67. sources with a list of nodes in a shortest path from one of the
  68. sources to the target.
  69. If neither the source nor target are specified return a dictionary
  70. of dictionaries with path[source][target]=[list of nodes in path].
  71. Raises
  72. ------
  73. NodeNotFound
  74. If `source` is not in `G`.
  75. ValueError
  76. If `method` is not among the supported options.
  77. Examples
  78. --------
  79. >>> G = nx.path_graph(5)
  80. >>> print(nx.shortest_path(G, source=0, target=4))
  81. [0, 1, 2, 3, 4]
  82. >>> p = nx.shortest_path(G, source=0) # target not specified
  83. >>> p[3] # shortest path from source=0 to target=3
  84. [0, 1, 2, 3]
  85. >>> p = nx.shortest_path(G, target=4) # source not specified
  86. >>> p[1] # shortest path from source=1 to target=4
  87. [1, 2, 3, 4]
  88. >>> p = nx.shortest_path(G) # source, target not specified
  89. >>> p[2][4] # shortest path from source=2 to target=4
  90. [2, 3, 4]
  91. Notes
  92. -----
  93. There may be more than one shortest path between a source and target.
  94. This returns only one of them.
  95. See Also
  96. --------
  97. all_pairs_shortest_path
  98. all_pairs_dijkstra_path
  99. all_pairs_bellman_ford_path
  100. single_source_shortest_path
  101. single_source_dijkstra_path
  102. single_source_bellman_ford_path
  103. """
  104. if method not in ("dijkstra", "bellman-ford"):
  105. # so we don't need to check in each branch later
  106. raise ValueError(f"method not supported: {method}")
  107. method = "unweighted" if weight is None else method
  108. if source is None:
  109. if target is None:
  110. msg = "shortest_path for all_pairs will return an iterator in v3.3"
  111. warnings.warn(msg, DeprecationWarning)
  112. # Find paths between all pairs.
  113. if method == "unweighted":
  114. paths = dict(nx.all_pairs_shortest_path(G))
  115. elif method == "dijkstra":
  116. paths = dict(nx.all_pairs_dijkstra_path(G, weight=weight))
  117. else: # method == 'bellman-ford':
  118. paths = dict(nx.all_pairs_bellman_ford_path(G, weight=weight))
  119. else:
  120. # Find paths from all nodes co-accessible to the target.
  121. if G.is_directed():
  122. G = G.reverse(copy=False)
  123. if method == "unweighted":
  124. paths = nx.single_source_shortest_path(G, target)
  125. elif method == "dijkstra":
  126. paths = nx.single_source_dijkstra_path(G, target, weight=weight)
  127. else: # method == 'bellman-ford':
  128. paths = nx.single_source_bellman_ford_path(G, target, weight=weight)
  129. # Now flip the paths so they go from a source to the target.
  130. for target in paths:
  131. paths[target] = list(reversed(paths[target]))
  132. else:
  133. if target is None:
  134. # Find paths to all nodes accessible from the source.
  135. if method == "unweighted":
  136. paths = nx.single_source_shortest_path(G, source)
  137. elif method == "dijkstra":
  138. paths = nx.single_source_dijkstra_path(G, source, weight=weight)
  139. else: # method == 'bellman-ford':
  140. paths = nx.single_source_bellman_ford_path(G, source, weight=weight)
  141. else:
  142. # Find shortest source-target path.
  143. if method == "unweighted":
  144. paths = nx.bidirectional_shortest_path(G, source, target)
  145. elif method == "dijkstra":
  146. _, paths = nx.bidirectional_dijkstra(G, source, target, weight)
  147. else: # method == 'bellman-ford':
  148. paths = nx.bellman_ford_path(G, source, target, weight)
  149. return paths
  150. @nx._dispatch
  151. def shortest_path_length(G, source=None, target=None, weight=None, method="dijkstra"):
  152. """Compute shortest path lengths in the graph.
  153. Parameters
  154. ----------
  155. G : NetworkX graph
  156. source : node, optional
  157. Starting node for path.
  158. If not specified, compute shortest path lengths using all nodes as
  159. source nodes.
  160. target : node, optional
  161. Ending node for path.
  162. If not specified, compute shortest path lengths using all nodes as
  163. target nodes.
  164. weight : None, string or function, optional (default = None)
  165. If None, every edge has weight/distance/cost 1.
  166. If a string, use this edge attribute as the edge weight.
  167. Any edge attribute not present defaults to 1.
  168. If this is a function, the weight of an edge is the value
  169. returned by the function. The function must accept exactly
  170. three positional arguments: the two endpoints of an edge and
  171. the dictionary of edge attributes for that edge.
  172. The function must return a number.
  173. method : string, optional (default = 'dijkstra')
  174. The algorithm to use to compute the path length.
  175. Supported options: 'dijkstra', 'bellman-ford'.
  176. Other inputs produce a ValueError.
  177. If `weight` is None, unweighted graph methods are used, and this
  178. suggestion is ignored.
  179. Returns
  180. -------
  181. length: int or iterator
  182. If the source and target are both specified, return the length of
  183. the shortest path from the source to the target.
  184. If only the source is specified, return a dict keyed by target
  185. to the shortest path length from the source to that target.
  186. If only the target is specified, return a dict keyed by source
  187. to the shortest path length from that source to the target.
  188. If neither the source nor target are specified, return an iterator
  189. over (source, dictionary) where dictionary is keyed by target to
  190. shortest path length from source to that target.
  191. Raises
  192. ------
  193. NodeNotFound
  194. If `source` is not in `G`.
  195. NetworkXNoPath
  196. If no path exists between source and target.
  197. ValueError
  198. If `method` is not among the supported options.
  199. Examples
  200. --------
  201. >>> G = nx.path_graph(5)
  202. >>> nx.shortest_path_length(G, source=0, target=4)
  203. 4
  204. >>> p = nx.shortest_path_length(G, source=0) # target not specified
  205. >>> p[4]
  206. 4
  207. >>> p = nx.shortest_path_length(G, target=4) # source not specified
  208. >>> p[0]
  209. 4
  210. >>> p = dict(nx.shortest_path_length(G)) # source,target not specified
  211. >>> p[0][4]
  212. 4
  213. Notes
  214. -----
  215. The length of the path is always 1 less than the number of nodes involved
  216. in the path since the length measures the number of edges followed.
  217. For digraphs this returns the shortest directed path length. To find path
  218. lengths in the reverse direction use G.reverse(copy=False) first to flip
  219. the edge orientation.
  220. See Also
  221. --------
  222. all_pairs_shortest_path_length
  223. all_pairs_dijkstra_path_length
  224. all_pairs_bellman_ford_path_length
  225. single_source_shortest_path_length
  226. single_source_dijkstra_path_length
  227. single_source_bellman_ford_path_length
  228. """
  229. if method not in ("dijkstra", "bellman-ford"):
  230. # so we don't need to check in each branch later
  231. raise ValueError(f"method not supported: {method}")
  232. method = "unweighted" if weight is None else method
  233. if source is None:
  234. if target is None:
  235. # Find paths between all pairs.
  236. if method == "unweighted":
  237. paths = nx.all_pairs_shortest_path_length(G)
  238. elif method == "dijkstra":
  239. paths = nx.all_pairs_dijkstra_path_length(G, weight=weight)
  240. else: # method == 'bellman-ford':
  241. paths = nx.all_pairs_bellman_ford_path_length(G, weight=weight)
  242. else:
  243. # Find paths from all nodes co-accessible to the target.
  244. if G.is_directed():
  245. G = G.reverse(copy=False)
  246. if method == "unweighted":
  247. path_length = nx.single_source_shortest_path_length
  248. paths = path_length(G, target)
  249. elif method == "dijkstra":
  250. path_length = nx.single_source_dijkstra_path_length
  251. paths = path_length(G, target, weight=weight)
  252. else: # method == 'bellman-ford':
  253. path_length = nx.single_source_bellman_ford_path_length
  254. paths = path_length(G, target, weight=weight)
  255. else:
  256. if target is None:
  257. # Find paths to all nodes accessible from the source.
  258. if method == "unweighted":
  259. paths = nx.single_source_shortest_path_length(G, source)
  260. elif method == "dijkstra":
  261. path_length = nx.single_source_dijkstra_path_length
  262. paths = path_length(G, source, weight=weight)
  263. else: # method == 'bellman-ford':
  264. path_length = nx.single_source_bellman_ford_path_length
  265. paths = path_length(G, source, weight=weight)
  266. else:
  267. # Find shortest source-target path.
  268. if method == "unweighted":
  269. p = nx.bidirectional_shortest_path(G, source, target)
  270. paths = len(p) - 1
  271. elif method == "dijkstra":
  272. paths = nx.dijkstra_path_length(G, source, target, weight)
  273. else: # method == 'bellman-ford':
  274. paths = nx.bellman_ford_path_length(G, source, target, weight)
  275. return paths
  276. def average_shortest_path_length(G, weight=None, method=None):
  277. r"""Returns the average shortest path length.
  278. The average shortest path length is
  279. .. math::
  280. a =\sum_{\substack{s,t \in V \\ s\neq t}} \frac{d(s, t)}{n(n-1)}
  281. where `V` is the set of nodes in `G`,
  282. `d(s, t)` is the shortest path from `s` to `t`,
  283. and `n` is the number of nodes in `G`.
  284. .. versionchanged:: 3.0
  285. An exception is raised for directed graphs that are not strongly
  286. connected.
  287. Parameters
  288. ----------
  289. G : NetworkX graph
  290. weight : None, string or function, optional (default = None)
  291. If None, every edge has weight/distance/cost 1.
  292. If a string, use this edge attribute as the edge weight.
  293. Any edge attribute not present defaults to 1.
  294. If this is a function, the weight of an edge is the value
  295. returned by the function. The function must accept exactly
  296. three positional arguments: the two endpoints of an edge and
  297. the dictionary of edge attributes for that edge.
  298. The function must return a number.
  299. method : string, optional (default = 'unweighted' or 'djikstra')
  300. The algorithm to use to compute the path lengths.
  301. Supported options are 'unweighted', 'dijkstra', 'bellman-ford',
  302. 'floyd-warshall' and 'floyd-warshall-numpy'.
  303. Other method values produce a ValueError.
  304. The default method is 'unweighted' if `weight` is None,
  305. otherwise the default method is 'dijkstra'.
  306. Raises
  307. ------
  308. NetworkXPointlessConcept
  309. If `G` is the null graph (that is, the graph on zero nodes).
  310. NetworkXError
  311. If `G` is not connected (or not strongly connected, in the case
  312. of a directed graph).
  313. ValueError
  314. If `method` is not among the supported options.
  315. Examples
  316. --------
  317. >>> G = nx.path_graph(5)
  318. >>> nx.average_shortest_path_length(G)
  319. 2.0
  320. For disconnected graphs, you can compute the average shortest path
  321. length for each component
  322. >>> G = nx.Graph([(1, 2), (3, 4)])
  323. >>> for C in (G.subgraph(c).copy() for c in nx.connected_components(G)):
  324. ... print(nx.average_shortest_path_length(C))
  325. 1.0
  326. 1.0
  327. """
  328. single_source_methods = ["unweighted", "dijkstra", "bellman-ford"]
  329. all_pairs_methods = ["floyd-warshall", "floyd-warshall-numpy"]
  330. supported_methods = single_source_methods + all_pairs_methods
  331. if method is None:
  332. method = "unweighted" if weight is None else "dijkstra"
  333. if method not in supported_methods:
  334. raise ValueError(f"method not supported: {method}")
  335. n = len(G)
  336. # For the special case of the null graph, raise an exception, since
  337. # there are no paths in the null graph.
  338. if n == 0:
  339. msg = (
  340. "the null graph has no paths, thus there is no average"
  341. "shortest path length"
  342. )
  343. raise nx.NetworkXPointlessConcept(msg)
  344. # For the special case of the trivial graph, return zero immediately.
  345. if n == 1:
  346. return 0
  347. # Shortest path length is undefined if the graph is not strongly connected.
  348. if G.is_directed() and not nx.is_strongly_connected(G):
  349. raise nx.NetworkXError("Graph is not strongly connected.")
  350. # Shortest path length is undefined if the graph is not connected.
  351. if not G.is_directed() and not nx.is_connected(G):
  352. raise nx.NetworkXError("Graph is not connected.")
  353. # Compute all-pairs shortest paths.
  354. def path_length(v):
  355. if method == "unweighted":
  356. return nx.single_source_shortest_path_length(G, v)
  357. elif method == "dijkstra":
  358. return nx.single_source_dijkstra_path_length(G, v, weight=weight)
  359. elif method == "bellman-ford":
  360. return nx.single_source_bellman_ford_path_length(G, v, weight=weight)
  361. if method in single_source_methods:
  362. # Sum the distances for each (ordered) pair of source and target node.
  363. s = sum(l for u in G for l in path_length(u).values())
  364. else:
  365. if method == "floyd-warshall":
  366. all_pairs = nx.floyd_warshall(G, weight=weight)
  367. s = sum(sum(t.values()) for t in all_pairs.values())
  368. elif method == "floyd-warshall-numpy":
  369. s = nx.floyd_warshall_numpy(G, weight=weight).sum()
  370. return s / (n * (n - 1))
  371. def all_shortest_paths(G, source, target, weight=None, method="dijkstra"):
  372. """Compute all shortest simple paths in the graph.
  373. Parameters
  374. ----------
  375. G : NetworkX graph
  376. source : node
  377. Starting node for path.
  378. target : node
  379. Ending node for path.
  380. weight : None, string or function, optional (default = None)
  381. If None, every edge has weight/distance/cost 1.
  382. If a string, use this edge attribute as the edge weight.
  383. Any edge attribute not present defaults to 1.
  384. If this is a function, the weight of an edge is the value
  385. returned by the function. The function must accept exactly
  386. three positional arguments: the two endpoints of an edge and
  387. the dictionary of edge attributes for that edge.
  388. The function must return a number.
  389. method : string, optional (default = 'dijkstra')
  390. The algorithm to use to compute the path lengths.
  391. Supported options: 'dijkstra', 'bellman-ford'.
  392. Other inputs produce a ValueError.
  393. If `weight` is None, unweighted graph methods are used, and this
  394. suggestion is ignored.
  395. Returns
  396. -------
  397. paths : generator of lists
  398. A generator of all paths between source and target.
  399. Raises
  400. ------
  401. ValueError
  402. If `method` is not among the supported options.
  403. NetworkXNoPath
  404. If `target` cannot be reached from `source`.
  405. Examples
  406. --------
  407. >>> G = nx.Graph()
  408. >>> nx.add_path(G, [0, 1, 2])
  409. >>> nx.add_path(G, [0, 10, 2])
  410. >>> print([p for p in nx.all_shortest_paths(G, source=0, target=2)])
  411. [[0, 1, 2], [0, 10, 2]]
  412. Notes
  413. -----
  414. There may be many shortest paths between the source and target. If G
  415. contains zero-weight cycles, this function will not produce all shortest
  416. paths because doing so would produce infinitely many paths of unbounded
  417. length -- instead, we only produce the shortest simple paths.
  418. See Also
  419. --------
  420. shortest_path
  421. single_source_shortest_path
  422. all_pairs_shortest_path
  423. """
  424. method = "unweighted" if weight is None else method
  425. if method == "unweighted":
  426. pred = nx.predecessor(G, source)
  427. elif method == "dijkstra":
  428. pred, dist = nx.dijkstra_predecessor_and_distance(G, source, weight=weight)
  429. elif method == "bellman-ford":
  430. pred, dist = nx.bellman_ford_predecessor_and_distance(G, source, weight=weight)
  431. else:
  432. raise ValueError(f"method not supported: {method}")
  433. return _build_paths_from_predecessors({source}, target, pred)
  434. def _build_paths_from_predecessors(sources, target, pred):
  435. """Compute all simple paths to target, given the predecessors found in
  436. pred, terminating when any source in sources is found.
  437. Parameters
  438. ----------
  439. sources : set
  440. Starting nodes for path.
  441. target : node
  442. Ending node for path.
  443. pred : dict
  444. A dictionary of predecessor lists, keyed by node
  445. Returns
  446. -------
  447. paths : generator of lists
  448. A generator of all paths between source and target.
  449. Raises
  450. ------
  451. NetworkXNoPath
  452. If `target` cannot be reached from `source`.
  453. Notes
  454. -----
  455. There may be many paths between the sources and target. If there are
  456. cycles among the predecessors, this function will not produce all
  457. possible paths because doing so would produce infinitely many paths
  458. of unbounded length -- instead, we only produce simple paths.
  459. See Also
  460. --------
  461. shortest_path
  462. single_source_shortest_path
  463. all_pairs_shortest_path
  464. all_shortest_paths
  465. bellman_ford_path
  466. """
  467. if target not in pred:
  468. raise nx.NetworkXNoPath(f"Target {target} cannot be reached from given sources")
  469. seen = {target}
  470. stack = [[target, 0]]
  471. top = 0
  472. while top >= 0:
  473. node, i = stack[top]
  474. if node in sources:
  475. yield [p for p, n in reversed(stack[: top + 1])]
  476. if len(pred[node]) > i:
  477. stack[top][1] = i + 1
  478. next = pred[node][i]
  479. if next in seen:
  480. continue
  481. else:
  482. seen.add(next)
  483. top += 1
  484. if top == len(stack):
  485. stack.append([next, 0])
  486. else:
  487. stack[top][:] = [next, 0]
  488. else:
  489. seen.discard(node)
  490. top -= 1