Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/flow/networksimplex.py: 9%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

294 statements  

1""" 

2Minimum cost flow algorithms on directed connected graphs. 

3""" 

4 

5__all__ = ["network_simplex"] 

6 

7from itertools import chain, islice, repeat 

8from math import ceil, sqrt 

9 

10import networkx as nx 

11from networkx.utils import not_implemented_for 

12 

13 

14class _DataEssentialsAndFunctions: 

15 def __init__( 

16 self, G, multigraph, demand="demand", capacity="capacity", weight="weight" 

17 ): 

18 # Number all nodes and edges and hereafter reference them using ONLY their numbers 

19 self.node_list = list(G) # nodes 

20 self.node_indices = {u: i for i, u in enumerate(self.node_list)} # node indices 

21 self.node_demands = [ 

22 G.nodes[u].get(demand, 0) for u in self.node_list 

23 ] # node demands 

24 

25 self.edge_sources = [] # edge sources 

26 self.edge_targets = [] # edge targets 

27 if multigraph: 

28 self.edge_keys = [] # edge keys 

29 self.edge_indices = {} # edge indices 

30 self.edge_capacities = [] # edge capacities 

31 self.edge_weights = [] # edge weights 

32 

33 if not multigraph: 

34 edges = G.edges(data=True) 

35 else: 

36 edges = G.edges(data=True, keys=True) 

37 

38 inf = float("inf") 

39 edges = (e for e in edges if e[0] != e[1] and e[-1].get(capacity, inf) != 0) 

40 for i, e in enumerate(edges): 

41 self.edge_sources.append(self.node_indices[e[0]]) 

42 self.edge_targets.append(self.node_indices[e[1]]) 

43 if multigraph: 

44 self.edge_keys.append(e[2]) 

45 self.edge_indices[e[:-1]] = i 

46 self.edge_capacities.append(e[-1].get(capacity, inf)) 

47 self.edge_weights.append(e[-1].get(weight, 0)) 

48 

49 # spanning tree specific data to be initialized 

50 

51 self.edge_count = None # number of edges 

52 self.edge_flow = None # edge flows 

53 self.node_potentials = None # node potentials 

54 self.parent = None # parent nodes 

55 self.parent_edge = None # edges to parents 

56 self.subtree_size = None # subtree sizes 

57 self.next_node_dft = None # next nodes in depth-first thread 

58 self.prev_node_dft = None # previous nodes in depth-first thread 

59 self.last_descendent_dft = None # last descendants in depth-first thread 

60 self._spanning_tree_initialized = ( 

61 False # False until initialize_spanning_tree() is called 

62 ) 

63 

64 def initialize_spanning_tree(self, n, faux_inf): 

65 self.edge_count = len(self.edge_indices) # number of edges 

66 self.edge_flow = list( 

67 chain(repeat(0, self.edge_count), (abs(d) for d in self.node_demands)) 

68 ) # edge flows 

69 self.node_potentials = [ 

70 faux_inf if d <= 0 else -faux_inf for d in self.node_demands 

71 ] # node potentials 

72 self.parent = list(chain(repeat(-1, n), [None])) # parent nodes 

73 self.parent_edge = list( 

74 range(self.edge_count, self.edge_count + n) 

75 ) # edges to parents 

76 self.subtree_size = list(chain(repeat(1, n), [n + 1])) # subtree sizes 

77 self.next_node_dft = list( 

78 chain(range(1, n), [-1, 0]) 

79 ) # next nodes in depth-first thread 

80 self.prev_node_dft = list(range(-1, n)) # previous nodes in depth-first thread 

81 self.last_descendent_dft = list( 

82 chain(range(n), [n - 1]) 

83 ) # last descendants in depth-first thread 

84 self._spanning_tree_initialized = True # True only if all the assignments pass 

85 

86 def find_apex(self, p, q): 

87 """ 

88 Find the lowest common ancestor of nodes p and q in the spanning tree. 

89 """ 

90 size_p = self.subtree_size[p] 

91 size_q = self.subtree_size[q] 

92 while True: 

93 while size_p < size_q: 

94 p = self.parent[p] 

95 size_p = self.subtree_size[p] 

96 while size_p > size_q: 

97 q = self.parent[q] 

98 size_q = self.subtree_size[q] 

99 if size_p == size_q: 

100 if p != q: 

101 p = self.parent[p] 

102 size_p = self.subtree_size[p] 

103 q = self.parent[q] 

104 size_q = self.subtree_size[q] 

105 else: 

106 return p 

107 

108 def trace_path(self, p, w): 

109 """ 

110 Returns the nodes and edges on the path from node p to its ancestor w. 

111 """ 

112 Wn = [p] 

113 We = [] 

114 while p != w: 

115 We.append(self.parent_edge[p]) 

116 p = self.parent[p] 

117 Wn.append(p) 

118 return Wn, We 

119 

120 def find_cycle(self, i, p, q): 

121 """ 

122 Returns the nodes and edges on the cycle containing edge i == (p, q) 

123 when the latter is added to the spanning tree. 

124 

125 The cycle is oriented in the direction from p to q. 

126 """ 

127 w = self.find_apex(p, q) 

128 Wn, We = self.trace_path(p, w) 

129 Wn.reverse() 

130 We.reverse() 

131 if We != [i]: 

132 We.append(i) 

133 WnR, WeR = self.trace_path(q, w) 

134 del WnR[-1] 

135 Wn += WnR 

136 We += WeR 

137 return Wn, We 

138 

139 def augment_flow(self, Wn, We, f): 

140 """ 

141 Augment f units of flow along a cycle represented by Wn and We. 

142 """ 

143 for i, p in zip(We, Wn): 

144 if self.edge_sources[i] == p: 

145 self.edge_flow[i] += f 

146 else: 

147 self.edge_flow[i] -= f 

148 

149 def trace_subtree(self, p): 

150 """ 

151 Yield the nodes in the subtree rooted at a node p. 

152 """ 

153 yield p 

154 l = self.last_descendent_dft[p] 

155 while p != l: 

156 p = self.next_node_dft[p] 

157 yield p 

158 

159 def remove_edge(self, s, t): 

160 """ 

161 Remove an edge (s, t) where parent[t] == s from the spanning tree. 

162 """ 

163 size_t = self.subtree_size[t] 

164 prev_t = self.prev_node_dft[t] 

165 last_t = self.last_descendent_dft[t] 

166 next_last_t = self.next_node_dft[last_t] 

167 # Remove (s, t). 

168 self.parent[t] = None 

169 self.parent_edge[t] = None 

170 # Remove the subtree rooted at t from the depth-first thread. 

171 self.next_node_dft[prev_t] = next_last_t 

172 self.prev_node_dft[next_last_t] = prev_t 

173 self.next_node_dft[last_t] = t 

174 self.prev_node_dft[t] = last_t 

175 # Update the subtree sizes and last descendants of the (old) ancestors 

176 # of t. 

177 while s is not None: 

178 self.subtree_size[s] -= size_t 

179 if self.last_descendent_dft[s] == last_t: 

180 self.last_descendent_dft[s] = prev_t 

181 s = self.parent[s] 

182 

183 def make_root(self, q): 

184 """ 

185 Make a node q the root of its containing subtree. 

186 """ 

187 ancestors = [] 

188 while q is not None: 

189 ancestors.append(q) 

190 q = self.parent[q] 

191 ancestors.reverse() 

192 for p, q in zip(ancestors, islice(ancestors, 1, None)): 

193 size_p = self.subtree_size[p] 

194 last_p = self.last_descendent_dft[p] 

195 prev_q = self.prev_node_dft[q] 

196 last_q = self.last_descendent_dft[q] 

197 next_last_q = self.next_node_dft[last_q] 

198 # Make p a child of q. 

199 self.parent[p] = q 

200 self.parent[q] = None 

201 self.parent_edge[p] = self.parent_edge[q] 

202 self.parent_edge[q] = None 

203 self.subtree_size[p] = size_p - self.subtree_size[q] 

204 self.subtree_size[q] = size_p 

205 # Remove the subtree rooted at q from the depth-first thread. 

206 self.next_node_dft[prev_q] = next_last_q 

207 self.prev_node_dft[next_last_q] = prev_q 

208 self.next_node_dft[last_q] = q 

209 self.prev_node_dft[q] = last_q 

210 if last_p == last_q: 

211 self.last_descendent_dft[p] = prev_q 

212 last_p = prev_q 

213 # Add the remaining parts of the subtree rooted at p as a subtree 

214 # of q in the depth-first thread. 

215 self.prev_node_dft[p] = last_q 

216 self.next_node_dft[last_q] = p 

217 self.next_node_dft[last_p] = q 

218 self.prev_node_dft[q] = last_p 

219 self.last_descendent_dft[q] = last_p 

220 

221 def add_edge(self, i, p, q): 

222 """ 

223 Add an edge (p, q) to the spanning tree where q is the root of a subtree. 

224 """ 

225 last_p = self.last_descendent_dft[p] 

226 next_last_p = self.next_node_dft[last_p] 

227 size_q = self.subtree_size[q] 

228 last_q = self.last_descendent_dft[q] 

229 # Make q a child of p. 

230 self.parent[q] = p 

231 self.parent_edge[q] = i 

232 # Insert the subtree rooted at q into the depth-first thread. 

233 self.next_node_dft[last_p] = q 

234 self.prev_node_dft[q] = last_p 

235 self.prev_node_dft[next_last_p] = last_q 

236 self.next_node_dft[last_q] = next_last_p 

237 # Update the subtree sizes and last descendants of the (new) ancestors 

238 # of q. 

239 while p is not None: 

240 self.subtree_size[p] += size_q 

241 if self.last_descendent_dft[p] == last_p: 

242 self.last_descendent_dft[p] = last_q 

243 p = self.parent[p] 

244 

245 def update_potentials(self, i, p, q): 

246 """ 

247 Update the potentials of the nodes in the subtree rooted at a node 

248 q connected to its parent p by an edge i. 

249 """ 

250 if q == self.edge_targets[i]: 

251 d = self.node_potentials[p] - self.edge_weights[i] - self.node_potentials[q] 

252 else: 

253 d = self.node_potentials[p] + self.edge_weights[i] - self.node_potentials[q] 

254 for q in self.trace_subtree(q): 

255 self.node_potentials[q] += d 

256 

257 def reduced_cost(self, i): 

258 """Returns the reduced cost of an edge i.""" 

259 c = ( 

260 self.edge_weights[i] 

261 - self.node_potentials[self.edge_sources[i]] 

262 + self.node_potentials[self.edge_targets[i]] 

263 ) 

264 return c if self.edge_flow[i] == 0 else -c 

265 

266 def find_entering_edges(self): 

267 """Yield entering edges until none can be found.""" 

268 if self.edge_count == 0: 

269 return 

270 

271 # Entering edges are found by combining Dantzig's rule and Bland's 

272 # rule. The edges are cyclically grouped into blocks of size B. Within 

273 # each block, Dantzig's rule is applied to find an entering edge. The 

274 # blocks to search is determined following Bland's rule. 

275 B = int(ceil(sqrt(self.edge_count))) # pivot block size 

276 M = (self.edge_count + B - 1) // B # number of blocks needed to cover all edges 

277 m = 0 # number of consecutive blocks without eligible 

278 # entering edges 

279 f = 0 # first edge in block 

280 while m < M: 

281 # Determine the next block of edges. 

282 l = f + B 

283 if l <= self.edge_count: 

284 edges = range(f, l) 

285 else: 

286 l -= self.edge_count 

287 edges = chain(range(f, self.edge_count), range(l)) 

288 f = l 

289 # Find the first edge with the lowest reduced cost. 

290 i = min(edges, key=self.reduced_cost) 

291 c = self.reduced_cost(i) 

292 if c >= 0: 

293 # No entering edge found in the current block. 

294 m += 1 

295 else: 

296 # Entering edge found. 

297 if self.edge_flow[i] == 0: 

298 p = self.edge_sources[i] 

299 q = self.edge_targets[i] 

300 else: 

301 p = self.edge_targets[i] 

302 q = self.edge_sources[i] 

303 yield i, p, q 

304 m = 0 

305 # All edges have nonnegative reduced costs. The current flow is 

306 # optimal. 

307 

308 def residual_capacity(self, i, p): 

309 """Returns the residual capacity of an edge i in the direction away 

310 from its endpoint p. 

311 """ 

312 return ( 

313 self.edge_capacities[i] - self.edge_flow[i] 

314 if self.edge_sources[i] == p 

315 else self.edge_flow[i] 

316 ) 

317 

318 def find_leaving_edge(self, Wn, We): 

319 """Returns the leaving edge in a cycle represented by Wn and We.""" 

320 j, s = min( 

321 zip(reversed(We), reversed(Wn)), 

322 key=lambda i_p: self.residual_capacity(*i_p), 

323 ) 

324 t = self.edge_targets[j] if self.edge_sources[j] == s else self.edge_sources[j] 

325 return j, s, t 

326 

327 

328@not_implemented_for("undirected") 

329@nx._dispatchable( 

330 node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0} 

331) 

332def network_simplex(G, demand="demand", capacity="capacity", weight="weight"): 

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

334 

335 This is a primal network simplex algorithm that uses the leaving 

336 arc rule to prevent cycling. 

337 

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

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

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

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

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

343 is equal to the demand of that node. 

344 

345 Parameters 

346 ---------- 

347 G : NetworkX graph 

348 DiGraph on which a minimum cost flow satisfying all demands is 

349 to be found. 

350 

351 demand : string 

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

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

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

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

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

357 demand. Default value: 'demand'. 

358 

359 capacity : string 

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

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

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

363 infinite capacity. Default value: 'capacity'. 

364 

365 weight : string 

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

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

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

369 Default value: 'weight'. 

370 

371 Returns 

372 ------- 

373 flowCost : integer, float 

374 Cost of a minimum cost flow satisfying all demands. 

375 

376 flowDict : dictionary 

377 Dictionary of dictionaries keyed by nodes such that 

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

379 

380 Raises 

381 ------ 

382 NetworkXError 

383 This exception is raised if the input graph is not directed or 

384 not connected. 

385 

386 NetworkXUnfeasible 

387 This exception is raised in the following situations: 

388 

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

390 flow satisfying all demands. 

391 * There is no flow satisfying all demand. 

392 

393 NetworkXUnbounded 

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

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

396 satisfying all demands is unbounded below. 

397 

398 Notes 

399 ----- 

400 This algorithm is not guaranteed to work if edge weights or demands 

401 are floating point numbers (overflows and roundoff errors can 

402 cause problems). As a workaround you can use integer numbers by 

403 multiplying the relevant edge attributes by a convenient 

404 constant factor (eg 100). 

405 

406 See also 

407 -------- 

408 cost_of_flow, max_flow_min_cost, min_cost_flow, min_cost_flow_cost 

409 

410 Examples 

411 -------- 

412 A simple example of a min cost flow problem. 

413 

414 >>> G = nx.DiGraph() 

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

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

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

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

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

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

421 >>> flowCost, flowDict = nx.network_simplex(G) 

422 >>> flowCost 

423 24 

424 >>> flowDict 

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

426 

427 The mincost flow algorithm can also be used to solve shortest path 

428 problems. To find the shortest path between two nodes u and v, 

429 give all edges an infinite capacity, give node u a demand of -1 and 

430 node v a demand a 1. Then run the network simplex. The value of a 

431 min cost flow will be the distance between u and v and edges 

432 carrying positive flow will indicate the path. 

433 

434 >>> G = nx.DiGraph() 

435 >>> G.add_weighted_edges_from( 

436 ... [ 

437 ... ("s", "u", 10), 

438 ... ("s", "x", 5), 

439 ... ("u", "v", 1), 

440 ... ("u", "x", 2), 

441 ... ("v", "y", 1), 

442 ... ("x", "u", 3), 

443 ... ("x", "v", 5), 

444 ... ("x", "y", 2), 

445 ... ("y", "s", 7), 

446 ... ("y", "v", 6), 

447 ... ] 

448 ... ) 

449 >>> G.add_node("s", demand=-1) 

450 >>> G.add_node("v", demand=1) 

451 >>> flowCost, flowDict = nx.network_simplex(G) 

452 >>> flowCost == nx.shortest_path_length(G, "s", "v", weight="weight") 

453 True 

454 >>> sorted([(u, v) for u in flowDict for v in flowDict[u] if flowDict[u][v] > 0]) 

455 [('s', 'x'), ('u', 'v'), ('x', 'u')] 

456 >>> nx.shortest_path(G, "s", "v", weight="weight") 

457 ['s', 'x', 'u', 'v'] 

458 

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

460 algorithm. 

461 

462 >>> G = nx.DiGraph() 

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

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

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

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

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

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

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

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

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

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

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

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

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

476 >>> flowCost, flowDict = nx.network_simplex( 

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

478 ... ) 

479 >>> flowCost 

480 37 

481 >>> flowDict 

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

483 

484 References 

485 ---------- 

486 .. [1] Z. Kiraly, P. Kovacs. 

487 Efficient implementation of minimum-cost flow algorithms. 

488 Acta Universitatis Sapientiae, Informatica 4(1):67--118. 2012. 

489 .. [2] R. Barr, F. Glover, D. Klingman. 

490 Enhancement of spanning tree labeling procedures for network 

491 optimization. 

492 INFOR 17(1):16--34. 1979. 

493 """ 

494 ########################################################################### 

495 # Problem essentials extraction and sanity check 

496 ########################################################################### 

497 

498 if len(G) == 0: 

499 raise nx.NetworkXError("graph has no nodes") 

500 

501 multigraph = G.is_multigraph() 

502 

503 # extracting data essential to problem 

504 DEAF = _DataEssentialsAndFunctions( 

505 G, multigraph, demand=demand, capacity=capacity, weight=weight 

506 ) 

507 

508 ########################################################################### 

509 # Quick Error Detection 

510 ########################################################################### 

511 

512 inf = float("inf") 

513 for u, d in zip(DEAF.node_list, DEAF.node_demands): 

514 if abs(d) == inf: 

515 raise nx.NetworkXError(f"node {u!r} has infinite demand") 

516 for e, w in zip(DEAF.edge_indices, DEAF.edge_weights): 

517 if abs(w) == inf: 

518 raise nx.NetworkXError(f"edge {e!r} has infinite weight") 

519 if not multigraph: 

520 edges = nx.selfloop_edges(G, data=True) 

521 else: 

522 edges = nx.selfloop_edges(G, data=True, keys=True) 

523 for e in edges: 

524 if abs(e[-1].get(weight, 0)) == inf: 

525 raise nx.NetworkXError(f"edge {e[:-1]!r} has infinite weight") 

526 

527 ########################################################################### 

528 # Quick Infeasibility Detection 

529 ########################################################################### 

530 

531 if sum(DEAF.node_demands) != 0: 

532 raise nx.NetworkXUnfeasible("total node demand is not zero") 

533 for e, c in zip(DEAF.edge_indices, DEAF.edge_capacities): 

534 if c < 0: 

535 raise nx.NetworkXUnfeasible(f"edge {e!r} has negative capacity") 

536 if not multigraph: 

537 edges = nx.selfloop_edges(G, data=True) 

538 else: 

539 edges = nx.selfloop_edges(G, data=True, keys=True) 

540 for e in edges: 

541 if e[-1].get(capacity, inf) < 0: 

542 raise nx.NetworkXUnfeasible(f"edge {e[:-1]!r} has negative capacity") 

543 

544 ########################################################################### 

545 # Initialization 

546 ########################################################################### 

547 

548 # Add a dummy node -1 and connect all existing nodes to it with infinite- 

549 # capacity dummy edges. Node -1 will serve as the root of the 

550 # spanning tree of the network simplex method. The new edges will used to 

551 # trivially satisfy the node demands and create an initial strongly 

552 # feasible spanning tree. 

553 for i, d in enumerate(DEAF.node_demands): 

554 # Must be greater-than here. Zero-demand nodes must have 

555 # edges pointing towards the root to ensure strong feasibility. 

556 if d > 0: 

557 DEAF.edge_sources.append(-1) 

558 DEAF.edge_targets.append(i) 

559 else: 

560 DEAF.edge_sources.append(i) 

561 DEAF.edge_targets.append(-1) 

562 faux_inf = ( 

563 3 

564 * max( 

565 sum(c for c in DEAF.edge_capacities if c < inf), 

566 sum(abs(w) for w in DEAF.edge_weights), 

567 sum(abs(d) for d in DEAF.node_demands), 

568 ) 

569 or 1 

570 ) 

571 

572 n = len(DEAF.node_list) # number of nodes 

573 DEAF.edge_weights.extend(repeat(faux_inf, n)) 

574 DEAF.edge_capacities.extend(repeat(faux_inf, n)) 

575 

576 # Construct the initial spanning tree. 

577 DEAF.initialize_spanning_tree(n, faux_inf) 

578 

579 ########################################################################### 

580 # Pivot loop 

581 ########################################################################### 

582 

583 for i, p, q in DEAF.find_entering_edges(): 

584 Wn, We = DEAF.find_cycle(i, p, q) 

585 j, s, t = DEAF.find_leaving_edge(Wn, We) 

586 DEAF.augment_flow(Wn, We, DEAF.residual_capacity(j, s)) 

587 # Do nothing more if the entering edge is the same as the leaving edge. 

588 if i != j: 

589 if DEAF.parent[t] != s: 

590 # Ensure that s is the parent of t. 

591 s, t = t, s 

592 if We.index(i) > We.index(j): 

593 # Ensure that q is in the subtree rooted at t. 

594 p, q = q, p 

595 DEAF.remove_edge(s, t) 

596 DEAF.make_root(q) 

597 DEAF.add_edge(i, p, q) 

598 DEAF.update_potentials(i, p, q) 

599 

600 ########################################################################### 

601 # Infeasibility and unboundedness detection 

602 ########################################################################### 

603 

604 if any(DEAF.edge_flow[i] != 0 for i in range(-n, 0)): 

605 raise nx.NetworkXUnfeasible("no flow satisfies all node demands") 

606 

607 if any(DEAF.edge_flow[i] * 2 >= faux_inf for i in range(DEAF.edge_count)) or any( 

608 e[-1].get(capacity, inf) == inf and e[-1].get(weight, 0) < 0 

609 for e in nx.selfloop_edges(G, data=True) 

610 ): 

611 raise nx.NetworkXUnbounded("negative cycle with infinite capacity found") 

612 

613 ########################################################################### 

614 # Flow cost calculation and flow dict construction 

615 ########################################################################### 

616 

617 del DEAF.edge_flow[DEAF.edge_count :] 

618 flow_cost = sum(w * x for w, x in zip(DEAF.edge_weights, DEAF.edge_flow)) 

619 flow_dict = {n: {} for n in DEAF.node_list} 

620 

621 def add_entry(e): 

622 """Add a flow dict entry.""" 

623 d = flow_dict[e[0]] 

624 for k in e[1:-2]: 

625 try: 

626 d = d[k] 

627 except KeyError: 

628 t = {} 

629 d[k] = t 

630 d = t 

631 d[e[-2]] = e[-1] 

632 

633 DEAF.edge_sources = ( 

634 DEAF.node_list[s] for s in DEAF.edge_sources 

635 ) # Use original nodes. 

636 DEAF.edge_targets = ( 

637 DEAF.node_list[t] for t in DEAF.edge_targets 

638 ) # Use original nodes. 

639 if not multigraph: 

640 for e in zip(DEAF.edge_sources, DEAF.edge_targets, DEAF.edge_flow): 

641 add_entry(e) 

642 edges = G.edges(data=True) 

643 else: 

644 for e in zip( 

645 DEAF.edge_sources, DEAF.edge_targets, DEAF.edge_keys, DEAF.edge_flow 

646 ): 

647 add_entry(e) 

648 edges = G.edges(data=True, keys=True) 

649 for e in edges: 

650 if e[0] != e[1]: 

651 if e[-1].get(capacity, inf) == 0: 

652 add_entry(e[:-1] + (0,)) 

653 else: 

654 w = e[-1].get(weight, 0) 

655 if w >= 0: 

656 add_entry(e[:-1] + (0,)) 

657 else: 

658 c = e[-1][capacity] 

659 flow_cost += w * c 

660 add_entry(e[:-1] + (c,)) 

661 

662 return flow_cost, flow_dict