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