comparison env/lib/python3.9/site-packages/networkx/algorithms/flow/preflowpush.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """
2 Highest-label preflow-push algorithm for maximum flow problems.
3 """
4
5 from collections import deque
6 from itertools import islice
7 import networkx as nx
8 from ...utils import arbitrary_element
9 from .utils import build_residual_network
10 from .utils import CurrentEdge
11 from .utils import detect_unboundedness
12 from .utils import GlobalRelabelThreshold
13 from .utils import Level
14
15 __all__ = ["preflow_push"]
16
17
18 def preflow_push_impl(G, s, t, capacity, residual, global_relabel_freq, value_only):
19 """Implementation of the highest-label preflow-push algorithm.
20 """
21 if s not in G:
22 raise nx.NetworkXError(f"node {str(s)} not in graph")
23 if t not in G:
24 raise nx.NetworkXError(f"node {str(t)} not in graph")
25 if s == t:
26 raise nx.NetworkXError("source and sink are the same node")
27
28 if global_relabel_freq is None:
29 global_relabel_freq = 0
30 if global_relabel_freq < 0:
31 raise nx.NetworkXError("global_relabel_freq must be nonnegative.")
32
33 if residual is None:
34 R = build_residual_network(G, capacity)
35 else:
36 R = residual
37
38 detect_unboundedness(R, s, t)
39
40 R_nodes = R.nodes
41 R_pred = R.pred
42 R_succ = R.succ
43
44 # Initialize/reset the residual network.
45 for u in R:
46 R_nodes[u]["excess"] = 0
47 for e in R_succ[u].values():
48 e["flow"] = 0
49
50 def reverse_bfs(src):
51 """Perform a reverse breadth-first search from src in the residual
52 network.
53 """
54 heights = {src: 0}
55 q = deque([(src, 0)])
56 while q:
57 u, height = q.popleft()
58 height += 1
59 for v, attr in R_pred[u].items():
60 if v not in heights and attr["flow"] < attr["capacity"]:
61 heights[v] = height
62 q.append((v, height))
63 return heights
64
65 # Initialize heights of the nodes.
66 heights = reverse_bfs(t)
67
68 if s not in heights:
69 # t is not reachable from s in the residual network. The maximum flow
70 # must be zero.
71 R.graph["flow_value"] = 0
72 return R
73
74 n = len(R)
75 # max_height represents the height of the highest level below level n with
76 # at least one active node.
77 max_height = max(heights[u] for u in heights if u != s)
78 heights[s] = n
79
80 grt = GlobalRelabelThreshold(n, R.size(), global_relabel_freq)
81
82 # Initialize heights and 'current edge' data structures of the nodes.
83 for u in R:
84 R_nodes[u]["height"] = heights[u] if u in heights else n + 1
85 R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u])
86
87 def push(u, v, flow):
88 """Push flow units of flow from u to v.
89 """
90 R_succ[u][v]["flow"] += flow
91 R_succ[v][u]["flow"] -= flow
92 R_nodes[u]["excess"] -= flow
93 R_nodes[v]["excess"] += flow
94
95 # The maximum flow must be nonzero now. Initialize the preflow by
96 # saturating all edges emanating from s.
97 for u, attr in R_succ[s].items():
98 flow = attr["capacity"]
99 if flow > 0:
100 push(s, u, flow)
101
102 # Partition nodes into levels.
103 levels = [Level() for i in range(2 * n)]
104 for u in R:
105 if u != s and u != t:
106 level = levels[R_nodes[u]["height"]]
107 if R_nodes[u]["excess"] > 0:
108 level.active.add(u)
109 else:
110 level.inactive.add(u)
111
112 def activate(v):
113 """Move a node from the inactive set to the active set of its level.
114 """
115 if v != s and v != t:
116 level = levels[R_nodes[v]["height"]]
117 if v in level.inactive:
118 level.inactive.remove(v)
119 level.active.add(v)
120
121 def relabel(u):
122 """Relabel a node to create an admissible edge.
123 """
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 """
178 # Move all nodes at levels (height + 1) to max_height to level n + 1.
179 for level in islice(levels, height + 1, max_height + 1):
180 for u in level.active:
181 R_nodes[u]["height"] = n + 1
182 for u in level.inactive:
183 R_nodes[u]["height"] = n + 1
184 levels[n + 1].active.update(level.active)
185 level.active.clear()
186 levels[n + 1].inactive.update(level.inactive)
187 level.inactive.clear()
188
189 def global_relabel(from_sink):
190 """Apply the global relabeling heuristic.
191 """
192 src = t if from_sink else s
193 heights = reverse_bfs(src)
194 if not from_sink:
195 # s must be reachable from t. Remove t explicitly.
196 del heights[t]
197 max_height = max(heights.values())
198 if from_sink:
199 # Also mark nodes from which t is unreachable for relabeling. This
200 # serves the same purpose as the gap heuristic.
201 for u in R:
202 if u not in heights and R_nodes[u]["height"] < n:
203 heights[u] = n + 1
204 else:
205 # Shift the computed heights because the height of s is n.
206 for u in heights:
207 heights[u] += n
208 max_height += n
209 del heights[src]
210 for u, new_height in heights.items():
211 old_height = R_nodes[u]["height"]
212 if new_height != old_height:
213 if u in levels[old_height].active:
214 levels[old_height].active.remove(u)
215 levels[new_height].active.add(u)
216 else:
217 levels[old_height].inactive.remove(u)
218 levels[new_height].inactive.add(u)
219 R_nodes[u]["height"] = new_height
220 return max_height
221
222 # Phase 1: Find the maximum preflow by pushing as much flow as possible to
223 # t.
224
225 height = max_height
226 while height > 0:
227 # Discharge active nodes in the current level.
228 while True:
229 level = levels[height]
230 if not level.active:
231 # All active nodes in the current level have been discharged.
232 # Move to the next lower level.
233 height -= 1
234 break
235 # Record the old height and level for the gap heuristic.
236 old_height = height
237 old_level = level
238 u = arbitrary_element(level.active)
239 height = discharge(u, True)
240 if grt.is_reached():
241 # Global relabeling heuristic: Recompute the exact heights of
242 # all nodes.
243 height = global_relabel(True)
244 max_height = height
245 grt.clear_work()
246 elif not old_level.active and not old_level.inactive:
247 # Gap heuristic: If the level at old_height is empty (a 'gap'),
248 # a minimum cut has been identified. All nodes with heights
249 # above old_height can have their heights set to n + 1 and not
250 # be further processed before a maximum preflow is found.
251 gap_heuristic(old_height)
252 height = old_height - 1
253 max_height = height
254 else:
255 # Update the height of the highest level with at least one
256 # active node.
257 max_height = max(max_height, height)
258
259 # A maximum preflow has been found. The excess at t is the maximum flow
260 # value.
261 if value_only:
262 R.graph["flow_value"] = R_nodes[t]["excess"]
263 return R
264
265 # Phase 2: Convert the maximum preflow into a maximum flow by returning the
266 # excess to s.
267
268 # Relabel all nodes so that they have accurate heights.
269 height = global_relabel(False)
270 grt.clear_work()
271
272 # Continue to discharge the active nodes.
273 while height > n:
274 # Discharge active nodes in the current level.
275 while True:
276 level = levels[height]
277 if not level.active:
278 # All active nodes in the current level have been discharged.
279 # Move to the next lower level.
280 height -= 1
281 break
282 u = arbitrary_element(level.active)
283 height = discharge(u, False)
284 if grt.is_reached():
285 # Global relabeling heuristic.
286 height = global_relabel(False)
287 grt.clear_work()
288
289 R.graph["flow_value"] = R_nodes[t]["excess"]
290 return R
291
292
293 def preflow_push(
294 G, s, t, capacity="capacity", residual=None, global_relabel_freq=1, value_only=False
295 ):
296 r"""Find a maximum single-commodity flow using the highest-label
297 preflow-push algorithm.
298
299 This function returns the residual network resulting after computing
300 the maximum flow. See below for details about the conventions
301 NetworkX uses for defining residual networks.
302
303 This algorithm has a running time of $O(n^2 \sqrt{m})$ for $n$ nodes and
304 $m$ edges.
305
306
307 Parameters
308 ----------
309 G : NetworkX graph
310 Edges of the graph are expected to have an attribute called
311 'capacity'. If this attribute is not present, the edge is
312 considered to have infinite capacity.
313
314 s : node
315 Source node for the flow.
316
317 t : node
318 Sink node for the flow.
319
320 capacity : string
321 Edges of the graph G are expected to have an attribute capacity
322 that indicates how much flow the edge can support. If this
323 attribute is not present, the edge is considered to have
324 infinite capacity. Default value: 'capacity'.
325
326 residual : NetworkX graph
327 Residual network on which the algorithm is to be executed. If None, a
328 new residual network is created. Default value: None.
329
330 global_relabel_freq : integer, float
331 Relative frequency of applying the global relabeling heuristic to speed
332 up the algorithm. If it is None, the heuristic is disabled. Default
333 value: 1.
334
335 value_only : bool
336 If False, compute a maximum flow; otherwise, compute a maximum preflow
337 which is enough for computing the maximum flow value. Default value:
338 False.
339
340 Returns
341 -------
342 R : NetworkX DiGraph
343 Residual network after computing the maximum flow.
344
345 Raises
346 ------
347 NetworkXError
348 The algorithm does not support MultiGraph and MultiDiGraph. If
349 the input graph is an instance of one of these two classes, a
350 NetworkXError is raised.
351
352 NetworkXUnbounded
353 If the graph has a path of infinite capacity, the value of a
354 feasible flow on the graph is unbounded above and the function
355 raises a NetworkXUnbounded.
356
357 See also
358 --------
359 :meth:`maximum_flow`
360 :meth:`minimum_cut`
361 :meth:`edmonds_karp`
362 :meth:`shortest_augmenting_path`
363
364 Notes
365 -----
366 The residual network :samp:`R` from an input graph :samp:`G` has the
367 same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
368 of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
369 self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
370 in :samp:`G`. For each node :samp:`u` in :samp:`R`,
371 :samp:`R.nodes[u]['excess']` represents the difference between flow into
372 :samp:`u` and flow out of :samp:`u`.
373
374 For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
375 is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
376 in :samp:`G` or zero otherwise. If the capacity is infinite,
377 :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
378 that does not affect the solution of the problem. This value is stored in
379 :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
380 :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
381 satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
382
383 The flow value, defined as the total flow into :samp:`t`, the sink, is
384 stored in :samp:`R.graph['flow_value']`. Reachability to :samp:`t` using
385 only edges :samp:`(u, v)` such that
386 :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
387 :samp:`s`-:samp:`t` cut.
388
389 Examples
390 --------
391 >>> from networkx.algorithms.flow import preflow_push
392
393 The functions that implement flow algorithms and output a residual
394 network, such as this one, are not imported to the base NetworkX
395 namespace, so you have to explicitly import them from the flow package.
396
397 >>> G = nx.DiGraph()
398 >>> G.add_edge("x", "a", capacity=3.0)
399 >>> G.add_edge("x", "b", capacity=1.0)
400 >>> G.add_edge("a", "c", capacity=3.0)
401 >>> G.add_edge("b", "c", capacity=5.0)
402 >>> G.add_edge("b", "d", capacity=4.0)
403 >>> G.add_edge("d", "e", capacity=2.0)
404 >>> G.add_edge("c", "y", capacity=2.0)
405 >>> G.add_edge("e", "y", capacity=3.0)
406 >>> R = preflow_push(G, "x", "y")
407 >>> flow_value = nx.maximum_flow_value(G, "x", "y")
408 >>> flow_value == R.graph["flow_value"]
409 True
410 >>> # preflow_push also stores the maximum flow value
411 >>> # in the excess attribute of the sink node t
412 >>> flow_value == R.nodes["y"]["excess"]
413 True
414 >>> # For some problems, you might only want to compute a
415 >>> # maximum preflow.
416 >>> R = preflow_push(G, "x", "y", value_only=True)
417 >>> flow_value == R.graph["flow_value"]
418 True
419 >>> flow_value == R.nodes["y"]["excess"]
420 True
421
422 """
423 R = preflow_push_impl(G, s, t, capacity, residual, global_relabel_freq, value_only)
424 R.graph["algorithm"] = "preflow_push"
425 return R