1"""
2Edmonds-Karp algorithm for maximum flow problems.
3"""
4
5import networkx as nx
6from networkx.algorithms.flow.utils import build_residual_network
7
8__all__ = ["edmonds_karp"]
9
10
11def edmonds_karp_core(R, s, t, cutoff):
12 """Implementation of the Edmonds-Karp algorithm."""
13 R_nodes = R.nodes
14 R_pred = R.pred
15 R_succ = R.succ
16
17 inf = R.graph["inf"]
18
19 def augment(path):
20 """Augment flow along a path from s to t."""
21 # Determine the path residual capacity.
22 flow = inf
23 it = iter(path)
24 u = next(it)
25 for v in it:
26 attr = R_succ[u][v]
27 flow = min(flow, attr["capacity"] - attr["flow"])
28 u = v
29 if flow * 2 > inf:
30 raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
31 # Augment flow along the path.
32 it = iter(path)
33 u = next(it)
34 for v in it:
35 R_succ[u][v]["flow"] += flow
36 R_succ[v][u]["flow"] -= flow
37 u = v
38 return flow
39
40 def bidirectional_bfs():
41 """Bidirectional breadth-first search for an augmenting path."""
42 pred = {s: None}
43 q_s = [s]
44 succ = {t: None}
45 q_t = [t]
46 while True:
47 q = []
48 if len(q_s) <= len(q_t):
49 for u in q_s:
50 for v, attr in R_succ[u].items():
51 if v not in pred and attr["flow"] < attr["capacity"]:
52 pred[v] = u
53 if v in succ:
54 return v, pred, succ
55 q.append(v)
56 if not q:
57 return None, None, None
58 q_s = q
59 else:
60 for u in q_t:
61 for v, attr in R_pred[u].items():
62 if v not in succ and attr["flow"] < attr["capacity"]:
63 succ[v] = u
64 if v in pred:
65 return v, pred, succ
66 q.append(v)
67 if not q:
68 return None, None, None
69 q_t = q
70
71 # Look for shortest augmenting paths using breadth-first search.
72 flow_value = 0
73 while flow_value < cutoff:
74 v, pred, succ = bidirectional_bfs()
75 if pred is None:
76 break
77 path = [v]
78 # Trace a path from s to v.
79 u = v
80 while u != s:
81 u = pred[u]
82 path.append(u)
83 path.reverse()
84 # Trace a path from v to t.
85 u = v
86 while u != t:
87 u = succ[u]
88 path.append(u)
89 flow_value += augment(path)
90
91 return flow_value
92
93
94def edmonds_karp_impl(G, s, t, capacity, residual, cutoff):
95 """Implementation of the Edmonds-Karp algorithm."""
96 if s not in G:
97 raise nx.NetworkXError(f"node {str(s)} not in graph")
98 if t not in G:
99 raise nx.NetworkXError(f"node {str(t)} not in graph")
100 if s == t:
101 raise nx.NetworkXError("source and sink are the same node")
102
103 if residual is None:
104 R = build_residual_network(G, capacity)
105 else:
106 R = residual
107
108 # Initialize/reset the residual network.
109 for u in R:
110 for e in R[u].values():
111 e["flow"] = 0
112
113 if cutoff is None:
114 cutoff = float("inf")
115 R.graph["flow_value"] = edmonds_karp_core(R, s, t, cutoff)
116
117 return R
118
119
120@nx._dispatchable(edge_attrs={"capacity": float("inf")}, returns_graph=True)
121def edmonds_karp(
122 G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None
123):
124 """Find a maximum single-commodity flow using the Edmonds-Karp algorithm.
125
126 This function returns the residual network resulting after computing
127 the maximum flow. See below for details about the conventions
128 NetworkX uses for defining residual networks.
129
130 This algorithm has a running time of $O(n m^2)$ for $n$ nodes and $m$
131 edges.
132
133
134 Parameters
135 ----------
136 G : NetworkX graph
137 Edges of the graph are expected to have an attribute called
138 'capacity'. If this attribute is not present, the edge is
139 considered to have infinite capacity.
140
141 s : node
142 Source node for the flow.
143
144 t : node
145 Sink node for the flow.
146
147 capacity : string
148 Edges of the graph G are expected to have an attribute capacity
149 that indicates how much flow the edge can support. If this
150 attribute is not present, the edge is considered to have
151 infinite capacity. Default value: 'capacity'.
152
153 residual : NetworkX graph
154 Residual network on which the algorithm is to be executed. If None, a
155 new residual network is created. Default value: None.
156
157 value_only : bool
158 If True compute only the value of the maximum flow. This parameter
159 will be ignored by this algorithm because it is not applicable.
160
161 cutoff : integer, float
162 If specified, the algorithm will terminate when the flow value reaches
163 or exceeds the cutoff. In this case, it may be unable to immediately
164 determine a minimum cut. Default value: None.
165
166 Returns
167 -------
168 R : NetworkX DiGraph
169 Residual network after computing the maximum flow.
170
171 Raises
172 ------
173 NetworkXError
174 The algorithm does not support MultiGraph and MultiDiGraph. If
175 the input graph is an instance of one of these two classes, a
176 NetworkXError is raised.
177
178 NetworkXUnbounded
179 If the graph has a path of infinite capacity, the value of a
180 feasible flow on the graph is unbounded above and the function
181 raises a NetworkXUnbounded.
182
183 See also
184 --------
185 :meth:`maximum_flow`
186 :meth:`minimum_cut`
187 :meth:`preflow_push`
188 :meth:`shortest_augmenting_path`
189
190 Notes
191 -----
192 The residual network :samp:`R` from an input graph :samp:`G` has the
193 same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
194 of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
195 self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
196 in :samp:`G`.
197
198 For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
199 is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
200 in :samp:`G` or zero otherwise. If the capacity is infinite,
201 :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
202 that does not affect the solution of the problem. This value is stored in
203 :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
204 :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
205 satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
206
207 The flow value, defined as the total flow into :samp:`t`, the sink, is
208 stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
209 specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
210 that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
211 :samp:`s`-:samp:`t` cut.
212
213 Examples
214 --------
215 >>> from networkx.algorithms.flow import edmonds_karp
216
217 The functions that implement flow algorithms and output a residual
218 network, such as this one, are not imported to the base NetworkX
219 namespace, so you have to explicitly import them from the flow package.
220
221 >>> G = nx.DiGraph()
222 >>> G.add_edge("x", "a", capacity=3.0)
223 >>> G.add_edge("x", "b", capacity=1.0)
224 >>> G.add_edge("a", "c", capacity=3.0)
225 >>> G.add_edge("b", "c", capacity=5.0)
226 >>> G.add_edge("b", "d", capacity=4.0)
227 >>> G.add_edge("d", "e", capacity=2.0)
228 >>> G.add_edge("c", "y", capacity=2.0)
229 >>> G.add_edge("e", "y", capacity=3.0)
230 >>> R = edmonds_karp(G, "x", "y")
231 >>> flow_value = nx.maximum_flow_value(G, "x", "y")
232 >>> flow_value
233 3.0
234 >>> flow_value == R.graph["flow_value"]
235 True
236
237 """
238 R = edmonds_karp_impl(G, s, t, capacity, residual, cutoff)
239 R.graph["algorithm"] = "edmonds_karp"
240 nx._clear_cache(R)
241 return R