Spaces:
Sleeping
Sleeping
| """ | |
| Highest-label preflow-push algorithm for maximum flow problems. | |
| """ | |
| from collections import deque | |
| from itertools import islice | |
| import networkx as nx | |
| from ...utils import arbitrary_element | |
| from .utils import ( | |
| CurrentEdge, | |
| GlobalRelabelThreshold, | |
| Level, | |
| build_residual_network, | |
| detect_unboundedness, | |
| ) | |
| __all__ = ["preflow_push"] | |
| def preflow_push_impl(G, s, t, capacity, residual, global_relabel_freq, value_only): | |
| """Implementation of the highest-label preflow-push algorithm.""" | |
| if s not in G: | |
| raise nx.NetworkXError(f"node {str(s)} not in graph") | |
| if t not in G: | |
| raise nx.NetworkXError(f"node {str(t)} not in graph") | |
| if s == t: | |
| raise nx.NetworkXError("source and sink are the same node") | |
| if global_relabel_freq is None: | |
| global_relabel_freq = 0 | |
| if global_relabel_freq < 0: | |
| raise nx.NetworkXError("global_relabel_freq must be nonnegative.") | |
| if residual is None: | |
| R = build_residual_network(G, capacity) | |
| else: | |
| R = residual | |
| detect_unboundedness(R, s, t) | |
| R_nodes = R.nodes | |
| R_pred = R.pred | |
| R_succ = R.succ | |
| # Initialize/reset the residual network. | |
| for u in R: | |
| R_nodes[u]["excess"] = 0 | |
| for e in R_succ[u].values(): | |
| e["flow"] = 0 | |
| def reverse_bfs(src): | |
| """Perform a reverse breadth-first search from src in the residual | |
| network. | |
| """ | |
| heights = {src: 0} | |
| q = deque([(src, 0)]) | |
| while q: | |
| u, height = q.popleft() | |
| height += 1 | |
| for v, attr in R_pred[u].items(): | |
| if v not in heights and attr["flow"] < attr["capacity"]: | |
| heights[v] = height | |
| q.append((v, height)) | |
| return heights | |
| # Initialize heights of the nodes. | |
| heights = reverse_bfs(t) | |
| if s not in heights: | |
| # t is not reachable from s in the residual network. The maximum flow | |
| # must be zero. | |
| R.graph["flow_value"] = 0 | |
| return R | |
| n = len(R) | |
| # max_height represents the height of the highest level below level n with | |
| # at least one active node. | |
| max_height = max(heights[u] for u in heights if u != s) | |
| heights[s] = n | |
| grt = GlobalRelabelThreshold(n, R.size(), global_relabel_freq) | |
| # Initialize heights and 'current edge' data structures of the nodes. | |
| for u in R: | |
| R_nodes[u]["height"] = heights[u] if u in heights else n + 1 | |
| R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u]) | |
| def push(u, v, flow): | |
| """Push flow units of flow from u to v.""" | |
| R_succ[u][v]["flow"] += flow | |
| R_succ[v][u]["flow"] -= flow | |
| R_nodes[u]["excess"] -= flow | |
| R_nodes[v]["excess"] += flow | |
| # The maximum flow must be nonzero now. Initialize the preflow by | |
| # saturating all edges emanating from s. | |
| for u, attr in R_succ[s].items(): | |
| flow = attr["capacity"] | |
| if flow > 0: | |
| push(s, u, flow) | |
| # Partition nodes into levels. | |
| levels = [Level() for i in range(2 * n)] | |
| for u in R: | |
| if u != s and u != t: | |
| level = levels[R_nodes[u]["height"]] | |
| if R_nodes[u]["excess"] > 0: | |
| level.active.add(u) | |
| else: | |
| level.inactive.add(u) | |
| def activate(v): | |
| """Move a node from the inactive set to the active set of its level.""" | |
| if v != s and v != t: | |
| level = levels[R_nodes[v]["height"]] | |
| if v in level.inactive: | |
| level.inactive.remove(v) | |
| level.active.add(v) | |
| def relabel(u): | |
| """Relabel a node to create an admissible edge.""" | |
| grt.add_work(len(R_succ[u])) | |
| return ( | |
| min( | |
| R_nodes[v]["height"] | |
| for v, attr in R_succ[u].items() | |
| if attr["flow"] < attr["capacity"] | |
| ) | |
| + 1 | |
| ) | |
| def discharge(u, is_phase1): | |
| """Discharge a node until it becomes inactive or, during phase 1 (see | |
| below), its height reaches at least n. The node is known to have the | |
| largest height among active nodes. | |
| """ | |
| height = R_nodes[u]["height"] | |
| curr_edge = R_nodes[u]["curr_edge"] | |
| # next_height represents the next height to examine after discharging | |
| # the current node. During phase 1, it is capped to below n. | |
| next_height = height | |
| levels[height].active.remove(u) | |
| while True: | |
| v, attr = curr_edge.get() | |
| if height == R_nodes[v]["height"] + 1 and attr["flow"] < attr["capacity"]: | |
| flow = min(R_nodes[u]["excess"], attr["capacity"] - attr["flow"]) | |
| push(u, v, flow) | |
| activate(v) | |
| if R_nodes[u]["excess"] == 0: | |
| # The node has become inactive. | |
| levels[height].inactive.add(u) | |
| break | |
| try: | |
| curr_edge.move_to_next() | |
| except StopIteration: | |
| # We have run off the end of the adjacency list, and there can | |
| # be no more admissible edges. Relabel the node to create one. | |
| height = relabel(u) | |
| if is_phase1 and height >= n - 1: | |
| # Although the node is still active, with a height at least | |
| # n - 1, it is now known to be on the s side of the minimum | |
| # s-t cut. Stop processing it until phase 2. | |
| levels[height].active.add(u) | |
| break | |
| # The first relabel operation after global relabeling may not | |
| # increase the height of the node since the 'current edge' data | |
| # structure is not rewound. Use height instead of (height - 1) | |
| # in case other active nodes at the same level are missed. | |
| next_height = height | |
| R_nodes[u]["height"] = height | |
| return next_height | |
| def gap_heuristic(height): | |
| """Apply the gap heuristic.""" | |
| # Move all nodes at levels (height + 1) to max_height to level n + 1. | |
| for level in islice(levels, height + 1, max_height + 1): | |
| for u in level.active: | |
| R_nodes[u]["height"] = n + 1 | |
| for u in level.inactive: | |
| R_nodes[u]["height"] = n + 1 | |
| levels[n + 1].active.update(level.active) | |
| level.active.clear() | |
| levels[n + 1].inactive.update(level.inactive) | |
| level.inactive.clear() | |
| def global_relabel(from_sink): | |
| """Apply the global relabeling heuristic.""" | |
| src = t if from_sink else s | |
| heights = reverse_bfs(src) | |
| if not from_sink: | |
| # s must be reachable from t. Remove t explicitly. | |
| del heights[t] | |
| max_height = max(heights.values()) | |
| if from_sink: | |
| # Also mark nodes from which t is unreachable for relabeling. This | |
| # serves the same purpose as the gap heuristic. | |
| for u in R: | |
| if u not in heights and R_nodes[u]["height"] < n: | |
| heights[u] = n + 1 | |
| else: | |
| # Shift the computed heights because the height of s is n. | |
| for u in heights: | |
| heights[u] += n | |
| max_height += n | |
| del heights[src] | |
| for u, new_height in heights.items(): | |
| old_height = R_nodes[u]["height"] | |
| if new_height != old_height: | |
| if u in levels[old_height].active: | |
| levels[old_height].active.remove(u) | |
| levels[new_height].active.add(u) | |
| else: | |
| levels[old_height].inactive.remove(u) | |
| levels[new_height].inactive.add(u) | |
| R_nodes[u]["height"] = new_height | |
| return max_height | |
| # Phase 1: Find the maximum preflow by pushing as much flow as possible to | |
| # t. | |
| height = max_height | |
| while height > 0: | |
| # Discharge active nodes in the current level. | |
| while True: | |
| level = levels[height] | |
| if not level.active: | |
| # All active nodes in the current level have been discharged. | |
| # Move to the next lower level. | |
| height -= 1 | |
| break | |
| # Record the old height and level for the gap heuristic. | |
| old_height = height | |
| old_level = level | |
| u = arbitrary_element(level.active) | |
| height = discharge(u, True) | |
| if grt.is_reached(): | |
| # Global relabeling heuristic: Recompute the exact heights of | |
| # all nodes. | |
| height = global_relabel(True) | |
| max_height = height | |
| grt.clear_work() | |
| elif not old_level.active and not old_level.inactive: | |
| # Gap heuristic: If the level at old_height is empty (a 'gap'), | |
| # a minimum cut has been identified. All nodes with heights | |
| # above old_height can have their heights set to n + 1 and not | |
| # be further processed before a maximum preflow is found. | |
| gap_heuristic(old_height) | |
| height = old_height - 1 | |
| max_height = height | |
| else: | |
| # Update the height of the highest level with at least one | |
| # active node. | |
| max_height = max(max_height, height) | |
| # A maximum preflow has been found. The excess at t is the maximum flow | |
| # value. | |
| if value_only: | |
| R.graph["flow_value"] = R_nodes[t]["excess"] | |
| return R | |
| # Phase 2: Convert the maximum preflow into a maximum flow by returning the | |
| # excess to s. | |
| # Relabel all nodes so that they have accurate heights. | |
| height = global_relabel(False) | |
| grt.clear_work() | |
| # Continue to discharge the active nodes. | |
| while height > n: | |
| # Discharge active nodes in the current level. | |
| while True: | |
| level = levels[height] | |
| if not level.active: | |
| # All active nodes in the current level have been discharged. | |
| # Move to the next lower level. | |
| height -= 1 | |
| break | |
| u = arbitrary_element(level.active) | |
| height = discharge(u, False) | |
| if grt.is_reached(): | |
| # Global relabeling heuristic. | |
| height = global_relabel(False) | |
| grt.clear_work() | |
| R.graph["flow_value"] = R_nodes[t]["excess"] | |
| return R | |
| def preflow_push( | |
| G, s, t, capacity="capacity", residual=None, global_relabel_freq=1, value_only=False | |
| ): | |
| r"""Find a maximum single-commodity flow using the highest-label | |
| preflow-push algorithm. | |
| This function returns the residual network resulting after computing | |
| the maximum flow. See below for details about the conventions | |
| NetworkX uses for defining residual networks. | |
| This algorithm has a running time of $O(n^2 \sqrt{m})$ for $n$ nodes and | |
| $m$ edges. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| Edges of the graph are expected to have an attribute called | |
| 'capacity'. If this attribute is not present, the edge is | |
| considered to have infinite capacity. | |
| s : node | |
| Source node for the flow. | |
| t : node | |
| Sink node for the flow. | |
| capacity : string | |
| Edges of the graph G are expected to have an attribute capacity | |
| that indicates how much flow the edge can support. If this | |
| attribute is not present, the edge is considered to have | |
| infinite capacity. Default value: 'capacity'. | |
| residual : NetworkX graph | |
| Residual network on which the algorithm is to be executed. If None, a | |
| new residual network is created. Default value: None. | |
| global_relabel_freq : integer, float | |
| Relative frequency of applying the global relabeling heuristic to speed | |
| up the algorithm. If it is None, the heuristic is disabled. Default | |
| value: 1. | |
| value_only : bool | |
| If False, compute a maximum flow; otherwise, compute a maximum preflow | |
| which is enough for computing the maximum flow value. Default value: | |
| False. | |
| Returns | |
| ------- | |
| R : NetworkX DiGraph | |
| Residual network after computing the maximum flow. | |
| Raises | |
| ------ | |
| NetworkXError | |
| The algorithm does not support MultiGraph and MultiDiGraph. If | |
| the input graph is an instance of one of these two classes, a | |
| NetworkXError is raised. | |
| NetworkXUnbounded | |
| If the graph has a path of infinite capacity, the value of a | |
| feasible flow on the graph is unbounded above and the function | |
| raises a NetworkXUnbounded. | |
| See also | |
| -------- | |
| :meth:`maximum_flow` | |
| :meth:`minimum_cut` | |
| :meth:`edmonds_karp` | |
| :meth:`shortest_augmenting_path` | |
| Notes | |
| ----- | |
| The residual network :samp:`R` from an input graph :samp:`G` has the | |
| same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair | |
| of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a | |
| self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists | |
| in :samp:`G`. For each node :samp:`u` in :samp:`R`, | |
| :samp:`R.nodes[u]['excess']` represents the difference between flow into | |
| :samp:`u` and flow out of :samp:`u`. | |
| For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']` | |
| is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists | |
| in :samp:`G` or zero otherwise. If the capacity is infinite, | |
| :samp:`R[u][v]['capacity']` will have a high arbitrary finite value | |
| that does not affect the solution of the problem. This value is stored in | |
| :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`, | |
| :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and | |
| satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`. | |
| The flow value, defined as the total flow into :samp:`t`, the sink, is | |
| stored in :samp:`R.graph['flow_value']`. Reachability to :samp:`t` using | |
| only edges :samp:`(u, v)` such that | |
| :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum | |
| :samp:`s`-:samp:`t` cut. | |
| Examples | |
| -------- | |
| >>> from networkx.algorithms.flow import preflow_push | |
| The functions that implement flow algorithms and output a residual | |
| network, such as this one, are not imported to the base NetworkX | |
| namespace, so you have to explicitly import them from the flow package. | |
| >>> G = nx.DiGraph() | |
| >>> G.add_edge("x", "a", capacity=3.0) | |
| >>> G.add_edge("x", "b", capacity=1.0) | |
| >>> G.add_edge("a", "c", capacity=3.0) | |
| >>> G.add_edge("b", "c", capacity=5.0) | |
| >>> G.add_edge("b", "d", capacity=4.0) | |
| >>> G.add_edge("d", "e", capacity=2.0) | |
| >>> G.add_edge("c", "y", capacity=2.0) | |
| >>> G.add_edge("e", "y", capacity=3.0) | |
| >>> R = preflow_push(G, "x", "y") | |
| >>> flow_value = nx.maximum_flow_value(G, "x", "y") | |
| >>> flow_value == R.graph["flow_value"] | |
| True | |
| >>> # preflow_push also stores the maximum flow value | |
| >>> # in the excess attribute of the sink node t | |
| >>> flow_value == R.nodes["y"]["excess"] | |
| True | |
| >>> # For some problems, you might only want to compute a | |
| >>> # maximum preflow. | |
| >>> R = preflow_push(G, "x", "y", value_only=True) | |
| >>> flow_value == R.graph["flow_value"] | |
| True | |
| >>> flow_value == R.nodes["y"]["excess"] | |
| True | |
| """ | |
| R = preflow_push_impl(G, s, t, capacity, residual, global_relabel_freq, value_only) | |
| R.graph["algorithm"] = "preflow_push" | |
| return R | |