utils.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. """
  2. Utility classes and functions for network flow algorithms.
  3. """
  4. from collections import deque
  5. import networkx as nx
  6. __all__ = [
  7. "CurrentEdge",
  8. "Level",
  9. "GlobalRelabelThreshold",
  10. "build_residual_network",
  11. "detect_unboundedness",
  12. "build_flow_dict",
  13. ]
  14. class CurrentEdge:
  15. """Mechanism for iterating over out-edges incident to a node in a circular
  16. manner. StopIteration exception is raised when wraparound occurs.
  17. """
  18. __slots__ = ("_edges", "_it", "_curr")
  19. def __init__(self, edges):
  20. self._edges = edges
  21. if self._edges:
  22. self._rewind()
  23. def get(self):
  24. return self._curr
  25. def move_to_next(self):
  26. try:
  27. self._curr = next(self._it)
  28. except StopIteration:
  29. self._rewind()
  30. raise
  31. def _rewind(self):
  32. self._it = iter(self._edges.items())
  33. self._curr = next(self._it)
  34. class Level:
  35. """Active and inactive nodes in a level."""
  36. __slots__ = ("active", "inactive")
  37. def __init__(self):
  38. self.active = set()
  39. self.inactive = set()
  40. class GlobalRelabelThreshold:
  41. """Measurement of work before the global relabeling heuristic should be
  42. applied.
  43. """
  44. def __init__(self, n, m, freq):
  45. self._threshold = (n + m) / freq if freq else float("inf")
  46. self._work = 0
  47. def add_work(self, work):
  48. self._work += work
  49. def is_reached(self):
  50. return self._work >= self._threshold
  51. def clear_work(self):
  52. self._work = 0
  53. def build_residual_network(G, capacity):
  54. """Build a residual network and initialize a zero flow.
  55. The residual network :samp:`R` from an input graph :samp:`G` has the
  56. same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
  57. of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
  58. self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
  59. in :samp:`G`.
  60. For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
  61. is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
  62. in :samp:`G` or zero otherwise. If the capacity is infinite,
  63. :samp:`R[u][v]['capacity']` will have a high arbitrary finite value
  64. that does not affect the solution of the problem. This value is stored in
  65. :samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
  66. :samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
  67. satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
  68. The flow value, defined as the total flow into :samp:`t`, the sink, is
  69. stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
  70. specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
  71. that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
  72. :samp:`s`-:samp:`t` cut.
  73. """
  74. if G.is_multigraph():
  75. raise nx.NetworkXError("MultiGraph and MultiDiGraph not supported (yet).")
  76. R = nx.DiGraph()
  77. R.add_nodes_from(G)
  78. inf = float("inf")
  79. # Extract edges with positive capacities. Self loops excluded.
  80. edge_list = [
  81. (u, v, attr)
  82. for u, v, attr in G.edges(data=True)
  83. if u != v and attr.get(capacity, inf) > 0
  84. ]
  85. # Simulate infinity with three times the sum of the finite edge capacities
  86. # or any positive value if the sum is zero. This allows the
  87. # infinite-capacity edges to be distinguished for unboundedness detection
  88. # and directly participate in residual capacity calculation. If the maximum
  89. # flow is finite, these edges cannot appear in the minimum cut and thus
  90. # guarantee correctness. Since the residual capacity of an
  91. # infinite-capacity edge is always at least 2/3 of inf, while that of an
  92. # finite-capacity edge is at most 1/3 of inf, if an operation moves more
  93. # than 1/3 of inf units of flow to t, there must be an infinite-capacity
  94. # s-t path in G.
  95. inf = (
  96. 3
  97. * sum(
  98. attr[capacity]
  99. for u, v, attr in edge_list
  100. if capacity in attr and attr[capacity] != inf
  101. )
  102. or 1
  103. )
  104. if G.is_directed():
  105. for u, v, attr in edge_list:
  106. r = min(attr.get(capacity, inf), inf)
  107. if not R.has_edge(u, v):
  108. # Both (u, v) and (v, u) must be present in the residual
  109. # network.
  110. R.add_edge(u, v, capacity=r)
  111. R.add_edge(v, u, capacity=0)
  112. else:
  113. # The edge (u, v) was added when (v, u) was visited.
  114. R[u][v]["capacity"] = r
  115. else:
  116. for u, v, attr in edge_list:
  117. # Add a pair of edges with equal residual capacities.
  118. r = min(attr.get(capacity, inf), inf)
  119. R.add_edge(u, v, capacity=r)
  120. R.add_edge(v, u, capacity=r)
  121. # Record the value simulating infinity.
  122. R.graph["inf"] = inf
  123. return R
  124. def detect_unboundedness(R, s, t):
  125. """Detect an infinite-capacity s-t path in R."""
  126. q = deque([s])
  127. seen = {s}
  128. inf = R.graph["inf"]
  129. while q:
  130. u = q.popleft()
  131. for v, attr in R[u].items():
  132. if attr["capacity"] == inf and v not in seen:
  133. if v == t:
  134. raise nx.NetworkXUnbounded(
  135. "Infinite capacity path, flow unbounded above."
  136. )
  137. seen.add(v)
  138. q.append(v)
  139. def build_flow_dict(G, R):
  140. """Build a flow dictionary from a residual network."""
  141. flow_dict = {}
  142. for u in G:
  143. flow_dict[u] = {v: 0 for v in G[u]}
  144. flow_dict[u].update(
  145. (v, attr["flow"]) for v, attr in R[u].items() if attr["flow"] > 0
  146. )
  147. return flow_dict