Spaces:
Running
Running
""" | |
Utility classes and functions for network flow algorithms. | |
""" | |
from collections import deque | |
import networkx as nx | |
__all__ = [ | |
"CurrentEdge", | |
"Level", | |
"GlobalRelabelThreshold", | |
"build_residual_network", | |
"detect_unboundedness", | |
"build_flow_dict", | |
] | |
class CurrentEdge: | |
"""Mechanism for iterating over out-edges incident to a node in a circular | |
manner. StopIteration exception is raised when wraparound occurs. | |
""" | |
__slots__ = ("_edges", "_it", "_curr") | |
def __init__(self, edges): | |
self._edges = edges | |
if self._edges: | |
self._rewind() | |
def get(self): | |
return self._curr | |
def move_to_next(self): | |
try: | |
self._curr = next(self._it) | |
except StopIteration: | |
self._rewind() | |
raise | |
def _rewind(self): | |
self._it = iter(self._edges.items()) | |
self._curr = next(self._it) | |
class Level: | |
"""Active and inactive nodes in a level.""" | |
__slots__ = ("active", "inactive") | |
def __init__(self): | |
self.active = set() | |
self.inactive = set() | |
class GlobalRelabelThreshold: | |
"""Measurement of work before the global relabeling heuristic should be | |
applied. | |
""" | |
def __init__(self, n, m, freq): | |
self._threshold = (n + m) / freq if freq else float("inf") | |
self._work = 0 | |
def add_work(self, work): | |
self._work += work | |
def is_reached(self): | |
return self._work >= self._threshold | |
def clear_work(self): | |
self._work = 0 | |
def build_residual_network(G, capacity): | |
"""Build a residual network and initialize a zero flow. | |
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 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']`. If :samp:`cutoff` is not | |
specified, 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. | |
""" | |
if G.is_multigraph(): | |
raise nx.NetworkXError("MultiGraph and MultiDiGraph not supported (yet).") | |
R = nx.DiGraph() | |
R.add_nodes_from(G) | |
inf = float("inf") | |
# Extract edges with positive capacities. Self loops excluded. | |
edge_list = [ | |
(u, v, attr) | |
for u, v, attr in G.edges(data=True) | |
if u != v and attr.get(capacity, inf) > 0 | |
] | |
# Simulate infinity with three times the sum of the finite edge capacities | |
# or any positive value if the sum is zero. This allows the | |
# infinite-capacity edges to be distinguished for unboundedness detection | |
# and directly participate in residual capacity calculation. If the maximum | |
# flow is finite, these edges cannot appear in the minimum cut and thus | |
# guarantee correctness. Since the residual capacity of an | |
# infinite-capacity edge is always at least 2/3 of inf, while that of an | |
# finite-capacity edge is at most 1/3 of inf, if an operation moves more | |
# than 1/3 of inf units of flow to t, there must be an infinite-capacity | |
# s-t path in G. | |
inf = ( | |
3 | |
* sum( | |
attr[capacity] | |
for u, v, attr in edge_list | |
if capacity in attr and attr[capacity] != inf | |
) | |
or 1 | |
) | |
if G.is_directed(): | |
for u, v, attr in edge_list: | |
r = min(attr.get(capacity, inf), inf) | |
if not R.has_edge(u, v): | |
# Both (u, v) and (v, u) must be present in the residual | |
# network. | |
R.add_edge(u, v, capacity=r) | |
R.add_edge(v, u, capacity=0) | |
else: | |
# The edge (u, v) was added when (v, u) was visited. | |
R[u][v]["capacity"] = r | |
else: | |
for u, v, attr in edge_list: | |
# Add a pair of edges with equal residual capacities. | |
r = min(attr.get(capacity, inf), inf) | |
R.add_edge(u, v, capacity=r) | |
R.add_edge(v, u, capacity=r) | |
# Record the value simulating infinity. | |
R.graph["inf"] = inf | |
return R | |
def detect_unboundedness(R, s, t): | |
"""Detect an infinite-capacity s-t path in R.""" | |
q = deque([s]) | |
seen = {s} | |
inf = R.graph["inf"] | |
while q: | |
u = q.popleft() | |
for v, attr in R[u].items(): | |
if attr["capacity"] == inf and v not in seen: | |
if v == t: | |
raise nx.NetworkXUnbounded( | |
"Infinite capacity path, flow unbounded above." | |
) | |
seen.add(v) | |
q.append(v) | |
def build_flow_dict(G, R): | |
"""Build a flow dictionary from a residual network.""" | |
flow_dict = {} | |
for u in G: | |
flow_dict[u] = {v: 0 for v in G[u]} | |
flow_dict[u].update( | |
(v, attr["flow"]) for v, attr in R[u].items() if attr["flow"] > 0 | |
) | |
return flow_dict | |