123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- """
- 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
|