1"""
2Capacity scaling minimum cost flow algorithm.
3"""
4
5__all__ = ["capacity_scaling"]
6
7from itertools import chain
8from math import log
9
10import networkx as nx
11
12from ...utils import BinaryHeap, arbitrary_element, not_implemented_for
13
14
15def _detect_unboundedness(R):
16 """Detect infinite-capacity negative cycles."""
17 G = nx.DiGraph()
18 G.add_nodes_from(R)
19
20 # Value simulating infinity.
21 inf = R.graph["inf"]
22 # True infinity.
23 f_inf = float("inf")
24 for u in R:
25 for v, e in R[u].items():
26 # Compute the minimum weight of infinite-capacity (u, v) edges.
27 w = f_inf
28 for k, e in e.items():
29 if e["capacity"] == inf:
30 w = min(w, e["weight"])
31 if w != f_inf:
32 G.add_edge(u, v, weight=w)
33
34 if nx.negative_edge_cycle(G):
35 raise nx.NetworkXUnbounded(
36 "Negative cost cycle of infinite capacity found. "
37 "Min cost flow may be unbounded below."
38 )
39
40
41@not_implemented_for("undirected")
42def _build_residual_network(G, demand, capacity, weight):
43 """Build a residual network and initialize a zero flow."""
44 if sum(G.nodes[u].get(demand, 0) for u in G) != 0:
45 raise nx.NetworkXUnfeasible("Sum of the demands should be 0.")
46
47 R = nx.MultiDiGraph()
48 R.add_nodes_from(
49 (u, {"excess": -G.nodes[u].get(demand, 0), "potential": 0}) for u in G
50 )
51
52 inf = float("inf")
53 # Detect selfloops with infinite capacities and negative weights.
54 for u, v, e in nx.selfloop_edges(G, data=True):
55 if e.get(weight, 0) < 0 and e.get(capacity, inf) == inf:
56 raise nx.NetworkXUnbounded(
57 "Negative cost cycle of infinite capacity found. "
58 "Min cost flow may be unbounded below."
59 )
60
61 # Extract edges with positive capacities. Self loops excluded.
62 if G.is_multigraph():
63 edge_list = [
64 (u, v, k, e)
65 for u, v, k, e in G.edges(data=True, keys=True)
66 if u != v and e.get(capacity, inf) > 0
67 ]
68 else:
69 edge_list = [
70 (u, v, 0, e)
71 for u, v, e in G.edges(data=True)
72 if u != v and e.get(capacity, inf) > 0
73 ]
74 # Simulate infinity with the larger of the sum of absolute node imbalances
75 # the sum of finite edge capacities or any positive value if both sums are
76 # zero. This allows the infinite-capacity edges to be distinguished for
77 # unboundedness detection and directly participate in residual capacity
78 # calculation.
79 inf = (
80 max(
81 sum(abs(R.nodes[u]["excess"]) for u in R),
82 2
83 * sum(
84 e[capacity]
85 for u, v, k, e in edge_list
86 if capacity in e and e[capacity] != inf
87 ),
88 )
89 or 1
90 )
91 for u, v, k, e in edge_list:
92 r = min(e.get(capacity, inf), inf)
93 w = e.get(weight, 0)
94 # Add both (u, v) and (v, u) into the residual network marked with the
95 # original key. (key[1] == True) indicates the (u, v) is in the
96 # original network.
97 R.add_edge(u, v, key=(k, True), capacity=r, weight=w, flow=0)
98 R.add_edge(v, u, key=(k, False), capacity=0, weight=-w, flow=0)
99
100 # Record the value simulating infinity.
101 R.graph["inf"] = inf
102
103 _detect_unboundedness(R)
104
105 return R
106
107
108def _build_flow_dict(G, R, capacity, weight):
109 """Build a flow dictionary from a residual network."""
110 inf = float("inf")
111 flow_dict = {}
112 if G.is_multigraph():
113 for u in G:
114 flow_dict[u] = {}
115 for v, es in G[u].items():
116 flow_dict[u][v] = {
117 # Always saturate negative selfloops.
118 k: (
119 0
120 if (
121 u != v or e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0
122 )
123 else e[capacity]
124 )
125 for k, e in es.items()
126 }
127 for v, es in R[u].items():
128 if v in flow_dict[u]:
129 flow_dict[u][v].update(
130 (k[0], e["flow"]) for k, e in es.items() if e["flow"] > 0
131 )
132 else:
133 for u in G:
134 flow_dict[u] = {
135 # Always saturate negative selfloops.
136 v: (
137 0
138 if (u != v or e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0)
139 else e[capacity]
140 )
141 for v, e in G[u].items()
142 }
143 flow_dict[u].update(
144 (v, e["flow"])
145 for v, es in R[u].items()
146 for e in es.values()
147 if e["flow"] > 0
148 )
149 return flow_dict
150
151
152@nx._dispatchable(
153 node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0}
154)
155def capacity_scaling(
156 G, demand="demand", capacity="capacity", weight="weight", heap=BinaryHeap
157):
158 r"""Find a minimum cost flow satisfying all demands in digraph G.
159
160 This is a capacity scaling successive shortest augmenting path algorithm.
161
162 G is a digraph with edge costs and capacities and in which nodes
163 have demand, i.e., they want to send or receive some amount of
164 flow. A negative demand means that the node wants to send flow, a
165 positive demand means that the node want to receive flow. A flow on
166 the digraph G satisfies all demand if the net flow into each node
167 is equal to the demand of that node.
168
169 Parameters
170 ----------
171 G : NetworkX graph
172 DiGraph or MultiDiGraph on which a minimum cost flow satisfying all
173 demands is to be found.
174
175 demand : string
176 Nodes of the graph G are expected to have an attribute demand
177 that indicates how much flow a node wants to send (negative
178 demand) or receive (positive demand). Note that the sum of the
179 demands should be 0 otherwise the problem in not feasible. If
180 this attribute is not present, a node is considered to have 0
181 demand. Default value: 'demand'.
182
183 capacity : string
184 Edges of the graph G are expected to have an attribute capacity
185 that indicates how much flow the edge can support. If this
186 attribute is not present, the edge is considered to have
187 infinite capacity. Default value: 'capacity'.
188
189 weight : string
190 Edges of the graph G are expected to have an attribute weight
191 that indicates the cost incurred by sending one unit of flow on
192 that edge. If not present, the weight is considered to be 0.
193 Default value: 'weight'.
194
195 heap : class
196 Type of heap to be used in the algorithm. It should be a subclass of
197 :class:`MinHeap` or implement a compatible interface.
198
199 If a stock heap implementation is to be used, :class:`BinaryHeap` is
200 recommended over :class:`PairingHeap` for Python implementations without
201 optimized attribute accesses (e.g., CPython) despite a slower
202 asymptotic running time. For Python implementations with optimized
203 attribute accesses (e.g., PyPy), :class:`PairingHeap` provides better
204 performance. Default value: :class:`BinaryHeap`.
205
206 Returns
207 -------
208 flowCost : integer
209 Cost of a minimum cost flow satisfying all demands.
210
211 flowDict : dictionary
212 If G is a digraph, a dict-of-dicts keyed by nodes such that
213 flowDict[u][v] is the flow on edge (u, v).
214 If G is a MultiDiGraph, a dict-of-dicts-of-dicts keyed by nodes
215 so that flowDict[u][v][key] is the flow on edge (u, v, key).
216
217 Raises
218 ------
219 NetworkXError
220 This exception is raised if the input graph is not directed,
221 not connected.
222
223 NetworkXUnfeasible
224 This exception is raised in the following situations:
225
226 * The sum of the demands is not zero. Then, there is no
227 flow satisfying all demands.
228 * There is no flow satisfying all demand.
229
230 NetworkXUnbounded
231 This exception is raised if the digraph G has a cycle of
232 negative cost and infinite capacity. Then, the cost of a flow
233 satisfying all demands is unbounded below.
234
235 Notes
236 -----
237 This algorithm does not work if edge weights are floating-point numbers.
238
239 See also
240 --------
241 :meth:`network_simplex`
242
243 Examples
244 --------
245 A simple example of a min cost flow problem.
246
247 >>> G = nx.DiGraph()
248 >>> G.add_node("a", demand=-5)
249 >>> G.add_node("d", demand=5)
250 >>> G.add_edge("a", "b", weight=3, capacity=4)
251 >>> G.add_edge("a", "c", weight=6, capacity=10)
252 >>> G.add_edge("b", "d", weight=1, capacity=9)
253 >>> G.add_edge("c", "d", weight=2, capacity=5)
254 >>> flowCost, flowDict = nx.capacity_scaling(G)
255 >>> flowCost
256 24
257 >>> flowDict
258 {'a': {'b': 4, 'c': 1}, 'd': {}, 'b': {'d': 4}, 'c': {'d': 1}}
259
260 It is possible to change the name of the attributes used for the
261 algorithm.
262
263 >>> G = nx.DiGraph()
264 >>> G.add_node("p", spam=-4)
265 >>> G.add_node("q", spam=2)
266 >>> G.add_node("a", spam=-2)
267 >>> G.add_node("d", spam=-1)
268 >>> G.add_node("t", spam=2)
269 >>> G.add_node("w", spam=3)
270 >>> G.add_edge("p", "q", cost=7, vacancies=5)
271 >>> G.add_edge("p", "a", cost=1, vacancies=4)
272 >>> G.add_edge("q", "d", cost=2, vacancies=3)
273 >>> G.add_edge("t", "q", cost=1, vacancies=2)
274 >>> G.add_edge("a", "t", cost=2, vacancies=4)
275 >>> G.add_edge("d", "w", cost=3, vacancies=4)
276 >>> G.add_edge("t", "w", cost=4, vacancies=1)
277 >>> flowCost, flowDict = nx.capacity_scaling(
278 ... G, demand="spam", capacity="vacancies", weight="cost"
279 ... )
280 >>> flowCost
281 37
282 >>> flowDict
283 {'p': {'q': 2, 'a': 2}, 'q': {'d': 1}, 'a': {'t': 4}, 'd': {'w': 2}, 't': {'q': 1, 'w': 1}, 'w': {}}
284 """
285 R = _build_residual_network(G, demand, capacity, weight)
286
287 inf = float("inf")
288 # Account cost of negative selfloops.
289 flow_cost = sum(
290 0
291 if e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0
292 else e[capacity] * e[weight]
293 for u, v, e in nx.selfloop_edges(G, data=True)
294 )
295
296 # Determine the maximum edge capacity.
297 wmax = max(chain([-inf], (e["capacity"] for u, v, e in R.edges(data=True))))
298 if wmax == -inf:
299 # Residual network has no edges.
300 return flow_cost, _build_flow_dict(G, R, capacity, weight)
301
302 R_nodes = R.nodes
303 R_succ = R.succ
304
305 delta = 2 ** int(log(wmax, 2))
306 while delta >= 1:
307 # Saturate Δ-residual edges with negative reduced costs to achieve
308 # Δ-optimality.
309 for u in R:
310 p_u = R_nodes[u]["potential"]
311 for v, es in R_succ[u].items():
312 for k, e in es.items():
313 flow = e["capacity"] - e["flow"]
314 if e["weight"] - p_u + R_nodes[v]["potential"] < 0:
315 flow = e["capacity"] - e["flow"]
316 if flow >= delta:
317 e["flow"] += flow
318 R_succ[v][u][(k[0], not k[1])]["flow"] -= flow
319 R_nodes[u]["excess"] -= flow
320 R_nodes[v]["excess"] += flow
321 # Determine the Δ-active nodes.
322 S = set()
323 T = set()
324 S_add = S.add
325 S_remove = S.remove
326 T_add = T.add
327 T_remove = T.remove
328 for u in R:
329 excess = R_nodes[u]["excess"]
330 if excess >= delta:
331 S_add(u)
332 elif excess <= -delta:
333 T_add(u)
334 # Repeatedly augment flow from S to T along shortest paths until
335 # Δ-feasibility is achieved.
336 while S and T:
337 s = arbitrary_element(S)
338 t = None
339 # Search for a shortest path in terms of reduce costs from s to
340 # any t in T in the Δ-residual network.
341 d = {}
342 pred = {s: None}
343 h = heap()
344 h_insert = h.insert
345 h_get = h.get
346 h_insert(s, 0)
347 while h:
348 u, d_u = h.pop()
349 d[u] = d_u
350 if u in T:
351 # Path found.
352 t = u
353 break
354 p_u = R_nodes[u]["potential"]
355 for v, es in R_succ[u].items():
356 if v in d:
357 continue
358 wmin = inf
359 # Find the minimum-weighted (u, v) Δ-residual edge.
360 for k, e in es.items():
361 if e["capacity"] - e["flow"] >= delta:
362 w = e["weight"]
363 if w < wmin:
364 wmin = w
365 kmin = k
366 emin = e
367 if wmin == inf:
368 continue
369 # Update the distance label of v.
370 d_v = d_u + wmin - p_u + R_nodes[v]["potential"]
371 if h_insert(v, d_v):
372 pred[v] = (u, kmin, emin)
373 if t is not None:
374 # Augment Δ units of flow from s to t.
375 while u != s:
376 v = u
377 u, k, e = pred[v]
378 e["flow"] += delta
379 R_succ[v][u][(k[0], not k[1])]["flow"] -= delta
380 # Account node excess and deficit.
381 R_nodes[s]["excess"] -= delta
382 R_nodes[t]["excess"] += delta
383 if R_nodes[s]["excess"] < delta:
384 S_remove(s)
385 if R_nodes[t]["excess"] > -delta:
386 T_remove(t)
387 # Update node potentials.
388 d_t = d[t]
389 for u, d_u in d.items():
390 R_nodes[u]["potential"] -= d_u - d_t
391 else:
392 # Path not found.
393 S_remove(s)
394 delta //= 2
395
396 if any(R.nodes[u]["excess"] != 0 for u in R):
397 raise nx.NetworkXUnfeasible("No flow satisfying all demands.")
398
399 # Calculate the flow cost.
400 for u in R:
401 for v, es in R_succ[u].items():
402 for e in es.values():
403 flow = e["flow"]
404 if flow > 0:
405 flow_cost += flow * e["weight"]
406
407 return flow_cost, _build_flow_dict(G, R, capacity, weight)