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(
121 edge_attrs={"capacity": float("inf")}, returns_graph=True, preserve_edge_attrs=True
122)
123def edmonds_karp(
124 G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None
125):
126 """Find a maximum single-commodity flow using the Edmonds-Karp algorithm.
127
128 This function returns the residual network resulting after computing
129 the maximum flow. See below for details about the conventions
130 NetworkX uses for defining residual networks.
131
132 This algorithm has a running time of $O(n m^2)$ for $n$ nodes and $m$
133 edges.
134
135
136 Parameters
137 ----------
138 G : NetworkX graph
139 Edges of the graph are expected to have an attribute called
140 'capacity'. If this attribute is not present, the edge is
141 considered to have infinite capacity.
142
143 s : node
144 Source node for the flow.
145
146 t : node
147 Sink node for the flow.
148
149 capacity : string or function (default= 'capacity')
150 If this is a string, then edge capacity will be accessed via the
151 edge attribute with this key (that is, the capacity of the edge
152 joining `u` to `v` will be ``G.edges[u, v][capacity]``). If no
153 such edge attribute exists, the capacity of the edge is assumed to
154 be infinite.
155
156 If this is a function, the capacity of an edge is the value
157 returned by the function. The function must accept exactly three
158 positional arguments: the two endpoints of an edge and the
159 dictionary of edge attributes for that edge. The function must
160 return a number or None to indicate a hidden edge.
161
162 residual : NetworkX graph
163 Residual network on which the algorithm is to be executed. If None, a
164 new residual network is created. Default value: None.
165
166 value_only : bool
167 If True compute only the value of the maximum flow. This parameter
168 will be ignored by this algorithm because it is not applicable.
169
170 cutoff : integer, float
171 If specified, the algorithm will terminate when the flow value reaches
172 or exceeds the cutoff. In this case, it may be unable to immediately
173 determine a minimum cut. Default value: None.
174
175 Returns
176 -------
177 R : NetworkX DiGraph
178 Residual network after computing the maximum flow.
179
180 Raises
181 ------
182 NetworkXError
183 The algorithm does not support MultiGraph and MultiDiGraph. If
184 the input graph is an instance of one of these two classes, a
185 NetworkXError is raised.
186
187 NetworkXUnbounded
188 If the graph has a path of infinite capacity, the value of a
189 feasible flow on the graph is unbounded above and the function
190 raises a NetworkXUnbounded.
191
192 See also
193 --------
194 :meth:`maximum_flow`
195 :meth:`minimum_cut`
196 :meth:`preflow_push`
197 :meth:`shortest_augmenting_path`
198
199 Notes
200 -----
201 The residual network :samp:`R` from an input graph :samp:`G` has the
202 same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
203 of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
204 self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
205 in :samp:`G`.
206
207 For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
208 is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
209 in :samp:`G` or zero otherwise. If the capacity is infinite,
210 :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
211 that does not affect the solution of the problem. This value is stored in
212 :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
213 :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
214 satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
215
216 The flow value, defined as the total flow into :samp:`t`, the sink, is
217 stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
218 specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
219 that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
220 :samp:`s`-:samp:`t` cut.
221
222 Examples
223 --------
224 >>> from networkx.algorithms.flow import edmonds_karp
225
226 The functions that implement flow algorithms and output a residual
227 network, such as this one, are not imported to the base NetworkX
228 namespace, so you have to explicitly import them from the flow package.
229
230 >>> G = nx.DiGraph()
231 >>> G.add_edge("x", "a", capacity=3.0)
232 >>> G.add_edge("x", "b", capacity=1.0)
233 >>> G.add_edge("a", "c", capacity=3.0)
234 >>> G.add_edge("b", "c", capacity=5.0)
235 >>> G.add_edge("b", "d", capacity=4.0)
236 >>> G.add_edge("d", "e", capacity=2.0)
237 >>> G.add_edge("c", "y", capacity=2.0)
238 >>> G.add_edge("e", "y", capacity=3.0)
239 >>> R = edmonds_karp(G, "x", "y")
240 >>> flow_value = nx.maximum_flow_value(G, "x", "y")
241 >>> flow_value
242 3.0
243 >>> flow_value == R.graph["flow_value"]
244 True
245
246 """
247 R = edmonds_karp_impl(G, s, t, capacity, residual, cutoff)
248 R.graph["algorithm"] = "edmonds_karp"
249 nx._clear_cache(R)
250 return R