|
""" |
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
heights = reverse_bfs(t) |
|
|
|
if s not in heights: |
|
|
|
|
|
R.graph["flow_value"] = 0 |
|
return R |
|
|
|
n = len(R) |
|
|
|
|
|
max_height = max(heights[u] for u in heights if u != s) |
|
heights[s] = n |
|
|
|
grt = GlobalRelabelThreshold(n, R.size(), global_relabel_freq) |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
for u, attr in R_succ[s].items(): |
|
flow = attr["capacity"] |
|
if flow > 0: |
|
push(s, u, flow) |
|
|
|
|
|
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 = 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: |
|
|
|
levels[height].inactive.add(u) |
|
break |
|
try: |
|
curr_edge.move_to_next() |
|
except StopIteration: |
|
|
|
|
|
height = relabel(u) |
|
if is_phase1 and height >= n - 1: |
|
|
|
|
|
|
|
levels[height].active.add(u) |
|
break |
|
|
|
|
|
|
|
|
|
next_height = height |
|
R_nodes[u]["height"] = height |
|
return next_height |
|
|
|
def gap_heuristic(height): |
|
"""Apply the gap heuristic.""" |
|
|
|
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: |
|
|
|
del heights[t] |
|
max_height = max(heights.values()) |
|
if from_sink: |
|
|
|
|
|
for u in R: |
|
if u not in heights and R_nodes[u]["height"] < n: |
|
heights[u] = n + 1 |
|
else: |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
height = max_height |
|
while height > 0: |
|
|
|
while True: |
|
level = levels[height] |
|
if not level.active: |
|
|
|
|
|
height -= 1 |
|
break |
|
|
|
old_height = height |
|
old_level = level |
|
u = arbitrary_element(level.active) |
|
height = discharge(u, True) |
|
if grt.is_reached(): |
|
|
|
|
|
height = global_relabel(True) |
|
max_height = height |
|
grt.clear_work() |
|
elif not old_level.active and not old_level.inactive: |
|
|
|
|
|
|
|
|
|
gap_heuristic(old_height) |
|
height = old_height - 1 |
|
max_height = height |
|
else: |
|
|
|
|
|
max_height = max(max_height, height) |
|
|
|
|
|
|
|
if value_only: |
|
R.graph["flow_value"] = R_nodes[t]["excess"] |
|
return R |
|
|
|
|
|
|
|
|
|
|
|
height = global_relabel(False) |
|
grt.clear_work() |
|
|
|
|
|
while height > n: |
|
|
|
while True: |
|
level = levels[height] |
|
if not level.active: |
|
|
|
|
|
height -= 1 |
|
break |
|
u = arbitrary_element(level.active) |
|
height = discharge(u, False) |
|
if grt.is_reached(): |
|
|
|
height = global_relabel(False) |
|
grt.clear_work() |
|
|
|
R.graph["flow_value"] = R_nodes[t]["excess"] |
|
return R |
|
|
|
|
|
@nx._dispatchable(edge_attrs={"capacity": float("inf")}, returns_graph=True) |
|
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" |
|
nx._clear_cache(R) |
|
return R |
|
|