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

173 statements  

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

1""" 

2Highest-label preflow-push algorithm for maximum flow problems. 

3""" 

4 

5from collections import deque 

6from itertools import islice 

7 

8import networkx as nx 

9 

10from ...utils import arbitrary_element 

11from .utils import ( 

12 CurrentEdge, 

13 GlobalRelabelThreshold, 

14 Level, 

15 build_residual_network, 

16 detect_unboundedness, 

17) 

18 

19__all__ = ["preflow_push"] 

20 

21 

22def preflow_push_impl(G, s, t, capacity, residual, global_relabel_freq, value_only): 

23 """Implementation of the highest-label preflow-push algorithm.""" 

24 if s not in G: 

25 raise nx.NetworkXError(f"node {str(s)} not in graph") 

26 if t not in G: 

27 raise nx.NetworkXError(f"node {str(t)} not in graph") 

28 if s == t: 

29 raise nx.NetworkXError("source and sink are the same node") 

30 

31 if global_relabel_freq is None: 

32 global_relabel_freq = 0 

33 if global_relabel_freq < 0: 

34 raise nx.NetworkXError("global_relabel_freq must be nonnegative.") 

35 

36 if residual is None: 

37 R = build_residual_network(G, capacity) 

38 else: 

39 R = residual 

40 

41 detect_unboundedness(R, s, t) 

42 

43 R_nodes = R.nodes 

44 R_pred = R.pred 

45 R_succ = R.succ 

46 

47 # Initialize/reset the residual network. 

48 for u in R: 

49 R_nodes[u]["excess"] = 0 

50 for e in R_succ[u].values(): 

51 e["flow"] = 0 

52 

53 def reverse_bfs(src): 

54 """Perform a reverse breadth-first search from src in the residual 

55 network. 

56 """ 

57 heights = {src: 0} 

58 q = deque([(src, 0)]) 

59 while q: 

60 u, height = q.popleft() 

61 height += 1 

62 for v, attr in R_pred[u].items(): 

63 if v not in heights and attr["flow"] < attr["capacity"]: 

64 heights[v] = height 

65 q.append((v, height)) 

66 return heights 

67 

68 # Initialize heights of the nodes. 

69 heights = reverse_bfs(t) 

70 

71 if s not in heights: 

72 # t is not reachable from s in the residual network. The maximum flow 

73 # must be zero. 

74 R.graph["flow_value"] = 0 

75 return R 

76 

77 n = len(R) 

78 # max_height represents the height of the highest level below level n with 

79 # at least one active node. 

80 max_height = max(heights[u] for u in heights if u != s) 

81 heights[s] = n 

82 

83 grt = GlobalRelabelThreshold(n, R.size(), global_relabel_freq) 

84 

85 # Initialize heights and 'current edge' data structures of the nodes. 

86 for u in R: 

87 R_nodes[u]["height"] = heights[u] if u in heights else n + 1 

88 R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u]) 

89 

90 def push(u, v, flow): 

91 """Push flow units of flow from u to v.""" 

92 R_succ[u][v]["flow"] += flow 

93 R_succ[v][u]["flow"] -= flow 

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

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

96 

97 # The maximum flow must be nonzero now. Initialize the preflow by 

98 # saturating all edges emanating from s. 

99 for u, attr in R_succ[s].items(): 

100 flow = attr["capacity"] 

101 if flow > 0: 

102 push(s, u, flow) 

103 

104 # Partition nodes into levels. 

105 levels = [Level() for i in range(2 * n)] 

106 for u in R: 

107 if u != s and u != t: 

108 level = levels[R_nodes[u]["height"]] 

109 if R_nodes[u]["excess"] > 0: 

110 level.active.add(u) 

111 else: 

112 level.inactive.add(u) 

113 

114 def activate(v): 

115 """Move a node from the inactive set to the active set of its level.""" 

116 if v != s and v != t: 

117 level = levels[R_nodes[v]["height"]] 

118 if v in level.inactive: 

119 level.inactive.remove(v) 

120 level.active.add(v) 

121 

122 def relabel(u): 

123 """Relabel a node to create an admissible edge.""" 

124 grt.add_work(len(R_succ[u])) 

125 return ( 

126 min( 

127 R_nodes[v]["height"] 

128 for v, attr in R_succ[u].items() 

129 if attr["flow"] < attr["capacity"] 

130 ) 

131 + 1 

132 ) 

133 

134 def discharge(u, is_phase1): 

135 """Discharge a node until it becomes inactive or, during phase 1 (see 

136 below), its height reaches at least n. The node is known to have the 

137 largest height among active nodes. 

138 """ 

139 height = R_nodes[u]["height"] 

140 curr_edge = R_nodes[u]["curr_edge"] 

141 # next_height represents the next height to examine after discharging 

142 # the current node. During phase 1, it is capped to below n. 

143 next_height = height 

144 levels[height].active.remove(u) 

145 while True: 

146 v, attr = curr_edge.get() 

147 if height == R_nodes[v]["height"] + 1 and attr["flow"] < attr["capacity"]: 

148 flow = min(R_nodes[u]["excess"], attr["capacity"] - attr["flow"]) 

149 push(u, v, flow) 

150 activate(v) 

151 if R_nodes[u]["excess"] == 0: 

152 # The node has become inactive. 

153 levels[height].inactive.add(u) 

154 break 

155 try: 

156 curr_edge.move_to_next() 

157 except StopIteration: 

158 # We have run off the end of the adjacency list, and there can 

159 # be no more admissible edges. Relabel the node to create one. 

160 height = relabel(u) 

161 if is_phase1 and height >= n - 1: 

162 # Although the node is still active, with a height at least 

163 # n - 1, it is now known to be on the s side of the minimum 

164 # s-t cut. Stop processing it until phase 2. 

165 levels[height].active.add(u) 

166 break 

167 # The first relabel operation after global relabeling may not 

168 # increase the height of the node since the 'current edge' data 

169 # structure is not rewound. Use height instead of (height - 1) 

170 # in case other active nodes at the same level are missed. 

171 next_height = height 

172 R_nodes[u]["height"] = height 

173 return next_height 

174 

175 def gap_heuristic(height): 

176 """Apply the gap heuristic.""" 

177 # Move all nodes at levels (height + 1) to max_height to level n + 1. 

178 for level in islice(levels, height + 1, max_height + 1): 

179 for u in level.active: 

180 R_nodes[u]["height"] = n + 1 

181 for u in level.inactive: 

182 R_nodes[u]["height"] = n + 1 

183 levels[n + 1].active.update(level.active) 

184 level.active.clear() 

185 levels[n + 1].inactive.update(level.inactive) 

186 level.inactive.clear() 

187 

188 def global_relabel(from_sink): 

189 """Apply the global relabeling heuristic.""" 

190 src = t if from_sink else s 

191 heights = reverse_bfs(src) 

192 if not from_sink: 

193 # s must be reachable from t. Remove t explicitly. 

194 del heights[t] 

195 max_height = max(heights.values()) 

196 if from_sink: 

197 # Also mark nodes from which t is unreachable for relabeling. This 

198 # serves the same purpose as the gap heuristic. 

199 for u in R: 

200 if u not in heights and R_nodes[u]["height"] < n: 

201 heights[u] = n + 1 

202 else: 

203 # Shift the computed heights because the height of s is n. 

204 for u in heights: 

205 heights[u] += n 

206 max_height += n 

207 del heights[src] 

208 for u, new_height in heights.items(): 

209 old_height = R_nodes[u]["height"] 

210 if new_height != old_height: 

211 if u in levels[old_height].active: 

212 levels[old_height].active.remove(u) 

213 levels[new_height].active.add(u) 

214 else: 

215 levels[old_height].inactive.remove(u) 

216 levels[new_height].inactive.add(u) 

217 R_nodes[u]["height"] = new_height 

218 return max_height 

219 

220 # Phase 1: Find the maximum preflow by pushing as much flow as possible to 

221 # t. 

222 

223 height = max_height 

224 while height > 0: 

225 # Discharge active nodes in the current level. 

226 while True: 

227 level = levels[height] 

228 if not level.active: 

229 # All active nodes in the current level have been discharged. 

230 # Move to the next lower level. 

231 height -= 1 

232 break 

233 # Record the old height and level for the gap heuristic. 

234 old_height = height 

235 old_level = level 

236 u = arbitrary_element(level.active) 

237 height = discharge(u, True) 

238 if grt.is_reached(): 

239 # Global relabeling heuristic: Recompute the exact heights of 

240 # all nodes. 

241 height = global_relabel(True) 

242 max_height = height 

243 grt.clear_work() 

244 elif not old_level.active and not old_level.inactive: 

245 # Gap heuristic: If the level at old_height is empty (a 'gap'), 

246 # a minimum cut has been identified. All nodes with heights 

247 # above old_height can have their heights set to n + 1 and not 

248 # be further processed before a maximum preflow is found. 

249 gap_heuristic(old_height) 

250 height = old_height - 1 

251 max_height = height 

252 else: 

253 # Update the height of the highest level with at least one 

254 # active node. 

255 max_height = max(max_height, height) 

256 

257 # A maximum preflow has been found. The excess at t is the maximum flow 

258 # value. 

259 if value_only: 

260 R.graph["flow_value"] = R_nodes[t]["excess"] 

261 return R 

262 

263 # Phase 2: Convert the maximum preflow into a maximum flow by returning the 

264 # excess to s. 

265 

266 # Relabel all nodes so that they have accurate heights. 

267 height = global_relabel(False) 

268 grt.clear_work() 

269 

270 # Continue to discharge the active nodes. 

271 while height > n: 

272 # Discharge active nodes in the current level. 

273 while True: 

274 level = levels[height] 

275 if not level.active: 

276 # All active nodes in the current level have been discharged. 

277 # Move to the next lower level. 

278 height -= 1 

279 break 

280 u = arbitrary_element(level.active) 

281 height = discharge(u, False) 

282 if grt.is_reached(): 

283 # Global relabeling heuristic. 

284 height = global_relabel(False) 

285 grt.clear_work() 

286 

287 R.graph["flow_value"] = R_nodes[t]["excess"] 

288 return R 

289 

290 

291@nx._dispatch( 

292 graphs={"G": 0, "residual?": 4}, 

293 edge_attrs={"capacity": float("inf")}, 

294 preserve_edge_attrs={"residual": {"capacity": float("inf")}}, 

295 preserve_graph_attrs={"residual"}, 

296) 

297def preflow_push( 

298 G, s, t, capacity="capacity", residual=None, global_relabel_freq=1, value_only=False 

299): 

300 r"""Find a maximum single-commodity flow using the highest-label 

301 preflow-push algorithm. 

302 

303 This function returns the residual network resulting after computing 

304 the maximum flow. See below for details about the conventions 

305 NetworkX uses for defining residual networks. 

306 

307 This algorithm has a running time of $O(n^2 \sqrt{m})$ for $n$ nodes and 

308 $m$ edges. 

309 

310 

311 Parameters 

312 ---------- 

313 G : NetworkX graph 

314 Edges of the graph are expected to have an attribute called 

315 'capacity'. If this attribute is not present, the edge is 

316 considered to have infinite capacity. 

317 

318 s : node 

319 Source node for the flow. 

320 

321 t : node 

322 Sink node for the flow. 

323 

324 capacity : string 

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

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

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

328 infinite capacity. Default value: 'capacity'. 

329 

330 residual : NetworkX graph 

331 Residual network on which the algorithm is to be executed. If None, a 

332 new residual network is created. Default value: None. 

333 

334 global_relabel_freq : integer, float 

335 Relative frequency of applying the global relabeling heuristic to speed 

336 up the algorithm. If it is None, the heuristic is disabled. Default 

337 value: 1. 

338 

339 value_only : bool 

340 If False, compute a maximum flow; otherwise, compute a maximum preflow 

341 which is enough for computing the maximum flow value. Default value: 

342 False. 

343 

344 Returns 

345 ------- 

346 R : NetworkX DiGraph 

347 Residual network after computing the maximum flow. 

348 

349 Raises 

350 ------ 

351 NetworkXError 

352 The algorithm does not support MultiGraph and MultiDiGraph. If 

353 the input graph is an instance of one of these two classes, a 

354 NetworkXError is raised. 

355 

356 NetworkXUnbounded 

357 If the graph has a path of infinite capacity, the value of a 

358 feasible flow on the graph is unbounded above and the function 

359 raises a NetworkXUnbounded. 

360 

361 See also 

362 -------- 

363 :meth:`maximum_flow` 

364 :meth:`minimum_cut` 

365 :meth:`edmonds_karp` 

366 :meth:`shortest_augmenting_path` 

367 

368 Notes 

369 ----- 

370 The residual network :samp:`R` from an input graph :samp:`G` has the 

371 same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair 

372 of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a 

373 self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists 

374 in :samp:`G`. For each node :samp:`u` in :samp:`R`, 

375 :samp:`R.nodes[u]['excess']` represents the difference between flow into 

376 :samp:`u` and flow out of :samp:`u`. 

377 

378 For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']` 

379 is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists 

380 in :samp:`G` or zero otherwise. If the capacity is infinite, 

381 :samp:`R[u][v]['capacity']` will have a high arbitrary finite value 

382 that does not affect the solution of the problem. This value is stored in 

383 :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`, 

384 :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and 

385 satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`. 

386 

387 The flow value, defined as the total flow into :samp:`t`, the sink, is 

388 stored in :samp:`R.graph['flow_value']`. Reachability to :samp:`t` using 

389 only edges :samp:`(u, v)` such that 

390 :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum 

391 :samp:`s`-:samp:`t` cut. 

392 

393 Examples 

394 -------- 

395 >>> from networkx.algorithms.flow import preflow_push 

396 

397 The functions that implement flow algorithms and output a residual 

398 network, such as this one, are not imported to the base NetworkX 

399 namespace, so you have to explicitly import them from the flow package. 

400 

401 >>> G = nx.DiGraph() 

402 >>> G.add_edge("x", "a", capacity=3.0) 

403 >>> G.add_edge("x", "b", capacity=1.0) 

404 >>> G.add_edge("a", "c", capacity=3.0) 

405 >>> G.add_edge("b", "c", capacity=5.0) 

406 >>> G.add_edge("b", "d", capacity=4.0) 

407 >>> G.add_edge("d", "e", capacity=2.0) 

408 >>> G.add_edge("c", "y", capacity=2.0) 

409 >>> G.add_edge("e", "y", capacity=3.0) 

410 >>> R = preflow_push(G, "x", "y") 

411 >>> flow_value = nx.maximum_flow_value(G, "x", "y") 

412 >>> flow_value == R.graph["flow_value"] 

413 True 

414 >>> # preflow_push also stores the maximum flow value 

415 >>> # in the excess attribute of the sink node t 

416 >>> flow_value == R.nodes["y"]["excess"] 

417 True 

418 >>> # For some problems, you might only want to compute a 

419 >>> # maximum preflow. 

420 >>> R = preflow_push(G, "x", "y", value_only=True) 

421 >>> flow_value == R.graph["flow_value"] 

422 True 

423 >>> flow_value == R.nodes["y"]["excess"] 

424 True 

425 

426 """ 

427 R = preflow_push_impl(G, s, t, capacity, residual, global_relabel_freq, value_only) 

428 R.graph["algorithm"] = "preflow_push" 

429 return R