Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/flow/capacityscaling.py: 7%

151 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

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._dispatch(node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0}) 

153def capacity_scaling( 

154 G, demand="demand", capacity="capacity", weight="weight", heap=BinaryHeap 

155): 

156 r"""Find a minimum cost flow satisfying all demands in digraph G. 

157 

158 This is a capacity scaling successive shortest augmenting path algorithm. 

159 

160 G is a digraph with edge costs and capacities and in which nodes 

161 have demand, i.e., they want to send or receive some amount of 

162 flow. A negative demand means that the node wants to send flow, a 

163 positive demand means that the node want to receive flow. A flow on 

164 the digraph G satisfies all demand if the net flow into each node 

165 is equal to the demand of that node. 

166 

167 Parameters 

168 ---------- 

169 G : NetworkX graph 

170 DiGraph or MultiDiGraph on which a minimum cost flow satisfying all 

171 demands is to be found. 

172 

173 demand : string 

174 Nodes of the graph G are expected to have an attribute demand 

175 that indicates how much flow a node wants to send (negative 

176 demand) or receive (positive demand). Note that the sum of the 

177 demands should be 0 otherwise the problem in not feasible. If 

178 this attribute is not present, a node is considered to have 0 

179 demand. Default value: 'demand'. 

180 

181 capacity : string 

182 Edges of the graph G are expected to have an attribute capacity 

183 that indicates how much flow the edge can support. If this 

184 attribute is not present, the edge is considered to have 

185 infinite capacity. Default value: 'capacity'. 

186 

187 weight : string 

188 Edges of the graph G are expected to have an attribute weight 

189 that indicates the cost incurred by sending one unit of flow on 

190 that edge. If not present, the weight is considered to be 0. 

191 Default value: 'weight'. 

192 

193 heap : class 

194 Type of heap to be used in the algorithm. It should be a subclass of 

195 :class:`MinHeap` or implement a compatible interface. 

196 

197 If a stock heap implementation is to be used, :class:`BinaryHeap` is 

198 recommended over :class:`PairingHeap` for Python implementations without 

199 optimized attribute accesses (e.g., CPython) despite a slower 

200 asymptotic running time. For Python implementations with optimized 

201 attribute accesses (e.g., PyPy), :class:`PairingHeap` provides better 

202 performance. Default value: :class:`BinaryHeap`. 

203 

204 Returns 

205 ------- 

206 flowCost : integer 

207 Cost of a minimum cost flow satisfying all demands. 

208 

209 flowDict : dictionary 

210 If G is a digraph, a dict-of-dicts keyed by nodes such that 

211 flowDict[u][v] is the flow on edge (u, v). 

212 If G is a MultiDiGraph, a dict-of-dicts-of-dicts keyed by nodes 

213 so that flowDict[u][v][key] is the flow on edge (u, v, key). 

214 

215 Raises 

216 ------ 

217 NetworkXError 

218 This exception is raised if the input graph is not directed, 

219 not connected. 

220 

221 NetworkXUnfeasible 

222 This exception is raised in the following situations: 

223 

224 * The sum of the demands is not zero. Then, there is no 

225 flow satisfying all demands. 

226 * There is no flow satisfying all demand. 

227 

228 NetworkXUnbounded 

229 This exception is raised if the digraph G has a cycle of 

230 negative cost and infinite capacity. Then, the cost of a flow 

231 satisfying all demands is unbounded below. 

232 

233 Notes 

234 ----- 

235 This algorithm does not work if edge weights are floating-point numbers. 

236 

237 See also 

238 -------- 

239 :meth:`network_simplex` 

240 

241 Examples 

242 -------- 

243 A simple example of a min cost flow problem. 

244 

245 >>> G = nx.DiGraph() 

246 >>> G.add_node("a", demand=-5) 

247 >>> G.add_node("d", demand=5) 

248 >>> G.add_edge("a", "b", weight=3, capacity=4) 

249 >>> G.add_edge("a", "c", weight=6, capacity=10) 

250 >>> G.add_edge("b", "d", weight=1, capacity=9) 

251 >>> G.add_edge("c", "d", weight=2, capacity=5) 

252 >>> flowCost, flowDict = nx.capacity_scaling(G) 

253 >>> flowCost 

254 24 

255 >>> flowDict 

256 {'a': {'b': 4, 'c': 1}, 'd': {}, 'b': {'d': 4}, 'c': {'d': 1}} 

257 

258 It is possible to change the name of the attributes used for the 

259 algorithm. 

260 

261 >>> G = nx.DiGraph() 

262 >>> G.add_node("p", spam=-4) 

263 >>> G.add_node("q", spam=2) 

264 >>> G.add_node("a", spam=-2) 

265 >>> G.add_node("d", spam=-1) 

266 >>> G.add_node("t", spam=2) 

267 >>> G.add_node("w", spam=3) 

268 >>> G.add_edge("p", "q", cost=7, vacancies=5) 

269 >>> G.add_edge("p", "a", cost=1, vacancies=4) 

270 >>> G.add_edge("q", "d", cost=2, vacancies=3) 

271 >>> G.add_edge("t", "q", cost=1, vacancies=2) 

272 >>> G.add_edge("a", "t", cost=2, vacancies=4) 

273 >>> G.add_edge("d", "w", cost=3, vacancies=4) 

274 >>> G.add_edge("t", "w", cost=4, vacancies=1) 

275 >>> flowCost, flowDict = nx.capacity_scaling( 

276 ... G, demand="spam", capacity="vacancies", weight="cost" 

277 ... ) 

278 >>> flowCost 

279 37 

280 >>> flowDict 

281 {'p': {'q': 2, 'a': 2}, 'q': {'d': 1}, 'a': {'t': 4}, 'd': {'w': 2}, 't': {'q': 1, 'w': 1}, 'w': {}} 

282 """ 

283 R = _build_residual_network(G, demand, capacity, weight) 

284 

285 inf = float("inf") 

286 # Account cost of negative selfloops. 

287 flow_cost = sum( 

288 0 

289 if e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0 

290 else e[capacity] * e[weight] 

291 for u, v, e in nx.selfloop_edges(G, data=True) 

292 ) 

293 

294 # Determine the maximum edge capacity. 

295 wmax = max(chain([-inf], (e["capacity"] for u, v, e in R.edges(data=True)))) 

296 if wmax == -inf: 

297 # Residual network has no edges. 

298 return flow_cost, _build_flow_dict(G, R, capacity, weight) 

299 

300 R_nodes = R.nodes 

301 R_succ = R.succ 

302 

303 delta = 2 ** int(log(wmax, 2)) 

304 while delta >= 1: 

305 # Saturate Δ-residual edges with negative reduced costs to achieve 

306 # Δ-optimality. 

307 for u in R: 

308 p_u = R_nodes[u]["potential"] 

309 for v, es in R_succ[u].items(): 

310 for k, e in es.items(): 

311 flow = e["capacity"] - e["flow"] 

312 if e["weight"] - p_u + R_nodes[v]["potential"] < 0: 

313 flow = e["capacity"] - e["flow"] 

314 if flow >= delta: 

315 e["flow"] += flow 

316 R_succ[v][u][(k[0], not k[1])]["flow"] -= flow 

317 R_nodes[u]["excess"] -= flow 

318 R_nodes[v]["excess"] += flow 

319 # Determine the Δ-active nodes. 

320 S = set() 

321 T = set() 

322 S_add = S.add 

323 S_remove = S.remove 

324 T_add = T.add 

325 T_remove = T.remove 

326 for u in R: 

327 excess = R_nodes[u]["excess"] 

328 if excess >= delta: 

329 S_add(u) 

330 elif excess <= -delta: 

331 T_add(u) 

332 # Repeatedly augment flow from S to T along shortest paths until 

333 # Δ-feasibility is achieved. 

334 while S and T: 

335 s = arbitrary_element(S) 

336 t = None 

337 # Search for a shortest path in terms of reduce costs from s to 

338 # any t in T in the Δ-residual network. 

339 d = {} 

340 pred = {s: None} 

341 h = heap() 

342 h_insert = h.insert 

343 h_get = h.get 

344 h_insert(s, 0) 

345 while h: 

346 u, d_u = h.pop() 

347 d[u] = d_u 

348 if u in T: 

349 # Path found. 

350 t = u 

351 break 

352 p_u = R_nodes[u]["potential"] 

353 for v, es in R_succ[u].items(): 

354 if v in d: 

355 continue 

356 wmin = inf 

357 # Find the minimum-weighted (u, v) Δ-residual edge. 

358 for k, e in es.items(): 

359 if e["capacity"] - e["flow"] >= delta: 

360 w = e["weight"] 

361 if w < wmin: 

362 wmin = w 

363 kmin = k 

364 emin = e 

365 if wmin == inf: 

366 continue 

367 # Update the distance label of v. 

368 d_v = d_u + wmin - p_u + R_nodes[v]["potential"] 

369 if h_insert(v, d_v): 

370 pred[v] = (u, kmin, emin) 

371 if t is not None: 

372 # Augment Δ units of flow from s to t. 

373 while u != s: 

374 v = u 

375 u, k, e = pred[v] 

376 e["flow"] += delta 

377 R_succ[v][u][(k[0], not k[1])]["flow"] -= delta 

378 # Account node excess and deficit. 

379 R_nodes[s]["excess"] -= delta 

380 R_nodes[t]["excess"] += delta 

381 if R_nodes[s]["excess"] < delta: 

382 S_remove(s) 

383 if R_nodes[t]["excess"] > -delta: 

384 T_remove(t) 

385 # Update node potentials. 

386 d_t = d[t] 

387 for u, d_u in d.items(): 

388 R_nodes[u]["potential"] -= d_u - d_t 

389 else: 

390 # Path not found. 

391 S_remove(s) 

392 delta //= 2 

393 

394 if any(R.nodes[u]["excess"] != 0 for u in R): 

395 raise nx.NetworkXUnfeasible("No flow satisfying all demands.") 

396 

397 # Calculate the flow cost. 

398 for u in R: 

399 for v, es in R_succ[u].items(): 

400 for e in es.values(): 

401 flow = e["flow"] 

402 if flow > 0: 

403 flow_cost += flow * e["weight"] 

404 

405 return flow_cost, _build_flow_dict(G, R, capacity, weight)