Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
11
venv/Lib/site-packages/networkx/algorithms/flow/__init__.py
Normal file
11
venv/Lib/site-packages/networkx/algorithms/flow/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from .maxflow import *
|
||||
from .mincost import *
|
||||
from .boykovkolmogorov import *
|
||||
from .dinitz_alg import *
|
||||
from .edmondskarp import *
|
||||
from .gomory_hu import *
|
||||
from .preflowpush import *
|
||||
from .shortestaugmentingpath import *
|
||||
from .capacityscaling import *
|
||||
from .networksimplex import *
|
||||
from .utils import build_flow_dict, build_residual_network
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,367 @@
|
|||
"""
|
||||
Boykov-Kolmogorov algorithm for maximum flow problems.
|
||||
"""
|
||||
from collections import deque
|
||||
from operator import itemgetter
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.flow.utils import build_residual_network
|
||||
|
||||
__all__ = ["boykov_kolmogorov"]
|
||||
|
||||
|
||||
def boykov_kolmogorov(
|
||||
G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None
|
||||
):
|
||||
r"""Find a maximum single-commodity flow using Boykov-Kolmogorov 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 worse case complexity $O(n^2 m |C|)$ for $n$ nodes, $m$
|
||||
edges, and $|C|$ the cost of the minimum cut [1]_. This implementation
|
||||
uses the marking heuristic defined in [2]_ which improves its running
|
||||
time in many practical problems.
|
||||
|
||||
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.
|
||||
|
||||
value_only : bool
|
||||
If True compute only the value of the maximum flow. This parameter
|
||||
will be ignored by this algorithm because it is not applicable.
|
||||
|
||||
cutoff : integer, float
|
||||
If specified, the algorithm will terminate when the flow value reaches
|
||||
or exceeds the cutoff. In this case, it may be unable to immediately
|
||||
determine a minimum cut. Default value: None.
|
||||
|
||||
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:`preflow_push`
|
||||
: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 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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.algorithms.flow import boykov_kolmogorov
|
||||
|
||||
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 = boykov_kolmogorov(G, "x", "y")
|
||||
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
||||
>>> flow_value
|
||||
3.0
|
||||
>>> flow_value == R.graph["flow_value"]
|
||||
True
|
||||
|
||||
A nice feature of the Boykov-Kolmogorov algorithm is that a partition
|
||||
of the nodes that defines a minimum cut can be easily computed based
|
||||
on the search trees used during the algorithm. These trees are stored
|
||||
in the graph attribute `trees` of the residual network.
|
||||
|
||||
>>> source_tree, target_tree = R.graph["trees"]
|
||||
>>> partition = (set(source_tree), set(G) - set(source_tree))
|
||||
|
||||
Or equivalently:
|
||||
|
||||
>>> partition = (set(G) - set(target_tree), set(target_tree))
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Boykov, Y., & Kolmogorov, V. (2004). An experimental comparison
|
||||
of min-cut/max-flow algorithms for energy minimization in vision.
|
||||
Pattern Analysis and Machine Intelligence, IEEE Transactions on,
|
||||
26(9), 1124-1137.
|
||||
http://www.csd.uwo.ca/~yuri/Papers/pami04.pdf
|
||||
|
||||
.. [2] Vladimir Kolmogorov. Graph-based Algorithms for Multi-camera
|
||||
Reconstruction Problem. PhD thesis, Cornell University, CS Department,
|
||||
2003. pp. 109-114.
|
||||
https://pub.ist.ac.at/~vnk/papers/thesis.pdf
|
||||
|
||||
"""
|
||||
R = boykov_kolmogorov_impl(G, s, t, capacity, residual, cutoff)
|
||||
R.graph["algorithm"] = "boykov_kolmogorov"
|
||||
return R
|
||||
|
||||
|
||||
def boykov_kolmogorov_impl(G, s, t, capacity, residual, cutoff):
|
||||
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 residual is None:
|
||||
R = build_residual_network(G, capacity)
|
||||
else:
|
||||
R = residual
|
||||
|
||||
# Initialize/reset the residual network.
|
||||
# This is way too slow
|
||||
# nx.set_edge_attributes(R, 0, 'flow')
|
||||
for u in R:
|
||||
for e in R[u].values():
|
||||
e["flow"] = 0
|
||||
|
||||
# Use an arbitrary high value as infinite. It is computed
|
||||
# when building the residual network.
|
||||
INF = R.graph["inf"]
|
||||
|
||||
if cutoff is None:
|
||||
cutoff = INF
|
||||
|
||||
R_succ = R.succ
|
||||
R_pred = R.pred
|
||||
|
||||
def grow():
|
||||
"""Bidirectional breadth-first search for the growth stage.
|
||||
|
||||
Returns a connecting edge, that is and edge that connects
|
||||
a node from the source search tree with a node from the
|
||||
target search tree.
|
||||
The first node in the connecting edge is always from the
|
||||
source tree and the last node from the target tree.
|
||||
"""
|
||||
while active:
|
||||
u = active[0]
|
||||
if u in source_tree:
|
||||
this_tree = source_tree
|
||||
other_tree = target_tree
|
||||
neighbors = R_succ
|
||||
else:
|
||||
this_tree = target_tree
|
||||
other_tree = source_tree
|
||||
neighbors = R_pred
|
||||
for v, attr in neighbors[u].items():
|
||||
if attr["capacity"] - attr["flow"] > 0:
|
||||
if v not in this_tree:
|
||||
if v in other_tree:
|
||||
return (u, v) if this_tree is source_tree else (v, u)
|
||||
this_tree[v] = u
|
||||
dist[v] = dist[u] + 1
|
||||
timestamp[v] = timestamp[u]
|
||||
active.append(v)
|
||||
elif v in this_tree and _is_closer(u, v):
|
||||
this_tree[v] = u
|
||||
dist[v] = dist[u] + 1
|
||||
timestamp[v] = timestamp[u]
|
||||
_ = active.popleft()
|
||||
return None, None
|
||||
|
||||
def augment(u, v):
|
||||
"""Augmentation stage.
|
||||
|
||||
Reconstruct path and determine its residual capacity.
|
||||
We start from a connecting edge, which links a node
|
||||
from the source tree to a node from the target tree.
|
||||
The connecting edge is the output of the grow function
|
||||
and the input of this function.
|
||||
"""
|
||||
attr = R_succ[u][v]
|
||||
flow = min(INF, attr["capacity"] - attr["flow"])
|
||||
path = [u]
|
||||
# Trace a path from u to s in source_tree.
|
||||
w = u
|
||||
while w != s:
|
||||
n = w
|
||||
w = source_tree[n]
|
||||
attr = R_pred[n][w]
|
||||
flow = min(flow, attr["capacity"] - attr["flow"])
|
||||
path.append(w)
|
||||
path.reverse()
|
||||
# Trace a path from v to t in target_tree.
|
||||
path.append(v)
|
||||
w = v
|
||||
while w != t:
|
||||
n = w
|
||||
w = target_tree[n]
|
||||
attr = R_succ[n][w]
|
||||
flow = min(flow, attr["capacity"] - attr["flow"])
|
||||
path.append(w)
|
||||
# Augment flow along the path and check for saturated edges.
|
||||
it = iter(path)
|
||||
u = next(it)
|
||||
these_orphans = []
|
||||
for v in it:
|
||||
R_succ[u][v]["flow"] += flow
|
||||
R_succ[v][u]["flow"] -= flow
|
||||
if R_succ[u][v]["flow"] == R_succ[u][v]["capacity"]:
|
||||
if v in source_tree:
|
||||
source_tree[v] = None
|
||||
these_orphans.append(v)
|
||||
if u in target_tree:
|
||||
target_tree[u] = None
|
||||
these_orphans.append(u)
|
||||
u = v
|
||||
orphans.extend(sorted(these_orphans, key=dist.get))
|
||||
return flow
|
||||
|
||||
def adopt():
|
||||
"""Adoption stage.
|
||||
|
||||
Reconstruct search trees by adopting or discarding orphans.
|
||||
During augmentation stage some edges got saturated and thus
|
||||
the source and target search trees broke down to forests, with
|
||||
orphans as roots of some of its trees. We have to reconstruct
|
||||
the search trees rooted to source and target before we can grow
|
||||
them again.
|
||||
"""
|
||||
while orphans:
|
||||
u = orphans.popleft()
|
||||
if u in source_tree:
|
||||
tree = source_tree
|
||||
neighbors = R_pred
|
||||
else:
|
||||
tree = target_tree
|
||||
neighbors = R_succ
|
||||
nbrs = ((n, attr, dist[n]) for n, attr in neighbors[u].items() if n in tree)
|
||||
for v, attr, d in sorted(nbrs, key=itemgetter(2)):
|
||||
if attr["capacity"] - attr["flow"] > 0:
|
||||
if _has_valid_root(v, tree):
|
||||
tree[u] = v
|
||||
dist[u] = dist[v] + 1
|
||||
timestamp[u] = time
|
||||
break
|
||||
else:
|
||||
nbrs = (
|
||||
(n, attr, dist[n]) for n, attr in neighbors[u].items() if n in tree
|
||||
)
|
||||
for v, attr, d in sorted(nbrs, key=itemgetter(2)):
|
||||
if attr["capacity"] - attr["flow"] > 0:
|
||||
if v not in active:
|
||||
active.append(v)
|
||||
if tree[v] == u:
|
||||
tree[v] = None
|
||||
orphans.appendleft(v)
|
||||
if u in active:
|
||||
active.remove(u)
|
||||
del tree[u]
|
||||
|
||||
def _has_valid_root(n, tree):
|
||||
path = []
|
||||
v = n
|
||||
while v is not None:
|
||||
path.append(v)
|
||||
if v == s or v == t:
|
||||
base_dist = 0
|
||||
break
|
||||
elif timestamp[v] == time:
|
||||
base_dist = dist[v]
|
||||
break
|
||||
v = tree[v]
|
||||
else:
|
||||
return False
|
||||
length = len(path)
|
||||
for i, u in enumerate(path, 1):
|
||||
dist[u] = base_dist + length - i
|
||||
timestamp[u] = time
|
||||
return True
|
||||
|
||||
def _is_closer(u, v):
|
||||
return timestamp[v] <= timestamp[u] and dist[v] > dist[u] + 1
|
||||
|
||||
source_tree = {s: None}
|
||||
target_tree = {t: None}
|
||||
active = deque([s, t])
|
||||
orphans = deque()
|
||||
flow_value = 0
|
||||
# data structures for the marking heuristic
|
||||
time = 1
|
||||
timestamp = {s: time, t: time}
|
||||
dist = {s: 0, t: 0}
|
||||
while flow_value < cutoff:
|
||||
# Growth stage
|
||||
u, v = grow()
|
||||
if u is None:
|
||||
break
|
||||
time += 1
|
||||
# Augmentation stage
|
||||
flow_value += augment(u, v)
|
||||
# Adoption stage
|
||||
adopt()
|
||||
|
||||
if flow_value * 2 > INF:
|
||||
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
||||
|
||||
# Add source and target tree in a graph attribute.
|
||||
# A partition that defines a minimum cut can be directly
|
||||
# computed from the search trees as explained in the docstrings.
|
||||
R.graph["trees"] = (source_tree, target_tree)
|
||||
# Add the standard flow_value graph attribute.
|
||||
R.graph["flow_value"] = flow_value
|
||||
return R
|
|
@ -0,0 +1,409 @@
|
|||
"""
|
||||
Capacity scaling minimum cost flow algorithm.
|
||||
"""
|
||||
|
||||
__all__ = ["capacity_scaling"]
|
||||
|
||||
from itertools import chain
|
||||
from math import log
|
||||
import networkx as nx
|
||||
from ...utils import BinaryHeap
|
||||
from ...utils import generate_unique_node
|
||||
from ...utils import not_implemented_for
|
||||
from ...utils import arbitrary_element
|
||||
|
||||
|
||||
def _detect_unboundedness(R):
|
||||
"""Detect infinite-capacity negative cycles.
|
||||
"""
|
||||
s = generate_unique_node()
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from(R)
|
||||
|
||||
# Value simulating infinity.
|
||||
inf = R.graph["inf"]
|
||||
# True infinity.
|
||||
f_inf = float("inf")
|
||||
for u in R:
|
||||
for v, e in R[u].items():
|
||||
# Compute the minimum weight of infinite-capacity (u, v) edges.
|
||||
w = f_inf
|
||||
for k, e in e.items():
|
||||
if e["capacity"] == inf:
|
||||
w = min(w, e["weight"])
|
||||
if w != f_inf:
|
||||
G.add_edge(u, v, weight=w)
|
||||
|
||||
if nx.negative_edge_cycle(G):
|
||||
raise nx.NetworkXUnbounded(
|
||||
"Negative cost cycle of infinite capacity found. "
|
||||
"Min cost flow may be unbounded below."
|
||||
)
|
||||
|
||||
|
||||
@not_implemented_for("undirected")
|
||||
def _build_residual_network(G, demand, capacity, weight):
|
||||
"""Build a residual network and initialize a zero flow.
|
||||
"""
|
||||
if sum(G.nodes[u].get(demand, 0) for u in G) != 0:
|
||||
raise nx.NetworkXUnfeasible("Sum of the demands should be 0.")
|
||||
|
||||
R = nx.MultiDiGraph()
|
||||
R.add_nodes_from(
|
||||
(u, {"excess": -G.nodes[u].get(demand, 0), "potential": 0}) for u in G
|
||||
)
|
||||
|
||||
inf = float("inf")
|
||||
# Detect selfloops with infinite capacities and negative weights.
|
||||
for u, v, e in nx.selfloop_edges(G, data=True):
|
||||
if e.get(weight, 0) < 0 and e.get(capacity, inf) == inf:
|
||||
raise nx.NetworkXUnbounded(
|
||||
"Negative cost cycle of infinite capacity found. "
|
||||
"Min cost flow may be unbounded below."
|
||||
)
|
||||
|
||||
# Extract edges with positive capacities. Self loops excluded.
|
||||
if G.is_multigraph():
|
||||
edge_list = [
|
||||
(u, v, k, e)
|
||||
for u, v, k, e in G.edges(data=True, keys=True)
|
||||
if u != v and e.get(capacity, inf) > 0
|
||||
]
|
||||
else:
|
||||
edge_list = [
|
||||
(u, v, 0, e)
|
||||
for u, v, e in G.edges(data=True)
|
||||
if u != v and e.get(capacity, inf) > 0
|
||||
]
|
||||
# Simulate infinity with the larger of the sum of absolute node imbalances
|
||||
# the sum of finite edge capacities or any positive value if both sums are
|
||||
# zero. This allows the infinite-capacity edges to be distinguished for
|
||||
# unboundedness detection and directly participate in residual capacity
|
||||
# calculation.
|
||||
inf = (
|
||||
max(
|
||||
sum(abs(R.nodes[u]["excess"]) for u in R),
|
||||
2
|
||||
* sum(
|
||||
e[capacity]
|
||||
for u, v, k, e in edge_list
|
||||
if capacity in e and e[capacity] != inf
|
||||
),
|
||||
)
|
||||
or 1
|
||||
)
|
||||
for u, v, k, e in edge_list:
|
||||
r = min(e.get(capacity, inf), inf)
|
||||
w = e.get(weight, 0)
|
||||
# Add both (u, v) and (v, u) into the residual network marked with the
|
||||
# original key. (key[1] == True) indicates the (u, v) is in the
|
||||
# original network.
|
||||
R.add_edge(u, v, key=(k, True), capacity=r, weight=w, flow=0)
|
||||
R.add_edge(v, u, key=(k, False), capacity=0, weight=-w, flow=0)
|
||||
|
||||
# Record the value simulating infinity.
|
||||
R.graph["inf"] = inf
|
||||
|
||||
_detect_unboundedness(R)
|
||||
|
||||
return R
|
||||
|
||||
|
||||
def _build_flow_dict(G, R, capacity, weight):
|
||||
"""Build a flow dictionary from a residual network.
|
||||
"""
|
||||
inf = float("inf")
|
||||
flow_dict = {}
|
||||
if G.is_multigraph():
|
||||
for u in G:
|
||||
flow_dict[u] = {}
|
||||
for v, es in G[u].items():
|
||||
flow_dict[u][v] = {
|
||||
# Always saturate negative selfloops.
|
||||
k: (
|
||||
0
|
||||
if (
|
||||
u != v or e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0
|
||||
)
|
||||
else e[capacity]
|
||||
)
|
||||
for k, e in es.items()
|
||||
}
|
||||
for v, es in R[u].items():
|
||||
if v in flow_dict[u]:
|
||||
flow_dict[u][v].update(
|
||||
(k[0], e["flow"]) for k, e in es.items() if e["flow"] > 0
|
||||
)
|
||||
else:
|
||||
for u in G:
|
||||
flow_dict[u] = {
|
||||
# Always saturate negative selfloops.
|
||||
v: (
|
||||
0
|
||||
if (u != v or e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0)
|
||||
else e[capacity]
|
||||
)
|
||||
for v, e in G[u].items()
|
||||
}
|
||||
flow_dict[u].update(
|
||||
(v, e["flow"])
|
||||
for v, es in R[u].items()
|
||||
for e in es.values()
|
||||
if e["flow"] > 0
|
||||
)
|
||||
return flow_dict
|
||||
|
||||
|
||||
def capacity_scaling(
|
||||
G, demand="demand", capacity="capacity", weight="weight", heap=BinaryHeap
|
||||
):
|
||||
r"""Find a minimum cost flow satisfying all demands in digraph G.
|
||||
|
||||
This is a capacity scaling successive shortest augmenting path algorithm.
|
||||
|
||||
G is a digraph with edge costs and capacities and in which nodes
|
||||
have demand, i.e., they want to send or receive some amount of
|
||||
flow. A negative demand means that the node wants to send flow, a
|
||||
positive demand means that the node want to receive flow. A flow on
|
||||
the digraph G satisfies all demand if the net flow into each node
|
||||
is equal to the demand of that node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
DiGraph or MultiDiGraph on which a minimum cost flow satisfying all
|
||||
demands is to be found.
|
||||
|
||||
demand : string
|
||||
Nodes of the graph G are expected to have an attribute demand
|
||||
that indicates how much flow a node wants to send (negative
|
||||
demand) or receive (positive demand). Note that the sum of the
|
||||
demands should be 0 otherwise the problem in not feasible. If
|
||||
this attribute is not present, a node is considered to have 0
|
||||
demand. Default value: 'demand'.
|
||||
|
||||
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'.
|
||||
|
||||
weight : string
|
||||
Edges of the graph G are expected to have an attribute weight
|
||||
that indicates the cost incurred by sending one unit of flow on
|
||||
that edge. If not present, the weight is considered to be 0.
|
||||
Default value: 'weight'.
|
||||
|
||||
heap : class
|
||||
Type of heap to be used in the algorithm. It should be a subclass of
|
||||
:class:`MinHeap` or implement a compatible interface.
|
||||
|
||||
If a stock heap implementation is to be used, :class:`BinaryHeap` is
|
||||
recommended over :class:`PairingHeap` for Python implementations without
|
||||
optimized attribute accesses (e.g., CPython) despite a slower
|
||||
asymptotic running time. For Python implementations with optimized
|
||||
attribute accesses (e.g., PyPy), :class:`PairingHeap` provides better
|
||||
performance. Default value: :class:`BinaryHeap`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flowCost : integer
|
||||
Cost of a minimum cost flow satisfying all demands.
|
||||
|
||||
flowDict : dictionary
|
||||
If G is a digraph, a dict-of-dicts keyed by nodes such that
|
||||
flowDict[u][v] is the flow on edge (u, v).
|
||||
If G is a MultiDiGraph, a dict-of-dicts-of-dicts keyed by nodes
|
||||
so that flowDict[u][v][key] is the flow on edge (u, v, key).
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
This exception is raised if the input graph is not directed,
|
||||
not connected.
|
||||
|
||||
NetworkXUnfeasible
|
||||
This exception is raised in the following situations:
|
||||
|
||||
* The sum of the demands is not zero. Then, there is no
|
||||
flow satisfying all demands.
|
||||
* There is no flow satisfying all demand.
|
||||
|
||||
NetworkXUnbounded
|
||||
This exception is raised if the digraph G has a cycle of
|
||||
negative cost and infinite capacity. Then, the cost of a flow
|
||||
satisfying all demands is unbounded below.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm does not work if edge weights are floating-point numbers.
|
||||
|
||||
See also
|
||||
--------
|
||||
:meth:`network_simplex`
|
||||
|
||||
Examples
|
||||
--------
|
||||
A simple example of a min cost flow problem.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_node("a", demand=-5)
|
||||
>>> G.add_node("d", demand=5)
|
||||
>>> G.add_edge("a", "b", weight=3, capacity=4)
|
||||
>>> G.add_edge("a", "c", weight=6, capacity=10)
|
||||
>>> G.add_edge("b", "d", weight=1, capacity=9)
|
||||
>>> G.add_edge("c", "d", weight=2, capacity=5)
|
||||
>>> flowCost, flowDict = nx.capacity_scaling(G)
|
||||
>>> flowCost
|
||||
24
|
||||
>>> flowDict # doctest: +SKIP
|
||||
{'a': {'c': 1, 'b': 4}, 'c': {'d': 1}, 'b': {'d': 4}, 'd': {}}
|
||||
|
||||
It is possible to change the name of the attributes used for the
|
||||
algorithm.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_node("p", spam=-4)
|
||||
>>> G.add_node("q", spam=2)
|
||||
>>> G.add_node("a", spam=-2)
|
||||
>>> G.add_node("d", spam=-1)
|
||||
>>> G.add_node("t", spam=2)
|
||||
>>> G.add_node("w", spam=3)
|
||||
>>> G.add_edge("p", "q", cost=7, vacancies=5)
|
||||
>>> G.add_edge("p", "a", cost=1, vacancies=4)
|
||||
>>> G.add_edge("q", "d", cost=2, vacancies=3)
|
||||
>>> G.add_edge("t", "q", cost=1, vacancies=2)
|
||||
>>> G.add_edge("a", "t", cost=2, vacancies=4)
|
||||
>>> G.add_edge("d", "w", cost=3, vacancies=4)
|
||||
>>> G.add_edge("t", "w", cost=4, vacancies=1)
|
||||
>>> flowCost, flowDict = nx.capacity_scaling(
|
||||
... G, demand="spam", capacity="vacancies", weight="cost"
|
||||
... )
|
||||
>>> flowCost
|
||||
37
|
||||
>>> flowDict # doctest: +SKIP
|
||||
{'a': {'t': 4}, 'd': {'w': 2}, 'q': {'d': 1}, 'p': {'q': 2, 'a': 2}, 't': {'q': 1, 'w': 1}, 'w': {}}
|
||||
"""
|
||||
R = _build_residual_network(G, demand, capacity, weight)
|
||||
|
||||
inf = float("inf")
|
||||
# Account cost of negative selfloops.
|
||||
flow_cost = sum(
|
||||
0
|
||||
if e.get(capacity, inf) <= 0 or e.get(weight, 0) >= 0
|
||||
else e[capacity] * e[weight]
|
||||
for u, v, e in nx.selfloop_edges(G, data=True)
|
||||
)
|
||||
|
||||
# Determine the maxmimum edge capacity.
|
||||
wmax = max(chain([-inf], (e["capacity"] for u, v, e in R.edges(data=True))))
|
||||
if wmax == -inf:
|
||||
# Residual network has no edges.
|
||||
return flow_cost, _build_flow_dict(G, R, capacity, weight)
|
||||
|
||||
R_nodes = R.nodes
|
||||
R_succ = R.succ
|
||||
|
||||
delta = 2 ** int(log(wmax, 2))
|
||||
while delta >= 1:
|
||||
# Saturate Δ-residual edges with negative reduced costs to achieve
|
||||
# Δ-optimality.
|
||||
for u in R:
|
||||
p_u = R_nodes[u]["potential"]
|
||||
for v, es in R_succ[u].items():
|
||||
for k, e in es.items():
|
||||
flow = e["capacity"] - e["flow"]
|
||||
if e["weight"] - p_u + R_nodes[v]["potential"] < 0:
|
||||
flow = e["capacity"] - e["flow"]
|
||||
if flow >= delta:
|
||||
e["flow"] += flow
|
||||
R_succ[v][u][(k[0], not k[1])]["flow"] -= flow
|
||||
R_nodes[u]["excess"] -= flow
|
||||
R_nodes[v]["excess"] += flow
|
||||
# Determine the Δ-active nodes.
|
||||
S = set()
|
||||
T = set()
|
||||
S_add = S.add
|
||||
S_remove = S.remove
|
||||
T_add = T.add
|
||||
T_remove = T.remove
|
||||
for u in R:
|
||||
excess = R_nodes[u]["excess"]
|
||||
if excess >= delta:
|
||||
S_add(u)
|
||||
elif excess <= -delta:
|
||||
T_add(u)
|
||||
# Repeatedly augment flow from S to T along shortest paths until
|
||||
# Δ-feasibility is achieved.
|
||||
while S and T:
|
||||
s = arbitrary_element(S)
|
||||
t = None
|
||||
# Search for a shortest path in terms of reduce costs from s to
|
||||
# any t in T in the Δ-residual network.
|
||||
d = {}
|
||||
pred = {s: None}
|
||||
h = heap()
|
||||
h_insert = h.insert
|
||||
h_get = h.get
|
||||
h_insert(s, 0)
|
||||
while h:
|
||||
u, d_u = h.pop()
|
||||
d[u] = d_u
|
||||
if u in T:
|
||||
# Path found.
|
||||
t = u
|
||||
break
|
||||
p_u = R_nodes[u]["potential"]
|
||||
for v, es in R_succ[u].items():
|
||||
if v in d:
|
||||
continue
|
||||
wmin = inf
|
||||
# Find the minimum-weighted (u, v) Δ-residual edge.
|
||||
for k, e in es.items():
|
||||
if e["capacity"] - e["flow"] >= delta:
|
||||
w = e["weight"]
|
||||
if w < wmin:
|
||||
wmin = w
|
||||
kmin = k
|
||||
emin = e
|
||||
if wmin == inf:
|
||||
continue
|
||||
# Update the distance label of v.
|
||||
d_v = d_u + wmin - p_u + R_nodes[v]["potential"]
|
||||
if h_insert(v, d_v):
|
||||
pred[v] = (u, kmin, emin)
|
||||
if t is not None:
|
||||
# Augment Δ units of flow from s to t.
|
||||
while u != s:
|
||||
v = u
|
||||
u, k, e = pred[v]
|
||||
e["flow"] += delta
|
||||
R_succ[v][u][(k[0], not k[1])]["flow"] -= delta
|
||||
# Account node excess and deficit.
|
||||
R_nodes[s]["excess"] -= delta
|
||||
R_nodes[t]["excess"] += delta
|
||||
if R_nodes[s]["excess"] < delta:
|
||||
S_remove(s)
|
||||
if R_nodes[t]["excess"] > -delta:
|
||||
T_remove(t)
|
||||
# Update node potentials.
|
||||
d_t = d[t]
|
||||
for u, d_u in d.items():
|
||||
R_nodes[u]["potential"] -= d_u - d_t
|
||||
else:
|
||||
# Path not found.
|
||||
S_remove(s)
|
||||
delta //= 2
|
||||
|
||||
if any(R.nodes[u]["excess"] != 0 for u in R):
|
||||
raise nx.NetworkXUnfeasible("No flow satisfying all demands.")
|
||||
|
||||
# Calculate the flow cost.
|
||||
for u in R:
|
||||
for v, es in R_succ[u].items():
|
||||
for e in es.values():
|
||||
flow = e["flow"]
|
||||
if flow > 0:
|
||||
flow_cost += flow * e["weight"]
|
||||
|
||||
return flow_cost, _build_flow_dict(G, R, capacity, weight)
|
211
venv/Lib/site-packages/networkx/algorithms/flow/dinitz_alg.py
Normal file
211
venv/Lib/site-packages/networkx/algorithms/flow/dinitz_alg.py
Normal file
|
@ -0,0 +1,211 @@
|
|||
"""
|
||||
Dinitz' algorithm for maximum flow problems.
|
||||
"""
|
||||
from collections import deque
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.flow.utils import build_residual_network
|
||||
from networkx.utils import pairwise
|
||||
|
||||
__all__ = ["dinitz"]
|
||||
|
||||
|
||||
def dinitz(G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None):
|
||||
"""Find a maximum single-commodity flow using Dinitz' 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 m)$ for $n$ nodes and $m$
|
||||
edges [1]_.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
value_only : bool
|
||||
If True compute only the value of the maximum flow. This parameter
|
||||
will be ignored by this algorithm because it is not applicable.
|
||||
|
||||
cutoff : integer, float
|
||||
If specified, the algorithm will terminate when the flow value reaches
|
||||
or exceeds the cutoff. In this case, it may be unable to immediately
|
||||
determine a minimum cut. Default value: None.
|
||||
|
||||
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:`preflow_push`
|
||||
: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 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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.algorithms.flow import dinitz
|
||||
|
||||
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 = dinitz(G, "x", "y")
|
||||
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
||||
>>> flow_value
|
||||
3.0
|
||||
>>> flow_value == R.graph["flow_value"]
|
||||
True
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Dinitz' Algorithm: The Original Version and Even's Version.
|
||||
2006. Yefim Dinitz. In Theoretical Computer Science. Lecture
|
||||
Notes in Computer Science. Volume 3895. pp 218-240.
|
||||
http://www.cs.bgu.ac.il/~dinitz/Papers/Dinitz_alg.pdf
|
||||
|
||||
"""
|
||||
R = dinitz_impl(G, s, t, capacity, residual, cutoff)
|
||||
R.graph["algorithm"] = "dinitz"
|
||||
return R
|
||||
|
||||
|
||||
def dinitz_impl(G, s, t, capacity, residual, cutoff):
|
||||
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 residual is None:
|
||||
R = build_residual_network(G, capacity)
|
||||
else:
|
||||
R = residual
|
||||
|
||||
# Initialize/reset the residual network.
|
||||
for u in R:
|
||||
for e in R[u].values():
|
||||
e["flow"] = 0
|
||||
|
||||
# Use an arbitrary high value as infinite. It is computed
|
||||
# when building the residual network.
|
||||
INF = R.graph["inf"]
|
||||
|
||||
if cutoff is None:
|
||||
cutoff = INF
|
||||
|
||||
R_succ = R.succ
|
||||
R_pred = R.pred
|
||||
|
||||
def breath_first_search():
|
||||
parents = {}
|
||||
queue = deque([s])
|
||||
while queue:
|
||||
if t in parents:
|
||||
break
|
||||
u = queue.popleft()
|
||||
for v in R_succ[u]:
|
||||
attr = R_succ[u][v]
|
||||
if v not in parents and attr["capacity"] - attr["flow"] > 0:
|
||||
parents[v] = u
|
||||
queue.append(v)
|
||||
return parents
|
||||
|
||||
def depth_first_search(parents):
|
||||
"""Build a path using DFS starting from the sink"""
|
||||
path = []
|
||||
u = t
|
||||
flow = INF
|
||||
while u != s:
|
||||
path.append(u)
|
||||
v = parents[u]
|
||||
flow = min(flow, R_pred[u][v]["capacity"] - R_pred[u][v]["flow"])
|
||||
u = v
|
||||
path.append(s)
|
||||
# Augment the flow along the path found
|
||||
if flow > 0:
|
||||
for u, v in pairwise(path):
|
||||
R_pred[u][v]["flow"] += flow
|
||||
R_pred[v][u]["flow"] -= flow
|
||||
return flow
|
||||
|
||||
flow_value = 0
|
||||
while flow_value < cutoff:
|
||||
parents = breath_first_search()
|
||||
if t not in parents:
|
||||
break
|
||||
this_flow = depth_first_search(parents)
|
||||
if this_flow * 2 > INF:
|
||||
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
||||
flow_value += this_flow
|
||||
|
||||
R.graph["flow_value"] = flow_value
|
||||
return R
|
243
venv/Lib/site-packages/networkx/algorithms/flow/edmondskarp.py
Normal file
243
venv/Lib/site-packages/networkx/algorithms/flow/edmondskarp.py
Normal file
|
@ -0,0 +1,243 @@
|
|||
"""
|
||||
Edmonds-Karp algorithm for maximum flow problems.
|
||||
"""
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.flow.utils import build_residual_network
|
||||
|
||||
__all__ = ["edmonds_karp"]
|
||||
|
||||
|
||||
def edmonds_karp_core(R, s, t, cutoff):
|
||||
"""Implementation of the Edmonds-Karp algorithm.
|
||||
"""
|
||||
R_nodes = R.nodes
|
||||
R_pred = R.pred
|
||||
R_succ = R.succ
|
||||
|
||||
inf = R.graph["inf"]
|
||||
|
||||
def augment(path):
|
||||
"""Augment flow along a path from s to t.
|
||||
"""
|
||||
# Determine the path residual capacity.
|
||||
flow = inf
|
||||
it = iter(path)
|
||||
u = next(it)
|
||||
for v in it:
|
||||
attr = R_succ[u][v]
|
||||
flow = min(flow, attr["capacity"] - attr["flow"])
|
||||
u = v
|
||||
if flow * 2 > inf:
|
||||
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
||||
# Augment flow along the path.
|
||||
it = iter(path)
|
||||
u = next(it)
|
||||
for v in it:
|
||||
R_succ[u][v]["flow"] += flow
|
||||
R_succ[v][u]["flow"] -= flow
|
||||
u = v
|
||||
return flow
|
||||
|
||||
def bidirectional_bfs():
|
||||
"""Bidirectional breadth-first search for an augmenting path.
|
||||
"""
|
||||
pred = {s: None}
|
||||
q_s = [s]
|
||||
succ = {t: None}
|
||||
q_t = [t]
|
||||
while True:
|
||||
q = []
|
||||
if len(q_s) <= len(q_t):
|
||||
for u in q_s:
|
||||
for v, attr in R_succ[u].items():
|
||||
if v not in pred and attr["flow"] < attr["capacity"]:
|
||||
pred[v] = u
|
||||
if v in succ:
|
||||
return v, pred, succ
|
||||
q.append(v)
|
||||
if not q:
|
||||
return None, None, None
|
||||
q_s = q
|
||||
else:
|
||||
for u in q_t:
|
||||
for v, attr in R_pred[u].items():
|
||||
if v not in succ and attr["flow"] < attr["capacity"]:
|
||||
succ[v] = u
|
||||
if v in pred:
|
||||
return v, pred, succ
|
||||
q.append(v)
|
||||
if not q:
|
||||
return None, None, None
|
||||
q_t = q
|
||||
|
||||
# Look for shortest augmenting paths using breadth-first search.
|
||||
flow_value = 0
|
||||
while flow_value < cutoff:
|
||||
v, pred, succ = bidirectional_bfs()
|
||||
if pred is None:
|
||||
break
|
||||
path = [v]
|
||||
# Trace a path from s to v.
|
||||
u = v
|
||||
while u != s:
|
||||
u = pred[u]
|
||||
path.append(u)
|
||||
path.reverse()
|
||||
# Trace a path from v to t.
|
||||
u = v
|
||||
while u != t:
|
||||
u = succ[u]
|
||||
path.append(u)
|
||||
flow_value += augment(path)
|
||||
|
||||
return flow_value
|
||||
|
||||
|
||||
def edmonds_karp_impl(G, s, t, capacity, residual, cutoff):
|
||||
"""Implementation of the Edmonds-Karp 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 residual is None:
|
||||
R = build_residual_network(G, capacity)
|
||||
else:
|
||||
R = residual
|
||||
|
||||
# Initialize/reset the residual network.
|
||||
for u in R:
|
||||
for e in R[u].values():
|
||||
e["flow"] = 0
|
||||
|
||||
if cutoff is None:
|
||||
cutoff = float("inf")
|
||||
R.graph["flow_value"] = edmonds_karp_core(R, s, t, cutoff)
|
||||
|
||||
return R
|
||||
|
||||
|
||||
def edmonds_karp(
|
||||
G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None
|
||||
):
|
||||
"""Find a maximum single-commodity flow using the Edmonds-Karp 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 m^2)$ 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.
|
||||
|
||||
value_only : bool
|
||||
If True compute only the value of the maximum flow. This parameter
|
||||
will be ignored by this algorithm because it is not applicable.
|
||||
|
||||
cutoff : integer, float
|
||||
If specified, the algorithm will terminate when the flow value reaches
|
||||
or exceeds the cutoff. In this case, it may be unable to immediately
|
||||
determine a minimum cut. Default value: None.
|
||||
|
||||
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:`preflow_push`
|
||||
: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 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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.algorithms.flow import edmonds_karp
|
||||
|
||||
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 = edmonds_karp(G, "x", "y")
|
||||
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
||||
>>> flow_value
|
||||
3.0
|
||||
>>> flow_value == R.graph["flow_value"]
|
||||
True
|
||||
|
||||
"""
|
||||
R = edmonds_karp_impl(G, s, t, capacity, residual, cutoff)
|
||||
R.graph["algorithm"] = "edmonds_karp"
|
||||
return R
|
176
venv/Lib/site-packages/networkx/algorithms/flow/gomory_hu.py
Normal file
176
venv/Lib/site-packages/networkx/algorithms/flow/gomory_hu.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
"""
|
||||
Gomory-Hu tree of undirected Graphs.
|
||||
"""
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
from .edmondskarp import edmonds_karp
|
||||
from .utils import build_residual_network
|
||||
|
||||
default_flow_func = edmonds_karp
|
||||
|
||||
__all__ = ["gomory_hu_tree"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def gomory_hu_tree(G, capacity="capacity", flow_func=None):
|
||||
r"""Returns the Gomory-Hu tree of an undirected graph G.
|
||||
|
||||
A Gomory-Hu tree of an undirected graph with capacities is a
|
||||
weighted tree that represents the minimum s-t cuts for all s-t
|
||||
pairs in the graph.
|
||||
|
||||
It only requires `n-1` minimum cut computations instead of the
|
||||
obvious `n(n-1)/2`. The tree represents all s-t cuts as the
|
||||
minimum cut value among any pair of nodes is the minimum edge
|
||||
weight in the shortest path between the two nodes in the
|
||||
Gomory-Hu tree.
|
||||
|
||||
The Gomory-Hu tree also has the property that removing the
|
||||
edge with the minimum weight in the shortest path between
|
||||
any two nodes leaves two connected components that form
|
||||
a partition of the nodes in G that defines the minimum s-t
|
||||
cut.
|
||||
|
||||
See Examples section below for details.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
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'.
|
||||
|
||||
flow_func : function
|
||||
Function to perform the underlying flow computations. Default value
|
||||
:func:`edmonds_karp`. This function performs better in sparse graphs
|
||||
with right tailed degree distributions.
|
||||
:func:`shortest_augmenting_path` will perform better in denser
|
||||
graphs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tree : NetworkX graph
|
||||
A NetworkX graph representing the Gomory-Hu tree of the input graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
Raised if the input graph is directed.
|
||||
|
||||
NetworkXError
|
||||
Raised if the input graph is an empty Graph.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.karate_club_graph()
|
||||
>>> nx.set_edge_attributes(G, 1, "capacity")
|
||||
>>> T = nx.gomory_hu_tree(G)
|
||||
>>> # The value of the minimum cut between any pair
|
||||
... # of nodes in G is the minimum edge weight in the
|
||||
... # shortest path between the two nodes in the
|
||||
... # Gomory-Hu tree.
|
||||
... def minimum_edge_weight_in_shortest_path(T, u, v):
|
||||
... path = nx.shortest_path(T, u, v, weight="weight")
|
||||
... return min((T[u][v]["weight"], (u, v)) for (u, v) in zip(path, path[1:]))
|
||||
>>> u, v = 0, 33
|
||||
>>> cut_value, edge = minimum_edge_weight_in_shortest_path(T, u, v)
|
||||
>>> cut_value
|
||||
10
|
||||
>>> nx.minimum_cut_value(G, u, v)
|
||||
10
|
||||
>>> # The Comory-Hu tree also has the property that removing the
|
||||
... # edge with the minimum weight in the shortest path between
|
||||
... # any two nodes leaves two connected components that form
|
||||
... # a partition of the nodes in G that defines the minimum s-t
|
||||
... # cut.
|
||||
... cut_value, edge = minimum_edge_weight_in_shortest_path(T, u, v)
|
||||
>>> T.remove_edge(*edge)
|
||||
>>> U, V = list(nx.connected_components(T))
|
||||
>>> # Thus U and V form a partition that defines a minimum cut
|
||||
... # between u and v in G. You can compute the edge cut set,
|
||||
... # that is, the set of edges that if removed from G will
|
||||
... # disconnect u from v in G, with this information:
|
||||
... cutset = set()
|
||||
>>> for x, nbrs in ((n, G[n]) for n in U):
|
||||
... cutset.update((x, y) for y in nbrs if y in V)
|
||||
>>> # Because we have set the capacities of all edges to 1
|
||||
... # the cutset contains ten edges
|
||||
... len(cutset)
|
||||
10
|
||||
>>> # You can use any maximum flow algorithm for the underlying
|
||||
... # flow computations using the argument flow_func
|
||||
... from networkx.algorithms import flow
|
||||
>>> T = nx.gomory_hu_tree(G, flow_func=flow.boykov_kolmogorov)
|
||||
>>> cut_value, edge = minimum_edge_weight_in_shortest_path(T, u, v)
|
||||
>>> cut_value
|
||||
10
|
||||
>>> nx.minimum_cut_value(G, u, v, flow_func=flow.boykov_kolmogorov)
|
||||
10
|
||||
|
||||
Notes
|
||||
-----
|
||||
This implementation is based on Gusfield approach [1]_ to compute
|
||||
Comory-Hu trees, which does not require node contractions and has
|
||||
the same computational complexity than the original method.
|
||||
|
||||
See also
|
||||
--------
|
||||
:func:`minimum_cut`
|
||||
:func:`maximum_flow`
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Gusfield D: Very simple methods for all pairs network flow analysis.
|
||||
SIAM J Comput 19(1):143-155, 1990.
|
||||
|
||||
"""
|
||||
if flow_func is None:
|
||||
flow_func = default_flow_func
|
||||
|
||||
if len(G) == 0: # empty graph
|
||||
msg = "Empty Graph does not have a Gomory-Hu tree representation"
|
||||
raise nx.NetworkXError(msg)
|
||||
|
||||
# Start the tree as a star graph with an arbitrary node at the center
|
||||
tree = {}
|
||||
labels = {}
|
||||
iter_nodes = iter(G)
|
||||
root = next(iter_nodes)
|
||||
for n in iter_nodes:
|
||||
tree[n] = root
|
||||
|
||||
# Reuse residual network
|
||||
R = build_residual_network(G, capacity)
|
||||
|
||||
# For all the leaves in the star graph tree (that is n-1 nodes).
|
||||
for source in tree:
|
||||
# Find neighbor in the tree
|
||||
target = tree[source]
|
||||
# compute minimum cut
|
||||
cut_value, partition = nx.minimum_cut(
|
||||
G, source, target, capacity=capacity, flow_func=flow_func, residual=R
|
||||
)
|
||||
labels[(source, target)] = cut_value
|
||||
# Update the tree
|
||||
# Source will always be in partition[0] and target in partition[1]
|
||||
for node in partition[0]:
|
||||
if node != source and node in tree and tree[node] == target:
|
||||
tree[node] = source
|
||||
labels[node, source] = labels.get((node, target), cut_value)
|
||||
#
|
||||
if target != root and tree[target] in partition[0]:
|
||||
labels[source, tree[target]] = labels[target, tree[target]]
|
||||
labels[target, source] = cut_value
|
||||
tree[source] = tree[target]
|
||||
tree[target] = source
|
||||
|
||||
# Build the tree
|
||||
T = nx.Graph()
|
||||
T.add_nodes_from(G)
|
||||
T.add_weighted_edges_from(((u, v, labels[u, v]) for u, v in tree.items()))
|
||||
return T
|
611
venv/Lib/site-packages/networkx/algorithms/flow/maxflow.py
Normal file
611
venv/Lib/site-packages/networkx/algorithms/flow/maxflow.py
Normal file
|
@ -0,0 +1,611 @@
|
|||
"""
|
||||
Maximum flow (and minimum cut) algorithms on capacitated graphs.
|
||||
"""
|
||||
import networkx as nx
|
||||
|
||||
from .boykovkolmogorov import boykov_kolmogorov
|
||||
from .dinitz_alg import dinitz
|
||||
from .edmondskarp import edmonds_karp
|
||||
from .preflowpush import preflow_push
|
||||
from .shortestaugmentingpath import shortest_augmenting_path
|
||||
from .utils import build_flow_dict
|
||||
|
||||
# Define the default flow function for computing maximum flow.
|
||||
default_flow_func = preflow_push
|
||||
# Functions that don't support cutoff for minimum cut computations.
|
||||
flow_funcs = [
|
||||
boykov_kolmogorov,
|
||||
dinitz,
|
||||
edmonds_karp,
|
||||
preflow_push,
|
||||
shortest_augmenting_path,
|
||||
]
|
||||
|
||||
__all__ = ["maximum_flow", "maximum_flow_value", "minimum_cut", "minimum_cut_value"]
|
||||
|
||||
|
||||
def maximum_flow(flowG, _s, _t, capacity="capacity", flow_func=None, **kwargs):
|
||||
"""Find a maximum single-commodity flow.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
flowG : 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'.
|
||||
|
||||
flow_func : function
|
||||
A function for computing the maximum flow among a pair of nodes
|
||||
in a capacitated graph. The function has to accept at least three
|
||||
parameters: a Graph or Digraph, a source node, and a target node.
|
||||
And return a residual network that follows NetworkX conventions
|
||||
(see Notes). If flow_func is None, the default maximum
|
||||
flow function (:meth:`preflow_push`) is used. See below for
|
||||
alternative algorithms. The choice of the default function may change
|
||||
from version to version and should not be relied on. Default value:
|
||||
None.
|
||||
|
||||
kwargs : Any other keyword parameter is passed to the function that
|
||||
computes the maximum flow.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flow_value : integer, float
|
||||
Value of the maximum flow, i.e., net outflow from the source.
|
||||
|
||||
flow_dict : dict
|
||||
A dictionary containing the value of the flow that went through
|
||||
each edge.
|
||||
|
||||
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_value`
|
||||
:meth:`minimum_cut`
|
||||
:meth:`minimum_cut_value`
|
||||
:meth:`edmonds_karp`
|
||||
:meth:`preflow_push`
|
||||
:meth:`shortest_augmenting_path`
|
||||
|
||||
Notes
|
||||
-----
|
||||
The function used in the flow_func parameter has to return a residual
|
||||
network that follows NetworkX conventions:
|
||||
|
||||
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']`. 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.
|
||||
|
||||
Specific algorithms may store extra data in :samp:`R`.
|
||||
|
||||
The function should supports an optional boolean parameter value_only. When
|
||||
True, it can optionally terminate the algorithm as soon as the maximum flow
|
||||
value and the minimum cut can be determined.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> 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)
|
||||
|
||||
maximum_flow returns both the value of the maximum flow and a
|
||||
dictionary with all flows.
|
||||
|
||||
>>> flow_value, flow_dict = nx.maximum_flow(G, "x", "y")
|
||||
>>> flow_value
|
||||
3.0
|
||||
>>> print(flow_dict["x"]["b"])
|
||||
1.0
|
||||
|
||||
You can also use alternative algorithms for computing the
|
||||
maximum flow by using the flow_func parameter.
|
||||
|
||||
>>> from networkx.algorithms.flow import shortest_augmenting_path
|
||||
>>> flow_value == nx.maximum_flow(G, "x", "y", flow_func=shortest_augmenting_path)[
|
||||
... 0
|
||||
... ]
|
||||
True
|
||||
|
||||
"""
|
||||
if flow_func is None:
|
||||
if kwargs:
|
||||
raise nx.NetworkXError(
|
||||
"You have to explicitly set a flow_func if"
|
||||
" you need to pass parameters via kwargs."
|
||||
)
|
||||
flow_func = default_flow_func
|
||||
|
||||
if not callable(flow_func):
|
||||
raise nx.NetworkXError("flow_func has to be callable.")
|
||||
|
||||
R = flow_func(flowG, _s, _t, capacity=capacity, value_only=False, **kwargs)
|
||||
flow_dict = build_flow_dict(flowG, R)
|
||||
|
||||
return (R.graph["flow_value"], flow_dict)
|
||||
|
||||
|
||||
def maximum_flow_value(flowG, _s, _t, capacity="capacity", flow_func=None, **kwargs):
|
||||
"""Find the value of maximum single-commodity flow.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
flowG : 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'.
|
||||
|
||||
flow_func : function
|
||||
A function for computing the maximum flow among a pair of nodes
|
||||
in a capacitated graph. The function has to accept at least three
|
||||
parameters: a Graph or Digraph, a source node, and a target node.
|
||||
And return a residual network that follows NetworkX conventions
|
||||
(see Notes). If flow_func is None, the default maximum
|
||||
flow function (:meth:`preflow_push`) is used. See below for
|
||||
alternative algorithms. The choice of the default function may change
|
||||
from version to version and should not be relied on. Default value:
|
||||
None.
|
||||
|
||||
kwargs : Any other keyword parameter is passed to the function that
|
||||
computes the maximum flow.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flow_value : integer, float
|
||||
Value of the maximum flow, i.e., net outflow from the source.
|
||||
|
||||
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:`minimum_cut_value`
|
||||
:meth:`edmonds_karp`
|
||||
:meth:`preflow_push`
|
||||
:meth:`shortest_augmenting_path`
|
||||
|
||||
Notes
|
||||
-----
|
||||
The function used in the flow_func parameter has to return a residual
|
||||
network that follows NetworkX conventions:
|
||||
|
||||
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']`. 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.
|
||||
|
||||
Specific algorithms may store extra data in :samp:`R`.
|
||||
|
||||
The function should supports an optional boolean parameter value_only. When
|
||||
True, it can optionally terminate the algorithm as soon as the maximum flow
|
||||
value and the minimum cut can be determined.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> 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)
|
||||
|
||||
maximum_flow_value computes only the value of the
|
||||
maximum flow:
|
||||
|
||||
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
||||
>>> flow_value
|
||||
3.0
|
||||
|
||||
You can also use alternative algorithms for computing the
|
||||
maximum flow by using the flow_func parameter.
|
||||
|
||||
>>> from networkx.algorithms.flow import shortest_augmenting_path
|
||||
>>> flow_value == nx.maximum_flow_value(
|
||||
... G, "x", "y", flow_func=shortest_augmenting_path
|
||||
... )
|
||||
True
|
||||
|
||||
"""
|
||||
if flow_func is None:
|
||||
if kwargs:
|
||||
raise nx.NetworkXError(
|
||||
"You have to explicitly set a flow_func if"
|
||||
" you need to pass parameters via kwargs."
|
||||
)
|
||||
flow_func = default_flow_func
|
||||
|
||||
if not callable(flow_func):
|
||||
raise nx.NetworkXError("flow_func has to be callable.")
|
||||
|
||||
R = flow_func(flowG, _s, _t, capacity=capacity, value_only=True, **kwargs)
|
||||
|
||||
return R.graph["flow_value"]
|
||||
|
||||
|
||||
def minimum_cut(flowG, _s, _t, capacity="capacity", flow_func=None, **kwargs):
|
||||
"""Compute the value and the node partition of a minimum (s, t)-cut.
|
||||
|
||||
Use the max-flow min-cut theorem, i.e., the capacity of a minimum
|
||||
capacity cut is equal to the flow value of a maximum flow.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
flowG : 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'.
|
||||
|
||||
flow_func : function
|
||||
A function for computing the maximum flow among a pair of nodes
|
||||
in a capacitated graph. The function has to accept at least three
|
||||
parameters: a Graph or Digraph, a source node, and a target node.
|
||||
And return a residual network that follows NetworkX conventions
|
||||
(see Notes). If flow_func is None, the default maximum
|
||||
flow function (:meth:`preflow_push`) is used. See below for
|
||||
alternative algorithms. The choice of the default function may change
|
||||
from version to version and should not be relied on. Default value:
|
||||
None.
|
||||
|
||||
kwargs : Any other keyword parameter is passed to the function that
|
||||
computes the maximum flow.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cut_value : integer, float
|
||||
Value of the minimum cut.
|
||||
|
||||
partition : pair of node sets
|
||||
A partitioning of the nodes that defines a minimum cut.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXUnbounded
|
||||
If the graph has a path of infinite capacity, all cuts have
|
||||
infinite capacity and the function raises a NetworkXError.
|
||||
|
||||
See also
|
||||
--------
|
||||
:meth:`maximum_flow`
|
||||
:meth:`maximum_flow_value`
|
||||
:meth:`minimum_cut_value`
|
||||
:meth:`edmonds_karp`
|
||||
:meth:`preflow_push`
|
||||
:meth:`shortest_augmenting_path`
|
||||
|
||||
Notes
|
||||
-----
|
||||
The function used in the flow_func parameter has to return a residual
|
||||
network that follows NetworkX conventions:
|
||||
|
||||
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']`. 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.
|
||||
|
||||
Specific algorithms may store extra data in :samp:`R`.
|
||||
|
||||
The function should supports an optional boolean parameter value_only. When
|
||||
True, it can optionally terminate the algorithm as soon as the maximum flow
|
||||
value and the minimum cut can be determined.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> 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)
|
||||
|
||||
minimum_cut computes both the value of the
|
||||
minimum cut and the node partition:
|
||||
|
||||
>>> cut_value, partition = nx.minimum_cut(G, "x", "y")
|
||||
>>> reachable, non_reachable = partition
|
||||
|
||||
'partition' here is a tuple with the two sets of nodes that define
|
||||
the minimum cut. You can compute the cut set of edges that induce
|
||||
the minimum cut as follows:
|
||||
|
||||
>>> cutset = set()
|
||||
>>> for u, nbrs in ((n, G[n]) for n in reachable):
|
||||
... cutset.update((u, v) for v in nbrs if v in non_reachable)
|
||||
>>> print(sorted(cutset))
|
||||
[('c', 'y'), ('x', 'b')]
|
||||
>>> cut_value == sum(G.edges[u, v]["capacity"] for (u, v) in cutset)
|
||||
True
|
||||
|
||||
You can also use alternative algorithms for computing the
|
||||
minimum cut by using the flow_func parameter.
|
||||
|
||||
>>> from networkx.algorithms.flow import shortest_augmenting_path
|
||||
>>> cut_value == nx.minimum_cut(G, "x", "y", flow_func=shortest_augmenting_path)[0]
|
||||
True
|
||||
|
||||
"""
|
||||
if flow_func is None:
|
||||
if kwargs:
|
||||
raise nx.NetworkXError(
|
||||
"You have to explicitly set a flow_func if"
|
||||
" you need to pass parameters via kwargs."
|
||||
)
|
||||
flow_func = default_flow_func
|
||||
|
||||
if not callable(flow_func):
|
||||
raise nx.NetworkXError("flow_func has to be callable.")
|
||||
|
||||
if kwargs.get("cutoff") is not None and flow_func in flow_funcs:
|
||||
raise nx.NetworkXError("cutoff should not be specified.")
|
||||
|
||||
R = flow_func(flowG, _s, _t, capacity=capacity, value_only=True, **kwargs)
|
||||
# Remove saturated edges from the residual network
|
||||
cutset = [(u, v, d) for u, v, d in R.edges(data=True) if d["flow"] == d["capacity"]]
|
||||
R.remove_edges_from(cutset)
|
||||
|
||||
# Then, reachable and non reachable nodes from source in the
|
||||
# residual network form the node partition that defines
|
||||
# the minimum cut.
|
||||
non_reachable = set(dict(nx.shortest_path_length(R, target=_t)))
|
||||
partition = (set(flowG) - non_reachable, non_reachable)
|
||||
# Finally add again cutset edges to the residual network to make
|
||||
# sure that it is reusable.
|
||||
if cutset is not None:
|
||||
R.add_edges_from(cutset)
|
||||
return (R.graph["flow_value"], partition)
|
||||
|
||||
|
||||
def minimum_cut_value(flowG, _s, _t, capacity="capacity", flow_func=None, **kwargs):
|
||||
"""Compute the value of a minimum (s, t)-cut.
|
||||
|
||||
Use the max-flow min-cut theorem, i.e., the capacity of a minimum
|
||||
capacity cut is equal to the flow value of a maximum flow.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
flowG : 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'.
|
||||
|
||||
flow_func : function
|
||||
A function for computing the maximum flow among a pair of nodes
|
||||
in a capacitated graph. The function has to accept at least three
|
||||
parameters: a Graph or Digraph, a source node, and a target node.
|
||||
And return a residual network that follows NetworkX conventions
|
||||
(see Notes). If flow_func is None, the default maximum
|
||||
flow function (:meth:`preflow_push`) is used. See below for
|
||||
alternative algorithms. The choice of the default function may change
|
||||
from version to version and should not be relied on. Default value:
|
||||
None.
|
||||
|
||||
kwargs : Any other keyword parameter is passed to the function that
|
||||
computes the maximum flow.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cut_value : integer, float
|
||||
Value of the minimum cut.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXUnbounded
|
||||
If the graph has a path of infinite capacity, all cuts have
|
||||
infinite capacity and the function raises a NetworkXError.
|
||||
|
||||
See also
|
||||
--------
|
||||
:meth:`maximum_flow`
|
||||
:meth:`maximum_flow_value`
|
||||
:meth:`minimum_cut`
|
||||
:meth:`edmonds_karp`
|
||||
:meth:`preflow_push`
|
||||
:meth:`shortest_augmenting_path`
|
||||
|
||||
Notes
|
||||
-----
|
||||
The function used in the flow_func parameter has to return a residual
|
||||
network that follows NetworkX conventions:
|
||||
|
||||
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']`. 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.
|
||||
|
||||
Specific algorithms may store extra data in :samp:`R`.
|
||||
|
||||
The function should supports an optional boolean parameter value_only. When
|
||||
True, it can optionally terminate the algorithm as soon as the maximum flow
|
||||
value and the minimum cut can be determined.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> 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)
|
||||
|
||||
minimum_cut_value computes only the value of the
|
||||
minimum cut:
|
||||
|
||||
>>> cut_value = nx.minimum_cut_value(G, "x", "y")
|
||||
>>> cut_value
|
||||
3.0
|
||||
|
||||
You can also use alternative algorithms for computing the
|
||||
minimum cut by using the flow_func parameter.
|
||||
|
||||
>>> from networkx.algorithms.flow import shortest_augmenting_path
|
||||
>>> cut_value == nx.minimum_cut_value(
|
||||
... G, "x", "y", flow_func=shortest_augmenting_path
|
||||
... )
|
||||
True
|
||||
|
||||
"""
|
||||
if flow_func is None:
|
||||
if kwargs:
|
||||
raise nx.NetworkXError(
|
||||
"You have to explicitly set a flow_func if"
|
||||
" you need to pass parameters via kwargs."
|
||||
)
|
||||
flow_func = default_flow_func
|
||||
|
||||
if not callable(flow_func):
|
||||
raise nx.NetworkXError("flow_func has to be callable.")
|
||||
|
||||
if kwargs.get("cutoff") is not None and flow_func in flow_funcs:
|
||||
raise nx.NetworkXError("cutoff should not be specified.")
|
||||
|
||||
R = flow_func(flowG, _s, _t, capacity=capacity, value_only=True, **kwargs)
|
||||
|
||||
return R.graph["flow_value"]
|
331
venv/Lib/site-packages/networkx/algorithms/flow/mincost.py
Normal file
331
venv/Lib/site-packages/networkx/algorithms/flow/mincost.py
Normal file
|
@ -0,0 +1,331 @@
|
|||
"""
|
||||
Minimum cost flow algorithms on directed connected graphs.
|
||||
"""
|
||||
|
||||
__all__ = ["min_cost_flow_cost", "min_cost_flow", "cost_of_flow", "max_flow_min_cost"]
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def min_cost_flow_cost(G, demand="demand", capacity="capacity", weight="weight"):
|
||||
r"""Find the cost of a minimum cost flow satisfying all demands in digraph G.
|
||||
|
||||
G is a digraph with edge costs and capacities and in which nodes
|
||||
have demand, i.e., they want to send or receive some amount of
|
||||
flow. A negative demand means that the node wants to send flow, a
|
||||
positive demand means that the node want to receive flow. A flow on
|
||||
the digraph G satisfies all demand if the net flow into each node
|
||||
is equal to the demand of that node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
DiGraph on which a minimum cost flow satisfying all demands is
|
||||
to be found.
|
||||
|
||||
demand : string
|
||||
Nodes of the graph G are expected to have an attribute demand
|
||||
that indicates how much flow a node wants to send (negative
|
||||
demand) or receive (positive demand). Note that the sum of the
|
||||
demands should be 0 otherwise the problem in not feasible. If
|
||||
this attribute is not present, a node is considered to have 0
|
||||
demand. Default value: 'demand'.
|
||||
|
||||
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'.
|
||||
|
||||
weight : string
|
||||
Edges of the graph G are expected to have an attribute weight
|
||||
that indicates the cost incurred by sending one unit of flow on
|
||||
that edge. If not present, the weight is considered to be 0.
|
||||
Default value: 'weight'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flowCost : integer, float
|
||||
Cost of a minimum cost flow satisfying all demands.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
This exception is raised if the input graph is not directed or
|
||||
not connected.
|
||||
|
||||
NetworkXUnfeasible
|
||||
This exception is raised in the following situations:
|
||||
|
||||
* The sum of the demands is not zero. Then, there is no
|
||||
flow satisfying all demands.
|
||||
* There is no flow satisfying all demand.
|
||||
|
||||
NetworkXUnbounded
|
||||
This exception is raised if the digraph G has a cycle of
|
||||
negative cost and infinite capacity. Then, the cost of a flow
|
||||
satisfying all demands is unbounded below.
|
||||
|
||||
See also
|
||||
--------
|
||||
cost_of_flow, max_flow_min_cost, min_cost_flow, network_simplex
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm is not guaranteed to work if edge weights or demands
|
||||
are floating point numbers (overflows and roundoff errors can
|
||||
cause problems). As a workaround you can use integer numbers by
|
||||
multiplying the relevant edge attributes by a convenient
|
||||
constant factor (eg 100).
|
||||
|
||||
Examples
|
||||
--------
|
||||
A simple example of a min cost flow problem.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_node("a", demand=-5)
|
||||
>>> G.add_node("d", demand=5)
|
||||
>>> G.add_edge("a", "b", weight=3, capacity=4)
|
||||
>>> G.add_edge("a", "c", weight=6, capacity=10)
|
||||
>>> G.add_edge("b", "d", weight=1, capacity=9)
|
||||
>>> G.add_edge("c", "d", weight=2, capacity=5)
|
||||
>>> flowCost = nx.min_cost_flow_cost(G)
|
||||
>>> flowCost
|
||||
24
|
||||
"""
|
||||
return nx.network_simplex(G, demand=demand, capacity=capacity, weight=weight)[0]
|
||||
|
||||
|
||||
def min_cost_flow(G, demand="demand", capacity="capacity", weight="weight"):
|
||||
r"""Returns a minimum cost flow satisfying all demands in digraph G.
|
||||
|
||||
G is a digraph with edge costs and capacities and in which nodes
|
||||
have demand, i.e., they want to send or receive some amount of
|
||||
flow. A negative demand means that the node wants to send flow, a
|
||||
positive demand means that the node want to receive flow. A flow on
|
||||
the digraph G satisfies all demand if the net flow into each node
|
||||
is equal to the demand of that node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
DiGraph on which a minimum cost flow satisfying all demands is
|
||||
to be found.
|
||||
|
||||
demand : string
|
||||
Nodes of the graph G are expected to have an attribute demand
|
||||
that indicates how much flow a node wants to send (negative
|
||||
demand) or receive (positive demand). Note that the sum of the
|
||||
demands should be 0 otherwise the problem in not feasible. If
|
||||
this attribute is not present, a node is considered to have 0
|
||||
demand. Default value: 'demand'.
|
||||
|
||||
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'.
|
||||
|
||||
weight : string
|
||||
Edges of the graph G are expected to have an attribute weight
|
||||
that indicates the cost incurred by sending one unit of flow on
|
||||
that edge. If not present, the weight is considered to be 0.
|
||||
Default value: 'weight'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flowDict : dictionary
|
||||
Dictionary of dictionaries keyed by nodes such that
|
||||
flowDict[u][v] is the flow edge (u, v).
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
This exception is raised if the input graph is not directed or
|
||||
not connected.
|
||||
|
||||
NetworkXUnfeasible
|
||||
This exception is raised in the following situations:
|
||||
|
||||
* The sum of the demands is not zero. Then, there is no
|
||||
flow satisfying all demands.
|
||||
* There is no flow satisfying all demand.
|
||||
|
||||
NetworkXUnbounded
|
||||
This exception is raised if the digraph G has a cycle of
|
||||
negative cost and infinite capacity. Then, the cost of a flow
|
||||
satisfying all demands is unbounded below.
|
||||
|
||||
See also
|
||||
--------
|
||||
cost_of_flow, max_flow_min_cost, min_cost_flow_cost, network_simplex
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm is not guaranteed to work if edge weights or demands
|
||||
are floating point numbers (overflows and roundoff errors can
|
||||
cause problems). As a workaround you can use integer numbers by
|
||||
multiplying the relevant edge attributes by a convenient
|
||||
constant factor (eg 100).
|
||||
|
||||
Examples
|
||||
--------
|
||||
A simple example of a min cost flow problem.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_node("a", demand=-5)
|
||||
>>> G.add_node("d", demand=5)
|
||||
>>> G.add_edge("a", "b", weight=3, capacity=4)
|
||||
>>> G.add_edge("a", "c", weight=6, capacity=10)
|
||||
>>> G.add_edge("b", "d", weight=1, capacity=9)
|
||||
>>> G.add_edge("c", "d", weight=2, capacity=5)
|
||||
>>> flowDict = nx.min_cost_flow(G)
|
||||
"""
|
||||
return nx.network_simplex(G, demand=demand, capacity=capacity, weight=weight)[1]
|
||||
|
||||
|
||||
def cost_of_flow(G, flowDict, weight="weight"):
|
||||
"""Compute the cost of the flow given by flowDict on graph G.
|
||||
|
||||
Note that this function does not check for the validity of the
|
||||
flow flowDict. This function will fail if the graph G and the
|
||||
flow don't have the same edge set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
DiGraph on which a minimum cost flow satisfying all demands is
|
||||
to be found.
|
||||
|
||||
weight : string
|
||||
Edges of the graph G are expected to have an attribute weight
|
||||
that indicates the cost incurred by sending one unit of flow on
|
||||
that edge. If not present, the weight is considered to be 0.
|
||||
Default value: 'weight'.
|
||||
|
||||
flowDict : dictionary
|
||||
Dictionary of dictionaries keyed by nodes such that
|
||||
flowDict[u][v] is the flow edge (u, v).
|
||||
|
||||
Returns
|
||||
-------
|
||||
cost : Integer, float
|
||||
The total cost of the flow. This is given by the sum over all
|
||||
edges of the product of the edge's flow and the edge's weight.
|
||||
|
||||
See also
|
||||
--------
|
||||
max_flow_min_cost, min_cost_flow, min_cost_flow_cost, network_simplex
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm is not guaranteed to work if edge weights or demands
|
||||
are floating point numbers (overflows and roundoff errors can
|
||||
cause problems). As a workaround you can use integer numbers by
|
||||
multiplying the relevant edge attributes by a convenient
|
||||
constant factor (eg 100).
|
||||
"""
|
||||
return sum((flowDict[u][v] * d.get(weight, 0) for u, v, d in G.edges(data=True)))
|
||||
|
||||
|
||||
def max_flow_min_cost(G, s, t, capacity="capacity", weight="weight"):
|
||||
"""Returns a maximum (s, t)-flow of minimum cost.
|
||||
|
||||
G is a digraph with edge costs and capacities. There is a source
|
||||
node s and a sink node t. This function finds a maximum flow from
|
||||
s to t whose total cost is minimized.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
DiGraph on which a minimum cost flow satisfying all demands is
|
||||
to be found.
|
||||
|
||||
s: node label
|
||||
Source of the flow.
|
||||
|
||||
t: node label
|
||||
Destination of 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'.
|
||||
|
||||
weight: string
|
||||
Edges of the graph G are expected to have an attribute weight
|
||||
that indicates the cost incurred by sending one unit of flow on
|
||||
that edge. If not present, the weight is considered to be 0.
|
||||
Default value: 'weight'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flowDict: dictionary
|
||||
Dictionary of dictionaries keyed by nodes such that
|
||||
flowDict[u][v] is the flow edge (u, v).
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
This exception is raised if the input graph is not directed or
|
||||
not connected.
|
||||
|
||||
NetworkXUnbounded
|
||||
This exception is raised if there is an infinite capacity path
|
||||
from s to t in G. In this case there is no maximum flow. This
|
||||
exception is also raised if the digraph G has a cycle of
|
||||
negative cost and infinite capacity. Then, the cost of a flow
|
||||
is unbounded below.
|
||||
|
||||
See also
|
||||
--------
|
||||
cost_of_flow, min_cost_flow, min_cost_flow_cost, network_simplex
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm is not guaranteed to work if edge weights or demands
|
||||
are floating point numbers (overflows and roundoff errors can
|
||||
cause problems). As a workaround you can use integer numbers by
|
||||
multiplying the relevant edge attributes by a convenient
|
||||
constant factor (eg 100).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_edges_from(
|
||||
... [
|
||||
... (1, 2, {"capacity": 12, "weight": 4}),
|
||||
... (1, 3, {"capacity": 20, "weight": 6}),
|
||||
... (2, 3, {"capacity": 6, "weight": -3}),
|
||||
... (2, 6, {"capacity": 14, "weight": 1}),
|
||||
... (3, 4, {"weight": 9}),
|
||||
... (3, 5, {"capacity": 10, "weight": 5}),
|
||||
... (4, 2, {"capacity": 19, "weight": 13}),
|
||||
... (4, 5, {"capacity": 4, "weight": 0}),
|
||||
... (5, 7, {"capacity": 28, "weight": 2}),
|
||||
... (6, 5, {"capacity": 11, "weight": 1}),
|
||||
... (6, 7, {"weight": 8}),
|
||||
... (7, 4, {"capacity": 6, "weight": 6}),
|
||||
... ]
|
||||
... )
|
||||
>>> mincostFlow = nx.max_flow_min_cost(G, 1, 7)
|
||||
>>> mincost = nx.cost_of_flow(G, mincostFlow)
|
||||
>>> mincost
|
||||
373
|
||||
>>> from networkx.algorithms.flow import maximum_flow
|
||||
>>> maxFlow = maximum_flow(G, 1, 7)[1]
|
||||
>>> nx.cost_of_flow(G, maxFlow) >= mincost
|
||||
True
|
||||
>>> mincostFlowValue = sum((mincostFlow[u][7] for u in G.predecessors(7))) - sum(
|
||||
... (mincostFlow[7][v] for v in G.successors(7))
|
||||
... )
|
||||
>>> mincostFlowValue == nx.maximum_flow_value(G, 1, 7)
|
||||
True
|
||||
|
||||
"""
|
||||
maxFlow = nx.maximum_flow_value(G, s, t, capacity=capacity)
|
||||
H = nx.DiGraph(G)
|
||||
H.add_node(s, demand=-maxFlow)
|
||||
H.add_node(t, demand=maxFlow)
|
||||
return min_cost_flow(H, capacity=capacity, weight=weight)
|
|
@ -0,0 +1,598 @@
|
|||
"""
|
||||
Minimum cost flow algorithms on directed connected graphs.
|
||||
"""
|
||||
|
||||
__all__ = ["network_simplex"]
|
||||
|
||||
from itertools import chain, islice, repeat
|
||||
from math import ceil, sqrt
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
|
||||
@not_implemented_for("undirected")
|
||||
def network_simplex(G, demand="demand", capacity="capacity", weight="weight"):
|
||||
r"""Find a minimum cost flow satisfying all demands in digraph G.
|
||||
|
||||
This is a primal network simplex algorithm that uses the leaving
|
||||
arc rule to prevent cycling.
|
||||
|
||||
G is a digraph with edge costs and capacities and in which nodes
|
||||
have demand, i.e., they want to send or receive some amount of
|
||||
flow. A negative demand means that the node wants to send flow, a
|
||||
positive demand means that the node want to receive flow. A flow on
|
||||
the digraph G satisfies all demand if the net flow into each node
|
||||
is equal to the demand of that node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
DiGraph on which a minimum cost flow satisfying all demands is
|
||||
to be found.
|
||||
|
||||
demand : string
|
||||
Nodes of the graph G are expected to have an attribute demand
|
||||
that indicates how much flow a node wants to send (negative
|
||||
demand) or receive (positive demand). Note that the sum of the
|
||||
demands should be 0 otherwise the problem in not feasible. If
|
||||
this attribute is not present, a node is considered to have 0
|
||||
demand. Default value: 'demand'.
|
||||
|
||||
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'.
|
||||
|
||||
weight : string
|
||||
Edges of the graph G are expected to have an attribute weight
|
||||
that indicates the cost incurred by sending one unit of flow on
|
||||
that edge. If not present, the weight is considered to be 0.
|
||||
Default value: 'weight'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flowCost : integer, float
|
||||
Cost of a minimum cost flow satisfying all demands.
|
||||
|
||||
flowDict : dictionary
|
||||
Dictionary of dictionaries keyed by nodes such that
|
||||
flowDict[u][v] is the flow edge (u, v).
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
This exception is raised if the input graph is not directed,
|
||||
not connected or is a multigraph.
|
||||
|
||||
NetworkXUnfeasible
|
||||
This exception is raised in the following situations:
|
||||
|
||||
* The sum of the demands is not zero. Then, there is no
|
||||
flow satisfying all demands.
|
||||
* There is no flow satisfying all demand.
|
||||
|
||||
NetworkXUnbounded
|
||||
This exception is raised if the digraph G has a cycle of
|
||||
negative cost and infinite capacity. Then, the cost of a flow
|
||||
satisfying all demands is unbounded below.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm is not guaranteed to work if edge weights or demands
|
||||
are floating point numbers (overflows and roundoff errors can
|
||||
cause problems). As a workaround you can use integer numbers by
|
||||
multiplying the relevant edge attributes by a convenient
|
||||
constant factor (eg 100).
|
||||
|
||||
See also
|
||||
--------
|
||||
cost_of_flow, max_flow_min_cost, min_cost_flow, min_cost_flow_cost
|
||||
|
||||
Examples
|
||||
--------
|
||||
A simple example of a min cost flow problem.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_node("a", demand=-5)
|
||||
>>> G.add_node("d", demand=5)
|
||||
>>> G.add_edge("a", "b", weight=3, capacity=4)
|
||||
>>> G.add_edge("a", "c", weight=6, capacity=10)
|
||||
>>> G.add_edge("b", "d", weight=1, capacity=9)
|
||||
>>> G.add_edge("c", "d", weight=2, capacity=5)
|
||||
>>> flowCost, flowDict = nx.network_simplex(G)
|
||||
>>> flowCost
|
||||
24
|
||||
>>> flowDict # doctest: +SKIP
|
||||
{'a': {'c': 1, 'b': 4}, 'c': {'d': 1}, 'b': {'d': 4}, 'd': {}}
|
||||
|
||||
The mincost flow algorithm can also be used to solve shortest path
|
||||
problems. To find the shortest path between two nodes u and v,
|
||||
give all edges an infinite capacity, give node u a demand of -1 and
|
||||
node v a demand a 1. Then run the network simplex. The value of a
|
||||
min cost flow will be the distance between u and v and edges
|
||||
carrying positive flow will indicate the path.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_weighted_edges_from(
|
||||
... [
|
||||
... ("s", "u", 10),
|
||||
... ("s", "x", 5),
|
||||
... ("u", "v", 1),
|
||||
... ("u", "x", 2),
|
||||
... ("v", "y", 1),
|
||||
... ("x", "u", 3),
|
||||
... ("x", "v", 5),
|
||||
... ("x", "y", 2),
|
||||
... ("y", "s", 7),
|
||||
... ("y", "v", 6),
|
||||
... ]
|
||||
... )
|
||||
>>> G.add_node("s", demand=-1)
|
||||
>>> G.add_node("v", demand=1)
|
||||
>>> flowCost, flowDict = nx.network_simplex(G)
|
||||
>>> flowCost == nx.shortest_path_length(G, "s", "v", weight="weight")
|
||||
True
|
||||
>>> sorted([(u, v) for u in flowDict for v in flowDict[u] if flowDict[u][v] > 0])
|
||||
[('s', 'x'), ('u', 'v'), ('x', 'u')]
|
||||
>>> nx.shortest_path(G, "s", "v", weight="weight")
|
||||
['s', 'x', 'u', 'v']
|
||||
|
||||
It is possible to change the name of the attributes used for the
|
||||
algorithm.
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> G.add_node("p", spam=-4)
|
||||
>>> G.add_node("q", spam=2)
|
||||
>>> G.add_node("a", spam=-2)
|
||||
>>> G.add_node("d", spam=-1)
|
||||
>>> G.add_node("t", spam=2)
|
||||
>>> G.add_node("w", spam=3)
|
||||
>>> G.add_edge("p", "q", cost=7, vacancies=5)
|
||||
>>> G.add_edge("p", "a", cost=1, vacancies=4)
|
||||
>>> G.add_edge("q", "d", cost=2, vacancies=3)
|
||||
>>> G.add_edge("t", "q", cost=1, vacancies=2)
|
||||
>>> G.add_edge("a", "t", cost=2, vacancies=4)
|
||||
>>> G.add_edge("d", "w", cost=3, vacancies=4)
|
||||
>>> G.add_edge("t", "w", cost=4, vacancies=1)
|
||||
>>> flowCost, flowDict = nx.network_simplex(
|
||||
... G, demand="spam", capacity="vacancies", weight="cost"
|
||||
... )
|
||||
>>> flowCost
|
||||
37
|
||||
>>> flowDict # doctest: +SKIP
|
||||
{'a': {'t': 4}, 'd': {'w': 2}, 'q': {'d': 1}, 'p': {'q': 2, 'a': 2}, 't': {'q': 1, 'w': 1}, 'w': {}}
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Z. Kiraly, P. Kovacs.
|
||||
Efficient implementation of minimum-cost flow algorithms.
|
||||
Acta Universitatis Sapientiae, Informatica 4(1):67--118. 2012.
|
||||
.. [2] R. Barr, F. Glover, D. Klingman.
|
||||
Enhancement of spanning tree labeling procedures for network
|
||||
optimization.
|
||||
INFOR 17(1):16--34. 1979.
|
||||
"""
|
||||
###########################################################################
|
||||
# Problem essentials extraction and sanity check
|
||||
###########################################################################
|
||||
|
||||
if len(G) == 0:
|
||||
raise nx.NetworkXError("graph has no nodes")
|
||||
|
||||
# Number all nodes and edges and hereafter reference them using ONLY their
|
||||
# numbers
|
||||
|
||||
N = list(G) # nodes
|
||||
I = {u: i for i, u in enumerate(N)} # node indices
|
||||
D = [G.nodes[u].get(demand, 0) for u in N] # node demands
|
||||
|
||||
inf = float("inf")
|
||||
for p, b in zip(N, D):
|
||||
if abs(b) == inf:
|
||||
raise nx.NetworkXError(f"node {p!r} has infinite demand")
|
||||
|
||||
multigraph = G.is_multigraph()
|
||||
S = [] # edge sources
|
||||
T = [] # edge targets
|
||||
if multigraph:
|
||||
K = [] # edge keys
|
||||
E = {} # edge indices
|
||||
U = [] # edge capacities
|
||||
C = [] # edge weights
|
||||
|
||||
if not multigraph:
|
||||
edges = G.edges(data=True)
|
||||
else:
|
||||
edges = G.edges(data=True, keys=True)
|
||||
edges = (e for e in edges if e[0] != e[1] and e[-1].get(capacity, inf) != 0)
|
||||
for i, e in enumerate(edges):
|
||||
S.append(I[e[0]])
|
||||
T.append(I[e[1]])
|
||||
if multigraph:
|
||||
K.append(e[2])
|
||||
E[e[:-1]] = i
|
||||
U.append(e[-1].get(capacity, inf))
|
||||
C.append(e[-1].get(weight, 0))
|
||||
|
||||
for e, c in zip(E, C):
|
||||
if abs(c) == inf:
|
||||
raise nx.NetworkXError(f"edge {e!r} has infinite weight")
|
||||
if not multigraph:
|
||||
edges = nx.selfloop_edges(G, data=True)
|
||||
else:
|
||||
edges = nx.selfloop_edges(G, data=True, keys=True)
|
||||
for e in edges:
|
||||
if abs(e[-1].get(weight, 0)) == inf:
|
||||
raise nx.NetworkXError(f"edge {e[:-1]!r} has infinite weight")
|
||||
|
||||
###########################################################################
|
||||
# Quick infeasibility detection
|
||||
###########################################################################
|
||||
|
||||
if sum(D) != 0:
|
||||
raise nx.NetworkXUnfeasible("total node demand is not zero")
|
||||
for e, u in zip(E, U):
|
||||
if u < 0:
|
||||
raise nx.NetworkXUnfeasible(f"edge {e!r} has negative capacity")
|
||||
if not multigraph:
|
||||
edges = nx.selfloop_edges(G, data=True)
|
||||
else:
|
||||
edges = nx.selfloop_edges(G, data=True, keys=True)
|
||||
for e in edges:
|
||||
if e[-1].get(capacity, inf) < 0:
|
||||
raise nx.NetworkXUnfeasible(f"edge {e[:-1]!r} has negative capacity")
|
||||
|
||||
###########################################################################
|
||||
# Initialization
|
||||
###########################################################################
|
||||
|
||||
# Add a dummy node -1 and connect all existing nodes to it with infinite-
|
||||
# capacity dummy edges. Node -1 will serve as the root of the
|
||||
# spanning tree of the network simplex method. The new edges will used to
|
||||
# trivially satisfy the node demands and create an initial strongly
|
||||
# feasible spanning tree.
|
||||
n = len(N) # number of nodes
|
||||
for p, d in enumerate(D):
|
||||
# Must be greater-than here. Zero-demand nodes must have
|
||||
# edges pointing towards the root to ensure strong
|
||||
# feasibility.
|
||||
if d > 0:
|
||||
S.append(-1)
|
||||
T.append(p)
|
||||
else:
|
||||
S.append(p)
|
||||
T.append(-1)
|
||||
faux_inf = (
|
||||
3
|
||||
* max(
|
||||
chain(
|
||||
[sum(u for u in U if u < inf), sum(abs(c) for c in C)],
|
||||
(abs(d) for d in D),
|
||||
)
|
||||
)
|
||||
or 1
|
||||
)
|
||||
C.extend(repeat(faux_inf, n))
|
||||
U.extend(repeat(faux_inf, n))
|
||||
|
||||
# Construct the initial spanning tree.
|
||||
e = len(E) # number of edges
|
||||
x = list(chain(repeat(0, e), (abs(d) for d in D))) # edge flows
|
||||
pi = [faux_inf if d <= 0 else -faux_inf for d in D] # node potentials
|
||||
parent = list(chain(repeat(-1, n), [None])) # parent nodes
|
||||
edge = list(range(e, e + n)) # edges to parents
|
||||
size = list(chain(repeat(1, n), [n + 1])) # subtree sizes
|
||||
next = list(chain(range(1, n), [-1, 0])) # next nodes in depth-first thread
|
||||
prev = list(range(-1, n)) # previous nodes in depth-first thread
|
||||
last = list(chain(range(n), [n - 1])) # last descendants in depth-first thread
|
||||
|
||||
###########################################################################
|
||||
# Pivot loop
|
||||
###########################################################################
|
||||
|
||||
def reduced_cost(i):
|
||||
"""Returns the reduced cost of an edge i.
|
||||
"""
|
||||
c = C[i] - pi[S[i]] + pi[T[i]]
|
||||
return c if x[i] == 0 else -c
|
||||
|
||||
def find_entering_edges():
|
||||
"""Yield entering edges until none can be found.
|
||||
"""
|
||||
if e == 0:
|
||||
return
|
||||
|
||||
# Entering edges are found by combining Dantzig's rule and Bland's
|
||||
# rule. The edges are cyclically grouped into blocks of size B. Within
|
||||
# each block, Dantzig's rule is applied to find an entering edge. The
|
||||
# blocks to search is determined following Bland's rule.
|
||||
B = int(ceil(sqrt(e))) # pivot block size
|
||||
M = (e + B - 1) // B # number of blocks needed to cover all edges
|
||||
m = 0 # number of consecutive blocks without eligible
|
||||
# entering edges
|
||||
f = 0 # first edge in block
|
||||
while m < M:
|
||||
# Determine the next block of edges.
|
||||
l = f + B
|
||||
if l <= e:
|
||||
edges = range(f, l)
|
||||
else:
|
||||
l -= e
|
||||
edges = chain(range(f, e), range(l))
|
||||
f = l
|
||||
# Find the first edge with the lowest reduced cost.
|
||||
i = min(edges, key=reduced_cost)
|
||||
c = reduced_cost(i)
|
||||
if c >= 0:
|
||||
# No entering edge found in the current block.
|
||||
m += 1
|
||||
else:
|
||||
# Entering edge found.
|
||||
if x[i] == 0:
|
||||
p = S[i]
|
||||
q = T[i]
|
||||
else:
|
||||
p = T[i]
|
||||
q = S[i]
|
||||
yield i, p, q
|
||||
m = 0
|
||||
# All edges have nonnegative reduced costs. The current flow is
|
||||
# optimal.
|
||||
|
||||
def find_apex(p, q):
|
||||
"""Find the lowest common ancestor of nodes p and q in the spanning
|
||||
tree.
|
||||
"""
|
||||
size_p = size[p]
|
||||
size_q = size[q]
|
||||
while True:
|
||||
while size_p < size_q:
|
||||
p = parent[p]
|
||||
size_p = size[p]
|
||||
while size_p > size_q:
|
||||
q = parent[q]
|
||||
size_q = size[q]
|
||||
if size_p == size_q:
|
||||
if p != q:
|
||||
p = parent[p]
|
||||
size_p = size[p]
|
||||
q = parent[q]
|
||||
size_q = size[q]
|
||||
else:
|
||||
return p
|
||||
|
||||
def trace_path(p, w):
|
||||
"""Returns the nodes and edges on the path from node p to its ancestor
|
||||
w.
|
||||
"""
|
||||
Wn = [p]
|
||||
We = []
|
||||
while p != w:
|
||||
We.append(edge[p])
|
||||
p = parent[p]
|
||||
Wn.append(p)
|
||||
return Wn, We
|
||||
|
||||
def find_cycle(i, p, q):
|
||||
"""Returns the nodes and edges on the cycle containing edge i == (p, q)
|
||||
when the latter is added to the spanning tree.
|
||||
|
||||
The cycle is oriented in the direction from p to q.
|
||||
"""
|
||||
w = find_apex(p, q)
|
||||
Wn, We = trace_path(p, w)
|
||||
Wn.reverse()
|
||||
We.reverse()
|
||||
if We != [i]:
|
||||
We.append(i)
|
||||
WnR, WeR = trace_path(q, w)
|
||||
del WnR[-1]
|
||||
Wn += WnR
|
||||
We += WeR
|
||||
return Wn, We
|
||||
|
||||
def residual_capacity(i, p):
|
||||
"""Returns the residual capacity of an edge i in the direction away
|
||||
from its endpoint p.
|
||||
"""
|
||||
return U[i] - x[i] if S[i] == p else x[i]
|
||||
|
||||
def find_leaving_edge(Wn, We):
|
||||
"""Returns the leaving edge in a cycle represented by Wn and We.
|
||||
"""
|
||||
j, s = min(
|
||||
zip(reversed(We), reversed(Wn)), key=lambda i_p: residual_capacity(*i_p)
|
||||
)
|
||||
t = T[j] if S[j] == s else S[j]
|
||||
return j, s, t
|
||||
|
||||
def augment_flow(Wn, We, f):
|
||||
"""Augment f units of flow along a cycle represented by Wn and We.
|
||||
"""
|
||||
for i, p in zip(We, Wn):
|
||||
if S[i] == p:
|
||||
x[i] += f
|
||||
else:
|
||||
x[i] -= f
|
||||
|
||||
def trace_subtree(p):
|
||||
"""Yield the nodes in the subtree rooted at a node p.
|
||||
"""
|
||||
yield p
|
||||
l = last[p]
|
||||
while p != l:
|
||||
p = next[p]
|
||||
yield p
|
||||
|
||||
def remove_edge(s, t):
|
||||
"""Remove an edge (s, t) where parent[t] == s from the spanning tree.
|
||||
"""
|
||||
size_t = size[t]
|
||||
prev_t = prev[t]
|
||||
last_t = last[t]
|
||||
next_last_t = next[last_t]
|
||||
# Remove (s, t).
|
||||
parent[t] = None
|
||||
edge[t] = None
|
||||
# Remove the subtree rooted at t from the depth-first thread.
|
||||
next[prev_t] = next_last_t
|
||||
prev[next_last_t] = prev_t
|
||||
next[last_t] = t
|
||||
prev[t] = last_t
|
||||
# Update the subtree sizes and last descendants of the (old) acenstors
|
||||
# of t.
|
||||
while s is not None:
|
||||
size[s] -= size_t
|
||||
if last[s] == last_t:
|
||||
last[s] = prev_t
|
||||
s = parent[s]
|
||||
|
||||
def make_root(q):
|
||||
"""Make a node q the root of its containing subtree.
|
||||
"""
|
||||
ancestors = []
|
||||
while q is not None:
|
||||
ancestors.append(q)
|
||||
q = parent[q]
|
||||
ancestors.reverse()
|
||||
for p, q in zip(ancestors, islice(ancestors, 1, None)):
|
||||
size_p = size[p]
|
||||
last_p = last[p]
|
||||
prev_q = prev[q]
|
||||
last_q = last[q]
|
||||
next_last_q = next[last_q]
|
||||
# Make p a child of q.
|
||||
parent[p] = q
|
||||
parent[q] = None
|
||||
edge[p] = edge[q]
|
||||
edge[q] = None
|
||||
size[p] = size_p - size[q]
|
||||
size[q] = size_p
|
||||
# Remove the subtree rooted at q from the depth-first thread.
|
||||
next[prev_q] = next_last_q
|
||||
prev[next_last_q] = prev_q
|
||||
next[last_q] = q
|
||||
prev[q] = last_q
|
||||
if last_p == last_q:
|
||||
last[p] = prev_q
|
||||
last_p = prev_q
|
||||
# Add the remaining parts of the subtree rooted at p as a subtree
|
||||
# of q in the depth-first thread.
|
||||
prev[p] = last_q
|
||||
next[last_q] = p
|
||||
next[last_p] = q
|
||||
prev[q] = last_p
|
||||
last[q] = last_p
|
||||
|
||||
def add_edge(i, p, q):
|
||||
"""Add an edge (p, q) to the spanning tree where q is the root of a
|
||||
subtree.
|
||||
"""
|
||||
last_p = last[p]
|
||||
next_last_p = next[last_p]
|
||||
size_q = size[q]
|
||||
last_q = last[q]
|
||||
# Make q a child of p.
|
||||
parent[q] = p
|
||||
edge[q] = i
|
||||
# Insert the subtree rooted at q into the depth-first thread.
|
||||
next[last_p] = q
|
||||
prev[q] = last_p
|
||||
prev[next_last_p] = last_q
|
||||
next[last_q] = next_last_p
|
||||
# Update the subtree sizes and last descendants of the (new) ancestors
|
||||
# of q.
|
||||
while p is not None:
|
||||
size[p] += size_q
|
||||
if last[p] == last_p:
|
||||
last[p] = last_q
|
||||
p = parent[p]
|
||||
|
||||
def update_potentials(i, p, q):
|
||||
"""Update the potentials of the nodes in the subtree rooted at a node
|
||||
q connected to its parent p by an edge i.
|
||||
"""
|
||||
if q == T[i]:
|
||||
d = pi[p] - C[i] - pi[q]
|
||||
else:
|
||||
d = pi[p] + C[i] - pi[q]
|
||||
for q in trace_subtree(q):
|
||||
pi[q] += d
|
||||
|
||||
# Pivot loop
|
||||
for i, p, q in find_entering_edges():
|
||||
Wn, We = find_cycle(i, p, q)
|
||||
j, s, t = find_leaving_edge(Wn, We)
|
||||
augment_flow(Wn, We, residual_capacity(j, s))
|
||||
# Do nothing more if the entering edge is the same as the leaving edge.
|
||||
if i != j:
|
||||
if parent[t] != s:
|
||||
# Ensure that s is the parent of t.
|
||||
s, t = t, s
|
||||
if We.index(i) > We.index(j):
|
||||
# Ensure that q is in the subtree rooted at t.
|
||||
p, q = q, p
|
||||
remove_edge(s, t)
|
||||
make_root(q)
|
||||
add_edge(i, p, q)
|
||||
update_potentials(i, p, q)
|
||||
|
||||
###########################################################################
|
||||
# Infeasibility and unboundedness detection
|
||||
###########################################################################
|
||||
|
||||
if any(x[i] != 0 for i in range(-n, 0)):
|
||||
raise nx.NetworkXUnfeasible("no flow satisfies all node demands")
|
||||
|
||||
if any(x[i] * 2 >= faux_inf for i in range(e)) or any(
|
||||
e[-1].get(capacity, inf) == inf and e[-1].get(weight, 0) < 0
|
||||
for e in nx.selfloop_edges(G, data=True)
|
||||
):
|
||||
raise nx.NetworkXUnbounded("negative cycle with infinite capacity found")
|
||||
|
||||
###########################################################################
|
||||
# Flow cost calculation and flow dict construction
|
||||
###########################################################################
|
||||
|
||||
del x[e:]
|
||||
flow_cost = sum(c * x for c, x in zip(C, x))
|
||||
flow_dict = {n: {} for n in N}
|
||||
|
||||
def add_entry(e):
|
||||
"""Add a flow dict entry.
|
||||
"""
|
||||
d = flow_dict[e[0]]
|
||||
for k in e[1:-2]:
|
||||
try:
|
||||
d = d[k]
|
||||
except KeyError:
|
||||
t = {}
|
||||
d[k] = t
|
||||
d = t
|
||||
d[e[-2]] = e[-1]
|
||||
|
||||
S = (N[s] for s in S) # Use original nodes.
|
||||
T = (N[t] for t in T) # Use original nodes.
|
||||
if not multigraph:
|
||||
for e in zip(S, T, x):
|
||||
add_entry(e)
|
||||
edges = G.edges(data=True)
|
||||
else:
|
||||
for e in zip(S, T, K, x):
|
||||
add_entry(e)
|
||||
edges = G.edges(data=True, keys=True)
|
||||
for e in edges:
|
||||
if e[0] != e[1]:
|
||||
if e[-1].get(capacity, inf) == 0:
|
||||
add_entry(e[:-1] + (0,))
|
||||
else:
|
||||
c = e[-1].get(weight, 0)
|
||||
if c >= 0:
|
||||
add_entry(e[:-1] + (0,))
|
||||
else:
|
||||
u = e[-1][capacity]
|
||||
flow_cost += c * u
|
||||
add_entry(e[:-1] + (u,))
|
||||
|
||||
return flow_cost, flow_dict
|
425
venv/Lib/site-packages/networkx/algorithms/flow/preflowpush.py
Normal file
425
venv/Lib/site-packages/networkx/algorithms/flow/preflowpush.py
Normal file
|
@ -0,0 +1,425 @@
|
|||
"""
|
||||
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 build_residual_network
|
||||
from .utils import CurrentEdge
|
||||
from .utils import detect_unboundedness
|
||||
from .utils import GlobalRelabelThreshold
|
||||
from .utils import Level
|
||||
|
||||
__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
|
|
@ -0,0 +1,299 @@
|
|||
"""
|
||||
Shortest augmenting path algorithm for maximum flow problems.
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
import networkx as nx
|
||||
from .utils import build_residual_network, CurrentEdge
|
||||
from .edmondskarp import edmonds_karp_core
|
||||
|
||||
__all__ = ["shortest_augmenting_path"]
|
||||
|
||||
|
||||
def shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff):
|
||||
"""Implementation of the shortest augmenting path 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 residual is None:
|
||||
R = build_residual_network(G, capacity)
|
||||
else:
|
||||
R = residual
|
||||
|
||||
R_nodes = R.nodes
|
||||
R_pred = R.pred
|
||||
R_succ = R.succ
|
||||
|
||||
# Initialize/reset the residual network.
|
||||
for u in R:
|
||||
for e in R_succ[u].values():
|
||||
e["flow"] = 0
|
||||
|
||||
# Initialize heights of the nodes.
|
||||
heights = {t: 0}
|
||||
q = deque([(t, 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))
|
||||
|
||||
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(G)
|
||||
m = R.size() / 2
|
||||
|
||||
# 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
|
||||
R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u])
|
||||
|
||||
# Initialize counts of nodes in each level.
|
||||
counts = [0] * (2 * n - 1)
|
||||
for u in R:
|
||||
counts[R_nodes[u]["height"]] += 1
|
||||
|
||||
inf = R.graph["inf"]
|
||||
|
||||
def augment(path):
|
||||
"""Augment flow along a path from s to t.
|
||||
"""
|
||||
# Determine the path residual capacity.
|
||||
flow = inf
|
||||
it = iter(path)
|
||||
u = next(it)
|
||||
for v in it:
|
||||
attr = R_succ[u][v]
|
||||
flow = min(flow, attr["capacity"] - attr["flow"])
|
||||
u = v
|
||||
if flow * 2 > inf:
|
||||
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
||||
# Augment flow along the path.
|
||||
it = iter(path)
|
||||
u = next(it)
|
||||
for v in it:
|
||||
R_succ[u][v]["flow"] += flow
|
||||
R_succ[v][u]["flow"] -= flow
|
||||
u = v
|
||||
return flow
|
||||
|
||||
def relabel(u):
|
||||
"""Relabel a node to create an admissible edge.
|
||||
"""
|
||||
height = n - 1
|
||||
for v, attr in R_succ[u].items():
|
||||
if attr["flow"] < attr["capacity"]:
|
||||
height = min(height, R_nodes[v]["height"])
|
||||
return height + 1
|
||||
|
||||
if cutoff is None:
|
||||
cutoff = float("inf")
|
||||
|
||||
# Phase 1: Look for shortest augmenting paths using depth-first search.
|
||||
|
||||
flow_value = 0
|
||||
path = [s]
|
||||
u = s
|
||||
d = n if not two_phase else int(min(m ** 0.5, 2 * n ** (2.0 / 3)))
|
||||
done = R_nodes[s]["height"] >= d
|
||||
while not done:
|
||||
height = R_nodes[u]["height"]
|
||||
curr_edge = R_nodes[u]["curr_edge"]
|
||||
# Depth-first search for the next node on the path to t.
|
||||
while True:
|
||||
v, attr = curr_edge.get()
|
||||
if height == R_nodes[v]["height"] + 1 and attr["flow"] < attr["capacity"]:
|
||||
# Advance to the next node following an admissible edge.
|
||||
path.append(v)
|
||||
u = v
|
||||
break
|
||||
try:
|
||||
curr_edge.move_to_next()
|
||||
except StopIteration:
|
||||
counts[height] -= 1
|
||||
if counts[height] == 0:
|
||||
# Gap heuristic: If relabeling causes a level to become
|
||||
# empty, a minimum cut has been identified. The algorithm
|
||||
# can now be terminated.
|
||||
R.graph["flow_value"] = flow_value
|
||||
return R
|
||||
height = relabel(u)
|
||||
if u == s and height >= d:
|
||||
if not two_phase:
|
||||
# t is disconnected from s in the residual network. No
|
||||
# more augmenting paths exist.
|
||||
R.graph["flow_value"] = flow_value
|
||||
return R
|
||||
else:
|
||||
# t is at least d steps away from s. End of phase 1.
|
||||
done = True
|
||||
break
|
||||
counts[height] += 1
|
||||
R_nodes[u]["height"] = height
|
||||
if u != s:
|
||||
# After relabeling, the last edge on the path is no longer
|
||||
# admissible. Retreat one step to look for an alternative.
|
||||
path.pop()
|
||||
u = path[-1]
|
||||
break
|
||||
if u == t:
|
||||
# t is reached. Augment flow along the path and reset it for a new
|
||||
# depth-first search.
|
||||
flow_value += augment(path)
|
||||
if flow_value >= cutoff:
|
||||
R.graph["flow_value"] = flow_value
|
||||
return R
|
||||
path = [s]
|
||||
u = s
|
||||
|
||||
# Phase 2: Look for shortest augmenting paths using breadth-first search.
|
||||
flow_value += edmonds_karp_core(R, s, t, cutoff - flow_value)
|
||||
|
||||
R.graph["flow_value"] = flow_value
|
||||
return R
|
||||
|
||||
|
||||
def shortest_augmenting_path(
|
||||
G,
|
||||
s,
|
||||
t,
|
||||
capacity="capacity",
|
||||
residual=None,
|
||||
value_only=False,
|
||||
two_phase=False,
|
||||
cutoff=None,
|
||||
):
|
||||
r"""Find a maximum single-commodity flow using the shortest augmenting path
|
||||
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 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.
|
||||
|
||||
value_only : bool
|
||||
If True compute only the value of the maximum flow. This parameter
|
||||
will be ignored by this algorithm because it is not applicable.
|
||||
|
||||
two_phase : bool
|
||||
If True, a two-phase variant is used. The two-phase variant improves
|
||||
the running time on unit-capacity networks from $O(nm)$ to
|
||||
$O(\min(n^{2/3}, m^{1/2}) m)$. Default value: False.
|
||||
|
||||
cutoff : integer, float
|
||||
If specified, the algorithm will terminate when the flow value reaches
|
||||
or exceeds the cutoff. In this case, it may be unable to immediately
|
||||
determine a minimum cut. Default value: None.
|
||||
|
||||
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:`preflow_push`
|
||||
|
||||
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 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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.algorithms.flow import shortest_augmenting_path
|
||||
|
||||
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 = shortest_augmenting_path(G, "x", "y")
|
||||
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
||||
>>> flow_value
|
||||
3.0
|
||||
>>> flow_value == R.graph["flow_value"]
|
||||
True
|
||||
|
||||
"""
|
||||
R = shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff)
|
||||
R.graph["algorithm"] = "shortest_augmenting_path"
|
||||
return R
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,125 @@
|
|||
from itertools import combinations
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.flow import boykov_kolmogorov
|
||||
from networkx.algorithms.flow import edmonds_karp
|
||||
from networkx.algorithms.flow import preflow_push
|
||||
from networkx.algorithms.flow import shortest_augmenting_path
|
||||
from networkx.algorithms.flow import dinitz
|
||||
|
||||
flow_funcs = [
|
||||
boykov_kolmogorov,
|
||||
dinitz,
|
||||
edmonds_karp,
|
||||
preflow_push,
|
||||
shortest_augmenting_path,
|
||||
]
|
||||
|
||||
|
||||
class TestGomoryHuTree:
|
||||
def minimum_edge_weight(self, T, u, v):
|
||||
path = nx.shortest_path(T, u, v, weight="weight")
|
||||
return min((T[u][v]["weight"], (u, v)) for (u, v) in zip(path, path[1:]))
|
||||
|
||||
def compute_cutset(self, G, T_orig, edge):
|
||||
T = T_orig.copy()
|
||||
T.remove_edge(*edge)
|
||||
U, V = list(nx.connected_components(T))
|
||||
cutset = set()
|
||||
for x, nbrs in ((n, G[n]) for n in U):
|
||||
cutset.update((x, y) for y in nbrs if y in V)
|
||||
return cutset
|
||||
|
||||
def test_default_flow_function_karate_club_graph(self):
|
||||
G = nx.karate_club_graph()
|
||||
nx.set_edge_attributes(G, 1, "capacity")
|
||||
T = nx.gomory_hu_tree(G)
|
||||
assert nx.is_tree(T)
|
||||
for u, v in combinations(G, 2):
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
assert nx.minimum_cut_value(G, u, v) == cut_value
|
||||
|
||||
def test_karate_club_graph(self):
|
||||
G = nx.karate_club_graph()
|
||||
nx.set_edge_attributes(G, 1, "capacity")
|
||||
for flow_func in flow_funcs:
|
||||
T = nx.gomory_hu_tree(G, flow_func=flow_func)
|
||||
assert nx.is_tree(T)
|
||||
for u, v in combinations(G, 2):
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
assert nx.minimum_cut_value(G, u, v) == cut_value
|
||||
|
||||
def test_davis_southern_women_graph(self):
|
||||
G = nx.davis_southern_women_graph()
|
||||
nx.set_edge_attributes(G, 1, "capacity")
|
||||
for flow_func in flow_funcs:
|
||||
T = nx.gomory_hu_tree(G, flow_func=flow_func)
|
||||
assert nx.is_tree(T)
|
||||
for u, v in combinations(G, 2):
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
assert nx.minimum_cut_value(G, u, v) == cut_value
|
||||
|
||||
def test_florentine_families_graph(self):
|
||||
G = nx.florentine_families_graph()
|
||||
nx.set_edge_attributes(G, 1, "capacity")
|
||||
for flow_func in flow_funcs:
|
||||
T = nx.gomory_hu_tree(G, flow_func=flow_func)
|
||||
assert nx.is_tree(T)
|
||||
for u, v in combinations(G, 2):
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
assert nx.minimum_cut_value(G, u, v) == cut_value
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_les_miserables_graph_cutset(self):
|
||||
G = nx.les_miserables_graph()
|
||||
nx.set_edge_attributes(G, 1, "capacity")
|
||||
for flow_func in flow_funcs:
|
||||
T = nx.gomory_hu_tree(G, flow_func=flow_func)
|
||||
assert nx.is_tree(T)
|
||||
for u, v in combinations(G, 2):
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
assert nx.minimum_cut_value(G, u, v) == cut_value
|
||||
|
||||
def test_karate_club_graph_cutset(self):
|
||||
G = nx.karate_club_graph()
|
||||
nx.set_edge_attributes(G, 1, "capacity")
|
||||
T = nx.gomory_hu_tree(G)
|
||||
assert nx.is_tree(T)
|
||||
u, v = 0, 33
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
cutset = self.compute_cutset(G, T, edge)
|
||||
assert cut_value == len(cutset)
|
||||
|
||||
def test_wikipedia_example(self):
|
||||
# Example from https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from(
|
||||
(
|
||||
(0, 1, 1),
|
||||
(0, 2, 7),
|
||||
(1, 2, 1),
|
||||
(1, 3, 3),
|
||||
(1, 4, 2),
|
||||
(2, 4, 4),
|
||||
(3, 4, 1),
|
||||
(3, 5, 6),
|
||||
(4, 5, 2),
|
||||
)
|
||||
)
|
||||
for flow_func in flow_funcs:
|
||||
T = nx.gomory_hu_tree(G, capacity="weight", flow_func=flow_func)
|
||||
assert nx.is_tree(T)
|
||||
for u, v in combinations(G, 2):
|
||||
cut_value, edge = self.minimum_edge_weight(T, u, v)
|
||||
assert nx.minimum_cut_value(G, u, v, capacity="weight") == cut_value
|
||||
|
||||
def test_directed_raises(self):
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
G = nx.DiGraph()
|
||||
T = nx.gomory_hu_tree(G)
|
||||
|
||||
def test_empty_raises(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.empty_graph()
|
||||
T = nx.gomory_hu_tree(G)
|
|
@ -0,0 +1,548 @@
|
|||
"""Maximum flow algorithms test suite.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.flow import build_flow_dict, build_residual_network
|
||||
from networkx.algorithms.flow import boykov_kolmogorov
|
||||
from networkx.algorithms.flow import edmonds_karp
|
||||
from networkx.algorithms.flow import preflow_push
|
||||
from networkx.algorithms.flow import shortest_augmenting_path
|
||||
from networkx.algorithms.flow import dinitz
|
||||
|
||||
flow_funcs = [
|
||||
boykov_kolmogorov,
|
||||
dinitz,
|
||||
edmonds_karp,
|
||||
preflow_push,
|
||||
shortest_augmenting_path,
|
||||
]
|
||||
max_min_funcs = [nx.maximum_flow, nx.minimum_cut]
|
||||
flow_value_funcs = [nx.maximum_flow_value, nx.minimum_cut_value]
|
||||
interface_funcs = sum([max_min_funcs, flow_value_funcs], [])
|
||||
all_funcs = sum([flow_funcs, interface_funcs], [])
|
||||
|
||||
|
||||
def compute_cutset(G, partition):
|
||||
reachable, non_reachable = partition
|
||||
cutset = set()
|
||||
for u, nbrs in ((n, G[n]) for n in reachable):
|
||||
cutset.update((u, v) for v in nbrs if v in non_reachable)
|
||||
return cutset
|
||||
|
||||
|
||||
def validate_flows(G, s, t, flowDict, solnValue, capacity, flow_func):
|
||||
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
||||
assert set(G) == set(flowDict), errmsg
|
||||
for u in G:
|
||||
assert set(G[u]) == set(flowDict[u]), errmsg
|
||||
excess = {u: 0 for u in flowDict}
|
||||
for u in flowDict:
|
||||
for v, flow in flowDict[u].items():
|
||||
if capacity in G[u][v]:
|
||||
assert flow <= G[u][v][capacity]
|
||||
assert flow >= 0, errmsg
|
||||
excess[u] -= flow
|
||||
excess[v] += flow
|
||||
for u, exc in excess.items():
|
||||
if u == s:
|
||||
assert exc == -solnValue, errmsg
|
||||
elif u == t:
|
||||
assert exc == solnValue, errmsg
|
||||
else:
|
||||
assert exc == 0, errmsg
|
||||
|
||||
|
||||
def validate_cuts(G, s, t, solnValue, partition, capacity, flow_func):
|
||||
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
||||
assert all(n in G for n in partition[0]), errmsg
|
||||
assert all(n in G for n in partition[1]), errmsg
|
||||
cutset = compute_cutset(G, partition)
|
||||
assert all(G.has_edge(u, v) for (u, v) in cutset), errmsg
|
||||
assert solnValue == sum(G[u][v][capacity] for (u, v) in cutset), errmsg
|
||||
H = G.copy()
|
||||
H.remove_edges_from(cutset)
|
||||
if not G.is_directed():
|
||||
assert not nx.is_connected(H), errmsg
|
||||
else:
|
||||
assert not nx.is_strongly_connected(H), errmsg
|
||||
|
||||
|
||||
def compare_flows_and_cuts(G, s, t, solnFlows, solnValue, capacity="capacity"):
|
||||
for flow_func in flow_funcs:
|
||||
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
||||
R = flow_func(G, s, t, capacity)
|
||||
# Test both legacy and new implementations.
|
||||
flow_value = R.graph["flow_value"]
|
||||
flow_dict = build_flow_dict(G, R)
|
||||
assert flow_value == solnValue, errmsg
|
||||
validate_flows(G, s, t, flow_dict, solnValue, capacity, flow_func)
|
||||
# Minimum cut
|
||||
cut_value, partition = nx.minimum_cut(
|
||||
G, s, t, capacity=capacity, flow_func=flow_func
|
||||
)
|
||||
validate_cuts(G, s, t, solnValue, partition, capacity, flow_func)
|
||||
|
||||
|
||||
class TestMaxflowMinCutCommon:
|
||||
def test_graph1(self):
|
||||
# Trivial undirected graph
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, capacity=1.0)
|
||||
|
||||
solnFlows = {1: {2: 1.0}, 2: {1: 1.0}}
|
||||
|
||||
compare_flows_and_cuts(G, 1, 2, solnFlows, 1.0)
|
||||
|
||||
def test_graph2(self):
|
||||
# A more complex undirected graph
|
||||
# adapted from www.topcoder.com/tc?module=Statc&d1=tutorials&d2=maxFlow
|
||||
G = nx.Graph()
|
||||
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)
|
||||
|
||||
H = {
|
||||
"x": {"a": 3, "b": 1},
|
||||
"a": {"c": 3, "x": 3},
|
||||
"b": {"c": 1, "d": 2, "x": 1},
|
||||
"c": {"a": 3, "b": 1, "y": 2},
|
||||
"d": {"b": 2, "e": 2},
|
||||
"e": {"d": 2, "y": 2},
|
||||
"y": {"c": 2, "e": 2},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "x", "y", H, 4.0)
|
||||
|
||||
def test_digraph1(self):
|
||||
# The classic directed graph example
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("a", "b", capacity=1000.0)
|
||||
G.add_edge("a", "c", capacity=1000.0)
|
||||
G.add_edge("b", "c", capacity=1.0)
|
||||
G.add_edge("b", "d", capacity=1000.0)
|
||||
G.add_edge("c", "d", capacity=1000.0)
|
||||
|
||||
H = {
|
||||
"a": {"b": 1000.0, "c": 1000.0},
|
||||
"b": {"c": 0, "d": 1000.0},
|
||||
"c": {"d": 1000.0},
|
||||
"d": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "a", "d", H, 2000.0)
|
||||
|
||||
def test_digraph2(self):
|
||||
# An example in which some edges end up with zero flow.
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "b", capacity=2)
|
||||
G.add_edge("s", "c", capacity=1)
|
||||
G.add_edge("c", "d", capacity=1)
|
||||
G.add_edge("d", "a", capacity=1)
|
||||
G.add_edge("b", "a", capacity=2)
|
||||
G.add_edge("a", "t", capacity=2)
|
||||
|
||||
H = {
|
||||
"s": {"b": 2, "c": 0},
|
||||
"c": {"d": 0},
|
||||
"d": {"a": 0},
|
||||
"b": {"a": 2},
|
||||
"a": {"t": 2},
|
||||
"t": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "s", "t", H, 2)
|
||||
|
||||
def test_digraph3(self):
|
||||
# A directed graph example from Cormen et al.
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "v1", capacity=16.0)
|
||||
G.add_edge("s", "v2", capacity=13.0)
|
||||
G.add_edge("v1", "v2", capacity=10.0)
|
||||
G.add_edge("v2", "v1", capacity=4.0)
|
||||
G.add_edge("v1", "v3", capacity=12.0)
|
||||
G.add_edge("v3", "v2", capacity=9.0)
|
||||
G.add_edge("v2", "v4", capacity=14.0)
|
||||
G.add_edge("v4", "v3", capacity=7.0)
|
||||
G.add_edge("v3", "t", capacity=20.0)
|
||||
G.add_edge("v4", "t", capacity=4.0)
|
||||
|
||||
H = {
|
||||
"s": {"v1": 12.0, "v2": 11.0},
|
||||
"v2": {"v1": 0, "v4": 11.0},
|
||||
"v1": {"v2": 0, "v3": 12.0},
|
||||
"v3": {"v2": 0, "t": 19.0},
|
||||
"v4": {"v3": 7.0, "t": 4.0},
|
||||
"t": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "s", "t", H, 23.0)
|
||||
|
||||
def test_digraph4(self):
|
||||
# A more complex directed graph
|
||||
# from www.topcoder.com/tc?module=Statc&d1=tutorials&d2=maxFlow
|
||||
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)
|
||||
|
||||
H = {
|
||||
"x": {"a": 2.0, "b": 1.0},
|
||||
"a": {"c": 2.0},
|
||||
"b": {"c": 0, "d": 1.0},
|
||||
"c": {"y": 2.0},
|
||||
"d": {"e": 1.0},
|
||||
"e": {"y": 1.0},
|
||||
"y": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "x", "y", H, 3.0)
|
||||
|
||||
def test_wikipedia_dinitz_example(self):
|
||||
# Nice example from https://en.wikipedia.org/wiki/Dinic's_algorithm
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", 1, capacity=10)
|
||||
G.add_edge("s", 2, capacity=10)
|
||||
G.add_edge(1, 3, capacity=4)
|
||||
G.add_edge(1, 4, capacity=8)
|
||||
G.add_edge(1, 2, capacity=2)
|
||||
G.add_edge(2, 4, capacity=9)
|
||||
G.add_edge(3, "t", capacity=10)
|
||||
G.add_edge(4, 3, capacity=6)
|
||||
G.add_edge(4, "t", capacity=10)
|
||||
|
||||
solnFlows = {
|
||||
1: {2: 0, 3: 4, 4: 6},
|
||||
2: {4: 9},
|
||||
3: {"t": 9},
|
||||
4: {3: 5, "t": 10},
|
||||
"s": {1: 10, 2: 9},
|
||||
"t": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "s", "t", solnFlows, 19)
|
||||
|
||||
def test_optional_capacity(self):
|
||||
# Test optional capacity parameter.
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("x", "a", spam=3.0)
|
||||
G.add_edge("x", "b", spam=1.0)
|
||||
G.add_edge("a", "c", spam=3.0)
|
||||
G.add_edge("b", "c", spam=5.0)
|
||||
G.add_edge("b", "d", spam=4.0)
|
||||
G.add_edge("d", "e", spam=2.0)
|
||||
G.add_edge("c", "y", spam=2.0)
|
||||
G.add_edge("e", "y", spam=3.0)
|
||||
|
||||
solnFlows = {
|
||||
"x": {"a": 2.0, "b": 1.0},
|
||||
"a": {"c": 2.0},
|
||||
"b": {"c": 0, "d": 1.0},
|
||||
"c": {"y": 2.0},
|
||||
"d": {"e": 1.0},
|
||||
"e": {"y": 1.0},
|
||||
"y": {},
|
||||
}
|
||||
solnValue = 3.0
|
||||
s = "x"
|
||||
t = "y"
|
||||
|
||||
compare_flows_and_cuts(G, s, t, solnFlows, solnValue, capacity="spam")
|
||||
|
||||
def test_digraph_infcap_edges(self):
|
||||
# DiGraph with infinite capacity edges
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "a")
|
||||
G.add_edge("s", "b", capacity=30)
|
||||
G.add_edge("a", "c", capacity=25)
|
||||
G.add_edge("b", "c", capacity=12)
|
||||
G.add_edge("a", "t", capacity=60)
|
||||
G.add_edge("c", "t")
|
||||
|
||||
H = {
|
||||
"s": {"a": 85, "b": 12},
|
||||
"a": {"c": 25, "t": 60},
|
||||
"b": {"c": 12},
|
||||
"c": {"t": 37},
|
||||
"t": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "s", "t", H, 97)
|
||||
|
||||
# DiGraph with infinite capacity digon
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "a", capacity=85)
|
||||
G.add_edge("s", "b", capacity=30)
|
||||
G.add_edge("a", "c")
|
||||
G.add_edge("c", "a")
|
||||
G.add_edge("b", "c", capacity=12)
|
||||
G.add_edge("a", "t", capacity=60)
|
||||
G.add_edge("c", "t", capacity=37)
|
||||
|
||||
H = {
|
||||
"s": {"a": 85, "b": 12},
|
||||
"a": {"c": 25, "t": 60},
|
||||
"c": {"a": 0, "t": 37},
|
||||
"b": {"c": 12},
|
||||
"t": {},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "s", "t", H, 97)
|
||||
|
||||
def test_digraph_infcap_path(self):
|
||||
# Graph with infinite capacity (s, t)-path
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "a")
|
||||
G.add_edge("s", "b", capacity=30)
|
||||
G.add_edge("a", "c")
|
||||
G.add_edge("b", "c", capacity=12)
|
||||
G.add_edge("a", "t", capacity=60)
|
||||
G.add_edge("c", "t")
|
||||
|
||||
for flow_func in all_funcs:
|
||||
pytest.raises(nx.NetworkXUnbounded, flow_func, G, "s", "t")
|
||||
|
||||
def test_graph_infcap_edges(self):
|
||||
# Undirected graph with infinite capacity edges
|
||||
G = nx.Graph()
|
||||
G.add_edge("s", "a")
|
||||
G.add_edge("s", "b", capacity=30)
|
||||
G.add_edge("a", "c", capacity=25)
|
||||
G.add_edge("b", "c", capacity=12)
|
||||
G.add_edge("a", "t", capacity=60)
|
||||
G.add_edge("c", "t")
|
||||
|
||||
H = {
|
||||
"s": {"a": 85, "b": 12},
|
||||
"a": {"c": 25, "s": 85, "t": 60},
|
||||
"b": {"c": 12, "s": 12},
|
||||
"c": {"a": 25, "b": 12, "t": 37},
|
||||
"t": {"a": 60, "c": 37},
|
||||
}
|
||||
|
||||
compare_flows_and_cuts(G, "s", "t", H, 97)
|
||||
|
||||
def test_digraph5(self):
|
||||
# From ticket #429 by mfrasca.
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "a", capacity=2)
|
||||
G.add_edge("s", "b", capacity=2)
|
||||
G.add_edge("a", "b", capacity=5)
|
||||
G.add_edge("a", "t", capacity=1)
|
||||
G.add_edge("b", "a", capacity=1)
|
||||
G.add_edge("b", "t", capacity=3)
|
||||
flowSoln = {
|
||||
"a": {"b": 1, "t": 1},
|
||||
"b": {"a": 0, "t": 3},
|
||||
"s": {"a": 2, "b": 2},
|
||||
"t": {},
|
||||
}
|
||||
compare_flows_and_cuts(G, "s", "t", flowSoln, 4)
|
||||
|
||||
def test_disconnected(self):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
|
||||
G.remove_node(1)
|
||||
assert nx.maximum_flow_value(G, 0, 3) == 0
|
||||
flowSoln = {0: {}, 2: {3: 0}, 3: {2: 0}}
|
||||
compare_flows_and_cuts(G, 0, 3, flowSoln, 0)
|
||||
|
||||
def test_source_target_not_in_graph(self):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
|
||||
G.remove_node(0)
|
||||
for flow_func in all_funcs:
|
||||
pytest.raises(nx.NetworkXError, flow_func, G, 0, 3)
|
||||
G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
|
||||
G.remove_node(3)
|
||||
for flow_func in all_funcs:
|
||||
pytest.raises(nx.NetworkXError, flow_func, G, 0, 3)
|
||||
|
||||
def test_source_target_coincide(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(0)
|
||||
for flow_func in all_funcs:
|
||||
pytest.raises(nx.NetworkXError, flow_func, G, 0, 0)
|
||||
|
||||
def test_multigraphs_raise(self):
|
||||
G = nx.MultiGraph()
|
||||
M = nx.MultiDiGraph()
|
||||
G.add_edges_from([(0, 1), (1, 0)], capacity=True)
|
||||
for flow_func in all_funcs:
|
||||
pytest.raises(nx.NetworkXError, flow_func, G, 0, 0)
|
||||
|
||||
|
||||
class TestMaxFlowMinCutInterface:
|
||||
def setup(self):
|
||||
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)
|
||||
self.G = G
|
||||
H = nx.DiGraph()
|
||||
H.add_edge(0, 1, capacity=1.0)
|
||||
H.add_edge(1, 2, capacity=1.0)
|
||||
self.H = H
|
||||
|
||||
def test_flow_func_not_callable(self):
|
||||
elements = ["this_should_be_callable", 10, {1, 2, 3}]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
|
||||
for flow_func in interface_funcs:
|
||||
for element in elements:
|
||||
pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element)
|
||||
pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element)
|
||||
|
||||
def test_flow_func_parameters(self):
|
||||
G = self.G
|
||||
fv = 3.0
|
||||
for interface_func in interface_funcs:
|
||||
for flow_func in flow_funcs:
|
||||
errmsg = (
|
||||
f"Assertion failed in function: {flow_func.__name__} "
|
||||
f"in interface {interface_func.__name__}"
|
||||
)
|
||||
result = interface_func(G, "x", "y", flow_func=flow_func)
|
||||
if interface_func in max_min_funcs:
|
||||
result = result[0]
|
||||
assert fv == result, errmsg
|
||||
|
||||
def test_minimum_cut_no_cutoff(self):
|
||||
G = self.G
|
||||
for flow_func in flow_funcs:
|
||||
pytest.raises(
|
||||
nx.NetworkXError,
|
||||
nx.minimum_cut,
|
||||
G,
|
||||
"x",
|
||||
"y",
|
||||
flow_func=flow_func,
|
||||
cutoff=1.0,
|
||||
)
|
||||
pytest.raises(
|
||||
nx.NetworkXError,
|
||||
nx.minimum_cut_value,
|
||||
G,
|
||||
"x",
|
||||
"y",
|
||||
flow_func=flow_func,
|
||||
cutoff=1.0,
|
||||
)
|
||||
|
||||
def test_kwargs(self):
|
||||
G = self.H
|
||||
fv = 1.0
|
||||
to_test = (
|
||||
(shortest_augmenting_path, dict(two_phase=True)),
|
||||
(preflow_push, dict(global_relabel_freq=5)),
|
||||
)
|
||||
for interface_func in interface_funcs:
|
||||
for flow_func, kwargs in to_test:
|
||||
errmsg = (
|
||||
f"Assertion failed in function: {flow_func.__name__} "
|
||||
f"in interface {interface_func.__name__}"
|
||||
)
|
||||
result = interface_func(G, 0, 2, flow_func=flow_func, **kwargs)
|
||||
if interface_func in max_min_funcs:
|
||||
result = result[0]
|
||||
assert fv == result, errmsg
|
||||
|
||||
def test_kwargs_default_flow_func(self):
|
||||
G = self.H
|
||||
for interface_func in interface_funcs:
|
||||
pytest.raises(
|
||||
nx.NetworkXError, interface_func, G, 0, 1, global_relabel_freq=2
|
||||
)
|
||||
|
||||
def test_reusing_residual(self):
|
||||
G = self.G
|
||||
fv = 3.0
|
||||
s, t = "x", "y"
|
||||
R = build_residual_network(G, "capacity")
|
||||
for interface_func in interface_funcs:
|
||||
for flow_func in flow_funcs:
|
||||
errmsg = (
|
||||
f"Assertion failed in function: {flow_func.__name__} "
|
||||
f"in interface {interface_func.__name__}"
|
||||
)
|
||||
for i in range(3):
|
||||
result = interface_func(
|
||||
G, "x", "y", flow_func=flow_func, residual=R
|
||||
)
|
||||
if interface_func in max_min_funcs:
|
||||
result = result[0]
|
||||
assert fv == result, errmsg
|
||||
|
||||
|
||||
# Tests specific to one algorithm
|
||||
def test_preflow_push_global_relabel_freq():
|
||||
G = nx.DiGraph()
|
||||
G.add_edge(1, 2, capacity=1)
|
||||
R = preflow_push(G, 1, 2, global_relabel_freq=None)
|
||||
assert R.graph["flow_value"] == 1
|
||||
pytest.raises(nx.NetworkXError, preflow_push, G, 1, 2, global_relabel_freq=-1)
|
||||
|
||||
|
||||
def test_preflow_push_makes_enough_space():
|
||||
# From ticket #1542
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, [0, 1, 3], capacity=1)
|
||||
nx.add_path(G, [1, 2, 3], capacity=1)
|
||||
R = preflow_push(G, 0, 3, value_only=False)
|
||||
assert R.graph["flow_value"] == 1
|
||||
|
||||
|
||||
def test_shortest_augmenting_path_two_phase():
|
||||
k = 5
|
||||
p = 1000
|
||||
G = nx.DiGraph()
|
||||
for i in range(k):
|
||||
G.add_edge("s", (i, 0), capacity=1)
|
||||
nx.add_path(G, ((i, j) for j in range(p)), capacity=1)
|
||||
G.add_edge((i, p - 1), "t", capacity=1)
|
||||
R = shortest_augmenting_path(G, "s", "t", two_phase=True)
|
||||
assert R.graph["flow_value"] == k
|
||||
R = shortest_augmenting_path(G, "s", "t", two_phase=False)
|
||||
assert R.graph["flow_value"] == k
|
||||
|
||||
|
||||
class TestCutoff:
|
||||
def test_cutoff(self):
|
||||
k = 5
|
||||
p = 1000
|
||||
G = nx.DiGraph()
|
||||
for i in range(k):
|
||||
G.add_edge("s", (i, 0), capacity=2)
|
||||
nx.add_path(G, ((i, j) for j in range(p)), capacity=2)
|
||||
G.add_edge((i, p - 1), "t", capacity=2)
|
||||
R = shortest_augmenting_path(G, "s", "t", two_phase=True, cutoff=k)
|
||||
assert k <= R.graph["flow_value"] <= (2 * k)
|
||||
R = shortest_augmenting_path(G, "s", "t", two_phase=False, cutoff=k)
|
||||
assert k <= R.graph["flow_value"] <= (2 * k)
|
||||
R = edmonds_karp(G, "s", "t", cutoff=k)
|
||||
assert k <= R.graph["flow_value"] <= (2 * k)
|
||||
|
||||
def test_complete_graph_cutoff(self):
|
||||
G = nx.complete_graph(5)
|
||||
nx.set_edge_attributes(G, {(u, v): 1 for u, v in G.edges()}, "capacity")
|
||||
for flow_func in [shortest_augmenting_path, edmonds_karp]:
|
||||
for cutoff in [3, 2, 1]:
|
||||
result = nx.maximum_flow_value(
|
||||
G, 0, 4, flow_func=flow_func, cutoff=cutoff
|
||||
)
|
||||
assert cutoff == result, f"cutoff error in {flow_func.__name__}"
|
|
@ -0,0 +1,146 @@
|
|||
"""Maximum flow algorithms test suite on large graphs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.flow import build_flow_dict, build_residual_network
|
||||
from networkx.algorithms.flow import boykov_kolmogorov
|
||||
from networkx.algorithms.flow import dinitz
|
||||
from networkx.algorithms.flow import edmonds_karp
|
||||
from networkx.algorithms.flow import preflow_push
|
||||
from networkx.algorithms.flow import shortest_augmenting_path
|
||||
from networkx.testing import almost_equal
|
||||
|
||||
flow_funcs = [
|
||||
boykov_kolmogorov,
|
||||
dinitz,
|
||||
edmonds_karp,
|
||||
preflow_push,
|
||||
shortest_augmenting_path,
|
||||
]
|
||||
|
||||
|
||||
def gen_pyramid(N):
|
||||
# This graph admits a flow of value 1 for which every arc is at
|
||||
# capacity (except the arcs incident to the sink which have
|
||||
# infinite capacity).
|
||||
G = nx.DiGraph()
|
||||
|
||||
for i in range(N - 1):
|
||||
cap = 1.0 / (i + 2)
|
||||
for j in range(i + 1):
|
||||
G.add_edge((i, j), (i + 1, j), capacity=cap)
|
||||
cap = 1.0 / (i + 1) - cap
|
||||
G.add_edge((i, j), (i + 1, j + 1), capacity=cap)
|
||||
cap = 1.0 / (i + 2) - cap
|
||||
|
||||
for j in range(N):
|
||||
G.add_edge((N - 1, j), "t")
|
||||
|
||||
return G
|
||||
|
||||
|
||||
def read_graph(name):
|
||||
dirname = os.path.dirname(__file__)
|
||||
path = os.path.join(dirname, name + ".gpickle.bz2")
|
||||
return nx.read_gpickle(path)
|
||||
|
||||
|
||||
def validate_flows(G, s, t, soln_value, R, flow_func):
|
||||
flow_value = R.graph["flow_value"]
|
||||
flow_dict = build_flow_dict(G, R)
|
||||
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
||||
assert soln_value == flow_value, errmsg
|
||||
assert set(G) == set(flow_dict), errmsg
|
||||
for u in G:
|
||||
assert set(G[u]) == set(flow_dict[u]), errmsg
|
||||
excess = {u: 0 for u in flow_dict}
|
||||
for u in flow_dict:
|
||||
for v, flow in flow_dict[u].items():
|
||||
assert flow <= G[u][v].get("capacity", float("inf")), errmsg
|
||||
assert flow >= 0, errmsg
|
||||
excess[u] -= flow
|
||||
excess[v] += flow
|
||||
for u, exc in excess.items():
|
||||
if u == s:
|
||||
assert exc == -soln_value, errmsg
|
||||
elif u == t:
|
||||
assert exc == soln_value, errmsg
|
||||
else:
|
||||
assert exc == 0, errmsg
|
||||
|
||||
|
||||
class TestMaxflowLargeGraph:
|
||||
def test_complete_graph(self):
|
||||
N = 50
|
||||
G = nx.complete_graph(N)
|
||||
nx.set_edge_attributes(G, 5, "capacity")
|
||||
R = build_residual_network(G, "capacity")
|
||||
kwargs = dict(residual=R)
|
||||
|
||||
for flow_func in flow_funcs:
|
||||
kwargs["flow_func"] = flow_func
|
||||
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
||||
flow_value = nx.maximum_flow_value(G, 1, 2, **kwargs)
|
||||
assert flow_value == 5 * (N - 1), errmsg
|
||||
|
||||
def test_pyramid(self):
|
||||
N = 10
|
||||
# N = 100 # this gives a graph with 5051 nodes
|
||||
G = gen_pyramid(N)
|
||||
R = build_residual_network(G, "capacity")
|
||||
kwargs = dict(residual=R)
|
||||
|
||||
for flow_func in flow_funcs:
|
||||
kwargs["flow_func"] = flow_func
|
||||
errmsg = f"Assertion failed in function: {flow_func.__name__}"
|
||||
flow_value = nx.maximum_flow_value(G, (0, 0), "t", **kwargs)
|
||||
assert almost_equal(flow_value, 1.0), errmsg
|
||||
|
||||
def test_gl1(self):
|
||||
G = read_graph("gl1")
|
||||
s = 1
|
||||
t = len(G)
|
||||
R = build_residual_network(G, "capacity")
|
||||
kwargs = dict(residual=R)
|
||||
|
||||
# do one flow_func to save time
|
||||
flow_func = flow_funcs[0]
|
||||
validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), flow_func)
|
||||
|
||||
# for flow_func in flow_funcs:
|
||||
# validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs),
|
||||
# flow_func)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_gw1(self):
|
||||
G = read_graph("gw1")
|
||||
s = 1
|
||||
t = len(G)
|
||||
R = build_residual_network(G, "capacity")
|
||||
kwargs = dict(residual=R)
|
||||
|
||||
for flow_func in flow_funcs:
|
||||
validate_flows(G, s, t, 1202018, flow_func(G, s, t, **kwargs), flow_func)
|
||||
|
||||
def test_wlm3(self):
|
||||
G = read_graph("wlm3")
|
||||
s = 1
|
||||
t = len(G)
|
||||
R = build_residual_network(G, "capacity")
|
||||
kwargs = dict(residual=R)
|
||||
|
||||
# do one flow_func to save time
|
||||
flow_func = flow_funcs[0]
|
||||
validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), flow_func)
|
||||
|
||||
# for flow_func in flow_funcs:
|
||||
# validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs),
|
||||
# flow_func)
|
||||
|
||||
def test_preflow_push_global_relabel(self):
|
||||
G = read_graph("gw1")
|
||||
R = preflow_push(G, 1, len(G), global_relabel_freq=50)
|
||||
assert R.graph["flow_value"] == 1202018
|
|
@ -0,0 +1,467 @@
|
|||
import networkx as nx
|
||||
import pytest
|
||||
import os
|
||||
|
||||
|
||||
class TestMinCostFlow:
|
||||
def test_simple_digraph(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_node("a", demand=-5)
|
||||
G.add_node("d", demand=5)
|
||||
G.add_edge("a", "b", weight=3, capacity=4)
|
||||
G.add_edge("a", "c", weight=6, capacity=10)
|
||||
G.add_edge("b", "d", weight=1, capacity=9)
|
||||
G.add_edge("c", "d", weight=2, capacity=5)
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}}
|
||||
assert flowCost == 24
|
||||
assert nx.min_cost_flow_cost(G) == 24
|
||||
assert H == soln
|
||||
assert nx.min_cost_flow(G) == soln
|
||||
assert nx.cost_of_flow(G, H) == 24
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 24
|
||||
assert nx.cost_of_flow(G, H) == 24
|
||||
assert H == soln
|
||||
|
||||
def test_negcycle_infcap(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_node("s", demand=-5)
|
||||
G.add_node("t", demand=5)
|
||||
G.add_edge("s", "a", weight=1, capacity=3)
|
||||
G.add_edge("a", "b", weight=3)
|
||||
G.add_edge("c", "a", weight=-6)
|
||||
G.add_edge("b", "d", weight=1)
|
||||
G.add_edge("d", "c", weight=-2)
|
||||
G.add_edge("d", "t", weight=1, capacity=3)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
||||
|
||||
def test_sum_demands_not_zero(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_node("s", demand=-5)
|
||||
G.add_node("t", demand=4)
|
||||
G.add_edge("s", "a", weight=1, capacity=3)
|
||||
G.add_edge("a", "b", weight=3)
|
||||
G.add_edge("a", "c", weight=-6)
|
||||
G.add_edge("b", "d", weight=1)
|
||||
G.add_edge("c", "d", weight=-2)
|
||||
G.add_edge("d", "t", weight=1, capacity=3)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
||||
|
||||
def test_no_flow_satisfying_demands(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_node("s", demand=-5)
|
||||
G.add_node("t", demand=5)
|
||||
G.add_edge("s", "a", weight=1, capacity=3)
|
||||
G.add_edge("a", "b", weight=3)
|
||||
G.add_edge("a", "c", weight=-6)
|
||||
G.add_edge("b", "d", weight=1)
|
||||
G.add_edge("c", "d", weight=-2)
|
||||
G.add_edge("d", "t", weight=1, capacity=3)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
||||
|
||||
def test_transshipment(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_node("a", demand=1)
|
||||
G.add_node("b", demand=-2)
|
||||
G.add_node("c", demand=-2)
|
||||
G.add_node("d", demand=3)
|
||||
G.add_node("e", demand=-4)
|
||||
G.add_node("f", demand=-4)
|
||||
G.add_node("g", demand=3)
|
||||
G.add_node("h", demand=2)
|
||||
G.add_node("r", demand=3)
|
||||
G.add_edge("a", "c", weight=3)
|
||||
G.add_edge("r", "a", weight=2)
|
||||
G.add_edge("b", "a", weight=9)
|
||||
G.add_edge("r", "c", weight=0)
|
||||
G.add_edge("b", "r", weight=-6)
|
||||
G.add_edge("c", "d", weight=5)
|
||||
G.add_edge("e", "r", weight=4)
|
||||
G.add_edge("e", "f", weight=3)
|
||||
G.add_edge("h", "b", weight=4)
|
||||
G.add_edge("f", "d", weight=7)
|
||||
G.add_edge("f", "h", weight=12)
|
||||
G.add_edge("g", "d", weight=12)
|
||||
G.add_edge("f", "g", weight=-1)
|
||||
G.add_edge("h", "g", weight=-10)
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
soln = {
|
||||
"a": {"c": 0},
|
||||
"b": {"a": 0, "r": 2},
|
||||
"c": {"d": 3},
|
||||
"d": {},
|
||||
"e": {"r": 3, "f": 1},
|
||||
"f": {"d": 0, "g": 3, "h": 2},
|
||||
"g": {"d": 0},
|
||||
"h": {"b": 0, "g": 0},
|
||||
"r": {"a": 1, "c": 1},
|
||||
}
|
||||
assert flowCost == 41
|
||||
assert nx.min_cost_flow_cost(G) == 41
|
||||
assert H == soln
|
||||
assert nx.min_cost_flow(G) == soln
|
||||
assert nx.cost_of_flow(G, H) == 41
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 41
|
||||
assert nx.cost_of_flow(G, H) == 41
|
||||
assert H == soln
|
||||
|
||||
def test_max_flow_min_cost(self):
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "a", bandwidth=6)
|
||||
G.add_edge("s", "c", bandwidth=10, cost=10)
|
||||
G.add_edge("a", "b", cost=6)
|
||||
G.add_edge("b", "d", bandwidth=8, cost=7)
|
||||
G.add_edge("c", "d", cost=10)
|
||||
G.add_edge("d", "t", bandwidth=5, cost=5)
|
||||
soln = {
|
||||
"s": {"a": 5, "c": 0},
|
||||
"a": {"b": 5},
|
||||
"b": {"d": 5},
|
||||
"c": {"d": 0},
|
||||
"d": {"t": 5},
|
||||
"t": {},
|
||||
}
|
||||
flow = nx.max_flow_min_cost(G, "s", "t", capacity="bandwidth", weight="cost")
|
||||
assert flow == soln
|
||||
assert nx.cost_of_flow(G, flow, weight="cost") == 90
|
||||
|
||||
G.add_edge("t", "s", cost=-100)
|
||||
flowCost, flow = nx.capacity_scaling(G, capacity="bandwidth", weight="cost")
|
||||
G.remove_edge("t", "s")
|
||||
assert flowCost == -410
|
||||
assert flow["t"]["s"] == 5
|
||||
del flow["t"]["s"]
|
||||
assert flow == soln
|
||||
assert nx.cost_of_flow(G, flow, weight="cost") == 90
|
||||
|
||||
def test_digraph1(self):
|
||||
# From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied
|
||||
# Mathematical Programming. Addison-Wesley, 1977.
|
||||
G = nx.DiGraph()
|
||||
G.add_node(1, demand=-20)
|
||||
G.add_node(4, demand=5)
|
||||
G.add_node(5, demand=15)
|
||||
G.add_edges_from(
|
||||
[
|
||||
(1, 2, {"capacity": 15, "weight": 4}),
|
||||
(1, 3, {"capacity": 8, "weight": 4}),
|
||||
(2, 3, {"weight": 2}),
|
||||
(2, 4, {"capacity": 4, "weight": 2}),
|
||||
(2, 5, {"capacity": 10, "weight": 6}),
|
||||
(3, 4, {"capacity": 15, "weight": 1}),
|
||||
(3, 5, {"capacity": 5, "weight": 3}),
|
||||
(4, 5, {"weight": 2}),
|
||||
(5, 3, {"capacity": 4, "weight": 1}),
|
||||
]
|
||||
)
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
soln = {
|
||||
1: {2: 12, 3: 8},
|
||||
2: {3: 8, 4: 4, 5: 0},
|
||||
3: {4: 11, 5: 5},
|
||||
4: {5: 10},
|
||||
5: {3: 0},
|
||||
}
|
||||
assert flowCost == 150
|
||||
assert nx.min_cost_flow_cost(G) == 150
|
||||
assert H == soln
|
||||
assert nx.min_cost_flow(G) == soln
|
||||
assert nx.cost_of_flow(G, H) == 150
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 150
|
||||
assert H == soln
|
||||
assert nx.cost_of_flow(G, H) == 150
|
||||
|
||||
def test_digraph2(self):
|
||||
# Example from ticket #430 from mfrasca. Original source:
|
||||
# http://www.cs.princeton.edu/courses/archive/spr03/cs226/lectures/mincost.4up.pdf, slide 11.
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", 1, capacity=12)
|
||||
G.add_edge("s", 2, capacity=6)
|
||||
G.add_edge("s", 3, capacity=14)
|
||||
G.add_edge(1, 2, capacity=11, weight=4)
|
||||
G.add_edge(2, 3, capacity=9, weight=6)
|
||||
G.add_edge(1, 4, capacity=5, weight=5)
|
||||
G.add_edge(1, 5, capacity=2, weight=12)
|
||||
G.add_edge(2, 5, capacity=4, weight=4)
|
||||
G.add_edge(2, 6, capacity=2, weight=6)
|
||||
G.add_edge(3, 6, capacity=31, weight=3)
|
||||
G.add_edge(4, 5, capacity=18, weight=4)
|
||||
G.add_edge(5, 6, capacity=9, weight=5)
|
||||
G.add_edge(4, "t", capacity=3)
|
||||
G.add_edge(5, "t", capacity=7)
|
||||
G.add_edge(6, "t", capacity=22)
|
||||
flow = nx.max_flow_min_cost(G, "s", "t")
|
||||
soln = {
|
||||
1: {2: 6, 4: 5, 5: 1},
|
||||
2: {3: 6, 5: 4, 6: 2},
|
||||
3: {6: 20},
|
||||
4: {5: 2, "t": 3},
|
||||
5: {6: 0, "t": 7},
|
||||
6: {"t": 22},
|
||||
"s": {1: 12, 2: 6, 3: 14},
|
||||
"t": {},
|
||||
}
|
||||
assert flow == soln
|
||||
|
||||
G.add_edge("t", "s", weight=-100)
|
||||
flowCost, flow = nx.capacity_scaling(G)
|
||||
G.remove_edge("t", "s")
|
||||
assert flow["t"]["s"] == 32
|
||||
assert flowCost == -3007
|
||||
del flow["t"]["s"]
|
||||
assert flow == soln
|
||||
assert nx.cost_of_flow(G, flow) == 193
|
||||
|
||||
def test_digraph3(self):
|
||||
"""Combinatorial Optimization: Algorithms and Complexity,
|
||||
Papadimitriou Steiglitz at page 140 has an example, 7.1, but that
|
||||
admits multiple solutions, so I alter it a bit. From ticket #430
|
||||
by mfrasca."""
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("s", "a")
|
||||
G["s"]["a"].update({0: 2, 1: 4})
|
||||
G.add_edge("s", "b")
|
||||
G["s"]["b"].update({0: 2, 1: 1})
|
||||
G.add_edge("a", "b")
|
||||
G["a"]["b"].update({0: 5, 1: 2})
|
||||
G.add_edge("a", "t")
|
||||
G["a"]["t"].update({0: 1, 1: 5})
|
||||
G.add_edge("b", "a")
|
||||
G["b"]["a"].update({0: 1, 1: 3})
|
||||
G.add_edge("b", "t")
|
||||
G["b"]["t"].update({0: 3, 1: 2})
|
||||
|
||||
"PS.ex.7.1: testing main function"
|
||||
sol = nx.max_flow_min_cost(G, "s", "t", capacity=0, weight=1)
|
||||
flow = sum(v for v in sol["s"].values())
|
||||
assert 4 == flow
|
||||
assert 23 == nx.cost_of_flow(G, sol, weight=1)
|
||||
assert sol["s"] == {"a": 2, "b": 2}
|
||||
assert sol["a"] == {"b": 1, "t": 1}
|
||||
assert sol["b"] == {"a": 0, "t": 3}
|
||||
assert sol["t"] == {}
|
||||
|
||||
G.add_edge("t", "s")
|
||||
G["t"]["s"].update({1: -100})
|
||||
flowCost, sol = nx.capacity_scaling(G, capacity=0, weight=1)
|
||||
G.remove_edge("t", "s")
|
||||
flow = sum(v for v in sol["s"].values())
|
||||
assert 4 == flow
|
||||
assert sol["t"]["s"] == 4
|
||||
assert flowCost == -377
|
||||
del sol["t"]["s"]
|
||||
assert sol["s"] == {"a": 2, "b": 2}
|
||||
assert sol["a"] == {"b": 1, "t": 1}
|
||||
assert sol["b"] == {"a": 0, "t": 3}
|
||||
assert sol["t"] == {}
|
||||
assert nx.cost_of_flow(G, sol, weight=1) == 23
|
||||
|
||||
def test_zero_capacity_edges(self):
|
||||
"""Address issue raised in ticket #617 by arv."""
|
||||
G = nx.DiGraph()
|
||||
G.add_edges_from(
|
||||
[
|
||||
(1, 2, {"capacity": 1, "weight": 1}),
|
||||
(1, 5, {"capacity": 1, "weight": 1}),
|
||||
(2, 3, {"capacity": 0, "weight": 1}),
|
||||
(2, 5, {"capacity": 1, "weight": 1}),
|
||||
(5, 3, {"capacity": 2, "weight": 1}),
|
||||
(5, 4, {"capacity": 0, "weight": 1}),
|
||||
(3, 4, {"capacity": 2, "weight": 1}),
|
||||
]
|
||||
)
|
||||
G.nodes[1]["demand"] = -1
|
||||
G.nodes[2]["demand"] = -1
|
||||
G.nodes[4]["demand"] = 2
|
||||
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}}
|
||||
assert flowCost == 6
|
||||
assert nx.min_cost_flow_cost(G) == 6
|
||||
assert H == soln
|
||||
assert nx.min_cost_flow(G) == soln
|
||||
assert nx.cost_of_flow(G, H) == 6
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 6
|
||||
assert H == soln
|
||||
assert nx.cost_of_flow(G, H) == 6
|
||||
|
||||
def test_digon(self):
|
||||
"""Check if digons are handled properly. Taken from ticket
|
||||
#618 by arv."""
|
||||
nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
|
||||
edges = [
|
||||
(1, 2, {"capacity": 3, "weight": 600000}),
|
||||
(2, 1, {"capacity": 2, "weight": 0}),
|
||||
(2, 3, {"capacity": 5, "weight": 714285}),
|
||||
(3, 2, {"capacity": 2, "weight": 0}),
|
||||
]
|
||||
G = nx.DiGraph(edges)
|
||||
G.add_nodes_from(nodes)
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}}
|
||||
assert flowCost == 2857140
|
||||
assert nx.min_cost_flow_cost(G) == 2857140
|
||||
assert H == soln
|
||||
assert nx.min_cost_flow(G) == soln
|
||||
assert nx.cost_of_flow(G, H) == 2857140
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 2857140
|
||||
assert H == soln
|
||||
assert nx.cost_of_flow(G, H) == 2857140
|
||||
|
||||
def test_deadend(self):
|
||||
"""Check if one-node cycles are handled properly. Taken from ticket
|
||||
#2906 from @sshraven."""
|
||||
G = nx.DiGraph()
|
||||
|
||||
G.add_nodes_from(range(5), demand=0)
|
||||
G.nodes[4]["demand"] = -13
|
||||
G.nodes[3]["demand"] = 13
|
||||
|
||||
G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.min_cost_flow, G)
|
||||
|
||||
def test_infinite_capacity_neg_digon(self):
|
||||
"""An infinite capacity negative cost digon results in an unbounded
|
||||
instance."""
|
||||
nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
|
||||
edges = [
|
||||
(1, 2, {"weight": -600}),
|
||||
(2, 1, {"weight": 0}),
|
||||
(2, 3, {"capacity": 5, "weight": 714285}),
|
||||
(3, 2, {"capacity": 2, "weight": 0}),
|
||||
]
|
||||
G = nx.DiGraph(edges)
|
||||
G.add_nodes_from(nodes)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
||||
|
||||
def test_finite_capacity_neg_digon(self):
|
||||
"""The digon should receive the maximum amount of flow it can handle.
|
||||
Taken from ticket #749 by @chuongdo."""
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("a", "b", capacity=1, weight=-1)
|
||||
G.add_edge("b", "a", capacity=1, weight=-1)
|
||||
min_cost = -2
|
||||
assert nx.min_cost_flow_cost(G) == min_cost
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == -2
|
||||
assert H == {"a": {"b": 1}, "b": {"a": 1}}
|
||||
assert nx.cost_of_flow(G, H) == -2
|
||||
|
||||
def test_multidigraph(self):
|
||||
"""Multidigraphs are acceptable."""
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity")
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
assert flowCost == 0
|
||||
assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
|
||||
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 0
|
||||
assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
|
||||
|
||||
def test_negative_selfloops(self):
|
||||
"""Negative selfloops should cause an exception if uncapacitated and
|
||||
always be saturated otherwise.
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
G.add_edge(1, 1, weight=-1)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
||||
G[1][1]["capacity"] = 2
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
assert flowCost == -2
|
||||
assert H == {1: {1: 2}}
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == -2
|
||||
assert H == {1: {1: 2}}
|
||||
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_edge(1, 1, "x", weight=-1)
|
||||
G.add_edge(1, 1, "y", weight=1)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
||||
G[1][1]["x"]["capacity"] = 2
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
assert flowCost == -2
|
||||
assert H == {1: {1: {"x": 2, "y": 0}}}
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == -2
|
||||
assert H == {1: {1: {"x": 2, "y": 0}}}
|
||||
|
||||
def test_bone_shaped(self):
|
||||
# From #1283
|
||||
G = nx.DiGraph()
|
||||
G.add_node(0, demand=-4)
|
||||
G.add_node(1, demand=2)
|
||||
G.add_node(2, demand=2)
|
||||
G.add_node(3, demand=4)
|
||||
G.add_node(4, demand=-2)
|
||||
G.add_node(5, demand=-2)
|
||||
G.add_edge(0, 1, capacity=4)
|
||||
G.add_edge(0, 2, capacity=4)
|
||||
G.add_edge(4, 3, capacity=4)
|
||||
G.add_edge(5, 3, capacity=4)
|
||||
G.add_edge(0, 3, capacity=0)
|
||||
flowCost, H = nx.network_simplex(G)
|
||||
assert flowCost == 0
|
||||
assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
|
||||
flowCost, H = nx.capacity_scaling(G)
|
||||
assert flowCost == 0
|
||||
assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
|
||||
|
||||
def test_exceptions(self):
|
||||
G = nx.Graph()
|
||||
pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G)
|
||||
G = nx.MultiGraph()
|
||||
pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G)
|
||||
G = nx.DiGraph()
|
||||
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXError, nx.capacity_scaling, G)
|
||||
G.add_node(0, demand=float("inf"))
|
||||
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
||||
G.nodes[0]["demand"] = 0
|
||||
G.add_node(1, demand=0)
|
||||
G.add_edge(0, 1, weight=-float("inf"))
|
||||
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
||||
G[0][1]["weight"] = 0
|
||||
G.add_edge(0, 0, weight=float("inf"))
|
||||
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
||||
# pytest.raises(nx.NetworkXError, nx.capacity_scaling, G)
|
||||
G[0][0]["weight"] = 0
|
||||
G[0][1]["capacity"] = -1
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
||||
# pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
||||
G[0][1]["capacity"] = 0
|
||||
G[0][0]["capacity"] = -1
|
||||
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
||||
# pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
||||
|
||||
def test_large(self):
|
||||
fname = os.path.join(os.path.dirname(__file__), "netgen-2.gpickle.bz2")
|
||||
G = nx.read_gpickle(fname)
|
||||
flowCost, flowDict = nx.network_simplex(G)
|
||||
assert 6749969302 == flowCost
|
||||
assert 6749969302 == nx.cost_of_flow(G, flowDict)
|
||||
flowCost, flowDict = nx.capacity_scaling(G)
|
||||
assert 6749969302 == flowCost
|
||||
assert 6749969302 == nx.cost_of_flow(G, flowDict)
|
Binary file not shown.
183
venv/Lib/site-packages/networkx/algorithms/flow/utils.py
Normal file
183
venv/Lib/site-packages/networkx/algorithms/flow/utils.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
"""
|
||||
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
|
Loading…
Add table
Add a link
Reference in a new issue