disjoint_paths.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. """Flow based node and edge disjoint paths."""
  2. import networkx as nx
  3. # Define the default maximum flow function to use for the underlying
  4. # maximum flow computations
  5. from networkx.algorithms.flow import (
  6. edmonds_karp,
  7. preflow_push,
  8. shortest_augmenting_path,
  9. )
  10. from networkx.exception import NetworkXNoPath
  11. default_flow_func = edmonds_karp
  12. from itertools import filterfalse as _filterfalse
  13. # Functions to build auxiliary data structures.
  14. from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
  15. __all__ = ["edge_disjoint_paths", "node_disjoint_paths"]
  16. def edge_disjoint_paths(
  17. G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
  18. ):
  19. """Returns the edges disjoint paths between source and target.
  20. Edge disjoint paths are paths that do not share any edge. The
  21. number of edge disjoint paths between source and target is equal
  22. to their edge connectivity.
  23. Parameters
  24. ----------
  25. G : NetworkX graph
  26. s : node
  27. Source node for the flow.
  28. t : node
  29. Sink node for the flow.
  30. flow_func : function
  31. A function for computing the maximum flow among a pair of nodes.
  32. The function has to accept at least three parameters: a Digraph,
  33. a source node, and a target node. And return a residual network
  34. that follows NetworkX conventions (see :meth:`maximum_flow` for
  35. details). If flow_func is None, the default maximum flow function
  36. (:meth:`edmonds_karp`) is used. The choice of the default function
  37. may change from version to version and should not be relied on.
  38. Default value: None.
  39. cutoff : integer or None (default: None)
  40. Maximum number of paths to yield. If specified, the maximum flow
  41. algorithm will terminate when the flow value reaches or exceeds the
  42. cutoff. This only works for flows that support the cutoff parameter
  43. (most do) and is ignored otherwise.
  44. auxiliary : NetworkX DiGraph
  45. Auxiliary digraph to compute flow based edge connectivity. It has
  46. to have a graph attribute called mapping with a dictionary mapping
  47. node names in G and in the auxiliary digraph. If provided
  48. it will be reused instead of recreated. Default value: None.
  49. residual : NetworkX DiGraph
  50. Residual network to compute maximum flow. If provided it will be
  51. reused instead of recreated. Default value: None.
  52. Returns
  53. -------
  54. paths : generator
  55. A generator of edge independent paths.
  56. Raises
  57. ------
  58. NetworkXNoPath
  59. If there is no path between source and target.
  60. NetworkXError
  61. If source or target are not in the graph G.
  62. See also
  63. --------
  64. :meth:`node_disjoint_paths`
  65. :meth:`edge_connectivity`
  66. :meth:`maximum_flow`
  67. :meth:`edmonds_karp`
  68. :meth:`preflow_push`
  69. :meth:`shortest_augmenting_path`
  70. Examples
  71. --------
  72. We use in this example the platonic icosahedral graph, which has node
  73. edge connectivity 5, thus there are 5 edge disjoint paths between any
  74. pair of nodes.
  75. >>> G = nx.icosahedral_graph()
  76. >>> len(list(nx.edge_disjoint_paths(G, 0, 6)))
  77. 5
  78. If you need to compute edge disjoint paths on several pairs of
  79. nodes in the same graph, it is recommended that you reuse the
  80. data structures that NetworkX uses in the computation: the
  81. auxiliary digraph for edge connectivity, and the residual
  82. network for the underlying maximum flow computation.
  83. Example of how to compute edge disjoint paths among all pairs of
  84. nodes of the platonic icosahedral graph reusing the data
  85. structures.
  86. >>> import itertools
  87. >>> # You also have to explicitly import the function for
  88. >>> # building the auxiliary digraph from the connectivity package
  89. >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
  90. >>> H = build_auxiliary_edge_connectivity(G)
  91. >>> # And the function for building the residual network from the
  92. >>> # flow package
  93. >>> from networkx.algorithms.flow import build_residual_network
  94. >>> # Note that the auxiliary digraph has an edge attribute named capacity
  95. >>> R = build_residual_network(H, "capacity")
  96. >>> result = {n: {} for n in G}
  97. >>> # Reuse the auxiliary digraph and the residual network by passing them
  98. >>> # as arguments
  99. >>> for u, v in itertools.combinations(G, 2):
  100. ... k = len(list(nx.edge_disjoint_paths(G, u, v, auxiliary=H, residual=R)))
  101. ... result[u][v] = k
  102. >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
  103. True
  104. You can also use alternative flow algorithms for computing edge disjoint
  105. paths. For instance, in dense networks the algorithm
  106. :meth:`shortest_augmenting_path` will usually perform better than
  107. the default :meth:`edmonds_karp` which is faster for sparse
  108. networks with highly skewed degree distributions. Alternative flow
  109. functions have to be explicitly imported from the flow package.
  110. >>> from networkx.algorithms.flow import shortest_augmenting_path
  111. >>> len(list(nx.edge_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
  112. 5
  113. Notes
  114. -----
  115. This is a flow based implementation of edge disjoint paths. We compute
  116. the maximum flow between source and target on an auxiliary directed
  117. network. The saturated edges in the residual network after running the
  118. maximum flow algorithm correspond to edge disjoint paths between source
  119. and target in the original network. This function handles both directed
  120. and undirected graphs, and can use all flow algorithms from NetworkX flow
  121. package.
  122. """
  123. if s not in G:
  124. raise nx.NetworkXError(f"node {s} not in graph")
  125. if t not in G:
  126. raise nx.NetworkXError(f"node {t} not in graph")
  127. if flow_func is None:
  128. flow_func = default_flow_func
  129. if auxiliary is None:
  130. H = build_auxiliary_edge_connectivity(G)
  131. else:
  132. H = auxiliary
  133. # Maximum possible edge disjoint paths
  134. possible = min(H.out_degree(s), H.in_degree(t))
  135. if not possible:
  136. raise NetworkXNoPath
  137. if cutoff is None:
  138. cutoff = possible
  139. else:
  140. cutoff = min(cutoff, possible)
  141. # Compute maximum flow between source and target. Flow functions in
  142. # NetworkX return a residual network.
  143. kwargs = {
  144. "capacity": "capacity",
  145. "residual": residual,
  146. "cutoff": cutoff,
  147. "value_only": True,
  148. }
  149. if flow_func is preflow_push:
  150. del kwargs["cutoff"]
  151. if flow_func is shortest_augmenting_path:
  152. kwargs["two_phase"] = True
  153. R = flow_func(H, s, t, **kwargs)
  154. if R.graph["flow_value"] == 0:
  155. raise NetworkXNoPath
  156. # Saturated edges in the residual network form the edge disjoint paths
  157. # between source and target
  158. cutset = [
  159. (u, v)
  160. for u, v, d in R.edges(data=True)
  161. if d["capacity"] == d["flow"] and d["flow"] > 0
  162. ]
  163. # This is equivalent of what flow.utils.build_flow_dict returns, but
  164. # only for the nodes with saturated edges and without reporting 0 flows.
  165. flow_dict = {n: {} for edge in cutset for n in edge}
  166. for u, v in cutset:
  167. flow_dict[u][v] = 1
  168. # Rebuild the edge disjoint paths from the flow dictionary.
  169. paths_found = 0
  170. for v in list(flow_dict[s]):
  171. if paths_found >= cutoff:
  172. # preflow_push does not support cutoff: we have to
  173. # keep track of the paths founds and stop at cutoff.
  174. break
  175. path = [s]
  176. if v == t:
  177. path.append(v)
  178. yield path
  179. continue
  180. u = v
  181. while u != t:
  182. path.append(u)
  183. try:
  184. u, _ = flow_dict[u].popitem()
  185. except KeyError:
  186. break
  187. else:
  188. path.append(t)
  189. yield path
  190. paths_found += 1
  191. def node_disjoint_paths(
  192. G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
  193. ):
  194. r"""Computes node disjoint paths between source and target.
  195. Node disjoint paths are paths that only share their first and last
  196. nodes. The number of node independent paths between two nodes is
  197. equal to their local node connectivity.
  198. Parameters
  199. ----------
  200. G : NetworkX graph
  201. s : node
  202. Source node.
  203. t : node
  204. Target node.
  205. flow_func : function
  206. A function for computing the maximum flow among a pair of nodes.
  207. The function has to accept at least three parameters: a Digraph,
  208. a source node, and a target node. And return a residual network
  209. that follows NetworkX conventions (see :meth:`maximum_flow` for
  210. details). If flow_func is None, the default maximum flow function
  211. (:meth:`edmonds_karp`) is used. See below for details. The choice
  212. of the default function may change from version to version and
  213. should not be relied on. Default value: None.
  214. cutoff : integer or None (default: None)
  215. Maximum number of paths to yield. If specified, the maximum flow
  216. algorithm will terminate when the flow value reaches or exceeds the
  217. cutoff. This only works for flows that support the cutoff parameter
  218. (most do) and is ignored otherwise.
  219. auxiliary : NetworkX DiGraph
  220. Auxiliary digraph to compute flow based node connectivity. It has
  221. to have a graph attribute called mapping with a dictionary mapping
  222. node names in G and in the auxiliary digraph. If provided
  223. it will be reused instead of recreated. Default value: None.
  224. residual : NetworkX DiGraph
  225. Residual network to compute maximum flow. If provided it will be
  226. reused instead of recreated. Default value: None.
  227. Returns
  228. -------
  229. paths : generator
  230. Generator of node disjoint paths.
  231. Raises
  232. ------
  233. NetworkXNoPath
  234. If there is no path between source and target.
  235. NetworkXError
  236. If source or target are not in the graph G.
  237. Examples
  238. --------
  239. We use in this example the platonic icosahedral graph, which has node
  240. connectivity 5, thus there are 5 node disjoint paths between any pair
  241. of non neighbor nodes.
  242. >>> G = nx.icosahedral_graph()
  243. >>> len(list(nx.node_disjoint_paths(G, 0, 6)))
  244. 5
  245. If you need to compute node disjoint paths between several pairs of
  246. nodes in the same graph, it is recommended that you reuse the
  247. data structures that NetworkX uses in the computation: the
  248. auxiliary digraph for node connectivity and node cuts, and the
  249. residual network for the underlying maximum flow computation.
  250. Example of how to compute node disjoint paths reusing the data
  251. structures:
  252. >>> # You also have to explicitly import the function for
  253. >>> # building the auxiliary digraph from the connectivity package
  254. >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
  255. >>> H = build_auxiliary_node_connectivity(G)
  256. >>> # And the function for building the residual network from the
  257. >>> # flow package
  258. >>> from networkx.algorithms.flow import build_residual_network
  259. >>> # Note that the auxiliary digraph has an edge attribute named capacity
  260. >>> R = build_residual_network(H, "capacity")
  261. >>> # Reuse the auxiliary digraph and the residual network by passing them
  262. >>> # as arguments
  263. >>> len(list(nx.node_disjoint_paths(G, 0, 6, auxiliary=H, residual=R)))
  264. 5
  265. You can also use alternative flow algorithms for computing node disjoint
  266. paths. For instance, in dense networks the algorithm
  267. :meth:`shortest_augmenting_path` will usually perform better than
  268. the default :meth:`edmonds_karp` which is faster for sparse
  269. networks with highly skewed degree distributions. Alternative flow
  270. functions have to be explicitly imported from the flow package.
  271. >>> from networkx.algorithms.flow import shortest_augmenting_path
  272. >>> len(list(nx.node_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
  273. 5
  274. Notes
  275. -----
  276. This is a flow based implementation of node disjoint paths. We compute
  277. the maximum flow between source and target on an auxiliary directed
  278. network. The saturated edges in the residual network after running the
  279. maximum flow algorithm correspond to node disjoint paths between source
  280. and target in the original network. This function handles both directed
  281. and undirected graphs, and can use all flow algorithms from NetworkX flow
  282. package.
  283. See also
  284. --------
  285. :meth:`edge_disjoint_paths`
  286. :meth:`node_connectivity`
  287. :meth:`maximum_flow`
  288. :meth:`edmonds_karp`
  289. :meth:`preflow_push`
  290. :meth:`shortest_augmenting_path`
  291. """
  292. if s not in G:
  293. raise nx.NetworkXError(f"node {s} not in graph")
  294. if t not in G:
  295. raise nx.NetworkXError(f"node {t} not in graph")
  296. if auxiliary is None:
  297. H = build_auxiliary_node_connectivity(G)
  298. else:
  299. H = auxiliary
  300. mapping = H.graph.get("mapping", None)
  301. if mapping is None:
  302. raise nx.NetworkXError("Invalid auxiliary digraph.")
  303. # Maximum possible edge disjoint paths
  304. possible = min(H.out_degree(f"{mapping[s]}B"), H.in_degree(f"{mapping[t]}A"))
  305. if not possible:
  306. raise NetworkXNoPath
  307. if cutoff is None:
  308. cutoff = possible
  309. else:
  310. cutoff = min(cutoff, possible)
  311. kwargs = {
  312. "flow_func": flow_func,
  313. "residual": residual,
  314. "auxiliary": H,
  315. "cutoff": cutoff,
  316. }
  317. # The edge disjoint paths in the auxiliary digraph correspond to the node
  318. # disjoint paths in the original graph.
  319. paths_edges = edge_disjoint_paths(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
  320. for path in paths_edges:
  321. # Each node in the original graph maps to two nodes in auxiliary graph
  322. yield list(_unique_everseen(H.nodes[node]["id"] for node in path))
  323. def _unique_everseen(iterable):
  324. # Adapted from https://docs.python.org/3/library/itertools.html examples
  325. "List unique elements, preserving order. Remember all elements ever seen."
  326. # unique_everseen('AAAABBBCCDAABBB') --> A B C D
  327. seen = set()
  328. seen_add = seen.add
  329. for element in _filterfalse(seen.__contains__, iterable):
  330. seen_add(element)
  331. yield element