1"""
2Shortest augmenting path algorithm for maximum flow problems.
3"""
4
5from collections import deque
6
7import networkx as nx
8
9from .edmondskarp import edmonds_karp_core
10from .utils import CurrentEdge, build_residual_network
11
12__all__ = ["shortest_augmenting_path"]
13
14
15def shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff):
16 """Implementation of the shortest augmenting path algorithm."""
17 if s not in G:
18 raise nx.NetworkXError(f"node {str(s)} not in graph")
19 if t not in G:
20 raise nx.NetworkXError(f"node {str(t)} not in graph")
21 if s == t:
22 raise nx.NetworkXError("source and sink are the same node")
23
24 if residual is None:
25 R = build_residual_network(G, capacity)
26 else:
27 R = residual
28
29 R_nodes = R.nodes
30 R_pred = R.pred
31 R_succ = R.succ
32
33 # Initialize/reset the residual network.
34 for u in R:
35 for e in R_succ[u].values():
36 e["flow"] = 0
37
38 # Initialize heights of the nodes.
39 heights = {t: 0}
40 q = deque([(t, 0)])
41 while q:
42 u, height = q.popleft()
43 height += 1
44 for v, attr in R_pred[u].items():
45 if v not in heights and attr["flow"] < attr["capacity"]:
46 heights[v] = height
47 q.append((v, height))
48
49 if s not in heights:
50 # t is not reachable from s in the residual network. The maximum flow
51 # must be zero.
52 R.graph["flow_value"] = 0
53 return R
54
55 n = len(G)
56 m = R.size() / 2
57
58 # Initialize heights and 'current edge' data structures of the nodes.
59 for u in R:
60 R_nodes[u]["height"] = heights[u] if u in heights else n
61 R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u])
62
63 # Initialize counts of nodes in each level.
64 counts = [0] * (2 * n - 1)
65 for u in R:
66 counts[R_nodes[u]["height"]] += 1
67
68 inf = R.graph["inf"]
69
70 def augment(path):
71 """Augment flow along a path from s to t."""
72 # Determine the path residual capacity.
73 flow = inf
74 it = iter(path)
75 u = next(it)
76 for v in it:
77 attr = R_succ[u][v]
78 flow = min(flow, attr["capacity"] - attr["flow"])
79 u = v
80 if flow * 2 > inf:
81 raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
82 # Augment flow along the path.
83 it = iter(path)
84 u = next(it)
85 for v in it:
86 R_succ[u][v]["flow"] += flow
87 R_succ[v][u]["flow"] -= flow
88 u = v
89 return flow
90
91 def relabel(u):
92 """Relabel a node to create an admissible edge."""
93 height = n - 1
94 for v, attr in R_succ[u].items():
95 if attr["flow"] < attr["capacity"]:
96 height = min(height, R_nodes[v]["height"])
97 return height + 1
98
99 if cutoff is None:
100 cutoff = float("inf")
101
102 # Phase 1: Look for shortest augmenting paths using depth-first search.
103
104 flow_value = 0
105 path = [s]
106 u = s
107 d = n if not two_phase else int(min(m**0.5, 2 * n ** (2.0 / 3)))
108 done = R_nodes[s]["height"] >= d
109 while not done:
110 height = R_nodes[u]["height"]
111 curr_edge = R_nodes[u]["curr_edge"]
112 # Depth-first search for the next node on the path to t.
113 while True:
114 v, attr = curr_edge.get()
115 if height == R_nodes[v]["height"] + 1 and attr["flow"] < attr["capacity"]:
116 # Advance to the next node following an admissible edge.
117 path.append(v)
118 u = v
119 break
120 try:
121 curr_edge.move_to_next()
122 except StopIteration:
123 counts[height] -= 1
124 if counts[height] == 0:
125 # Gap heuristic: If relabeling causes a level to become
126 # empty, a minimum cut has been identified. The algorithm
127 # can now be terminated.
128 R.graph["flow_value"] = flow_value
129 return R
130 height = relabel(u)
131 if u == s and height >= d:
132 if not two_phase:
133 # t is disconnected from s in the residual network. No
134 # more augmenting paths exist.
135 R.graph["flow_value"] = flow_value
136 return R
137 else:
138 # t is at least d steps away from s. End of phase 1.
139 done = True
140 break
141 counts[height] += 1
142 R_nodes[u]["height"] = height
143 if u != s:
144 # After relabeling, the last edge on the path is no longer
145 # admissible. Retreat one step to look for an alternative.
146 path.pop()
147 u = path[-1]
148 break
149 if u == t:
150 # t is reached. Augment flow along the path and reset it for a new
151 # depth-first search.
152 flow_value += augment(path)
153 if flow_value >= cutoff:
154 R.graph["flow_value"] = flow_value
155 return R
156 path = [s]
157 u = s
158
159 # Phase 2: Look for shortest augmenting paths using breadth-first search.
160 flow_value += edmonds_karp_core(R, s, t, cutoff - flow_value)
161
162 R.graph["flow_value"] = flow_value
163 return R
164
165
166@nx._dispatchable(
167 edge_attrs={"capacity": float("inf")}, returns_graph=True, preserve_edge_attrs=True
168)
169def shortest_augmenting_path(
170 G,
171 s,
172 t,
173 capacity="capacity",
174 residual=None,
175 value_only=False,
176 two_phase=False,
177 cutoff=None,
178):
179 r"""Find a maximum single-commodity flow using the shortest augmenting path
180 algorithm.
181
182 This function returns the residual network resulting after computing
183 the maximum flow. See below for details about the conventions
184 NetworkX uses for defining residual networks.
185
186 This algorithm has a running time of $O(n^2 m)$ for $n$ nodes and $m$
187 edges.
188
189
190 Parameters
191 ----------
192 G : NetworkX graph
193 Edges of the graph are expected to have an attribute called
194 'capacity'. If this attribute is not present, the edge is
195 considered to have infinite capacity.
196
197 s : node
198 Source node for the flow.
199
200 t : node
201 Sink node for the flow.
202
203 capacity : string or function (default= 'capacity')
204 If this is a string, then edge capacity will be accessed via the
205 edge attribute with this key (that is, the capacity of the edge
206 joining `u` to `v` will be ``G.edges[u, v][capacity]``). If no
207 such edge attribute exists, the capacity of the edge is assumed to
208 be infinite.
209
210 If this is a function, the capacity of an edge is the value
211 returned by the function. The function must accept exactly three
212 positional arguments: the two endpoints of an edge and the
213 dictionary of edge attributes for that edge. The function must
214 return a number or None to indicate a hidden edge.
215
216 residual : NetworkX graph
217 Residual network on which the algorithm is to be executed. If None, a
218 new residual network is created. Default value: None.
219
220 value_only : bool
221 If True compute only the value of the maximum flow. This parameter
222 will be ignored by this algorithm because it is not applicable.
223
224 two_phase : bool
225 If True, a two-phase variant is used. The two-phase variant improves
226 the running time on unit-capacity networks from $O(nm)$ to
227 $O(\min(n^{2/3}, m^{1/2}) m)$. Default value: False.
228
229 cutoff : integer, float
230 If specified, the algorithm will terminate when the flow value reaches
231 or exceeds the cutoff. In this case, it may be unable to immediately
232 determine a minimum cut. Default value: None.
233
234 Returns
235 -------
236 R : NetworkX DiGraph
237 Residual network after computing the maximum flow.
238
239 Raises
240 ------
241 NetworkXError
242 The algorithm does not support MultiGraph and MultiDiGraph. If
243 the input graph is an instance of one of these two classes, a
244 NetworkXError is raised.
245
246 NetworkXUnbounded
247 If the graph has a path of infinite capacity, the value of a
248 feasible flow on the graph is unbounded above and the function
249 raises a NetworkXUnbounded.
250
251 See also
252 --------
253 :meth:`maximum_flow`
254 :meth:`minimum_cut`
255 :meth:`edmonds_karp`
256 :meth:`preflow_push`
257
258 Notes
259 -----
260 The residual network :samp:`R` from an input graph :samp:`G` has the
261 same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
262 of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
263 self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
264 in :samp:`G`.
265
266 For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
267 is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
268 in :samp:`G` or zero otherwise. If the capacity is infinite,
269 :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
270 that does not affect the solution of the problem. This value is stored in
271 :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
272 :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
273 satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
274
275 The flow value, defined as the total flow into :samp:`t`, the sink, is
276 stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
277 specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
278 that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
279 :samp:`s`-:samp:`t` cut.
280
281 Examples
282 --------
283 >>> from networkx.algorithms.flow import shortest_augmenting_path
284
285 The functions that implement flow algorithms and output a residual
286 network, such as this one, are not imported to the base NetworkX
287 namespace, so you have to explicitly import them from the flow package.
288
289 >>> G = nx.DiGraph()
290 >>> G.add_edge("x", "a", capacity=3.0)
291 >>> G.add_edge("x", "b", capacity=1.0)
292 >>> G.add_edge("a", "c", capacity=3.0)
293 >>> G.add_edge("b", "c", capacity=5.0)
294 >>> G.add_edge("b", "d", capacity=4.0)
295 >>> G.add_edge("d", "e", capacity=2.0)
296 >>> G.add_edge("c", "y", capacity=2.0)
297 >>> G.add_edge("e", "y", capacity=3.0)
298 >>> R = shortest_augmenting_path(G, "x", "y")
299 >>> flow_value = nx.maximum_flow_value(G, "x", "y")
300 >>> flow_value
301 3.0
302 >>> flow_value == R.graph["flow_value"]
303 True
304
305 """
306 R = shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff)
307 R.graph["algorithm"] = "shortest_augmenting_path"
308 nx._clear_cache(R)
309 return R