| """ |
| 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, arbitrary_element, not_implemented_for |
|
|
|
|
| def _detect_unboundedness(R): |
| """Detect infinite-capacity negative cycles.""" |
| G = nx.DiGraph() |
| G.add_nodes_from(R) |
|
|
| |
| inf = R.graph["inf"] |
| |
| f_inf = float("inf") |
| for u in R: |
| for v, e in R[u].items(): |
| |
| 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") |
| |
| 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." |
| ) |
|
|
| |
| 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 |
| ] |
| |
| |
| |
| |
| |
| 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) |
| |
| |
| |
| 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) |
|
|
| |
| 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] = { |
| |
| 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] = { |
| |
| 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 |
|
|
|
|
| @nx._dispatchable( |
| node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0} |
| ) |
| 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 |
| {'a': {'b': 4, 'c': 1}, 'd': {}, 'b': {'d': 4}, 'c': {'d': 1}} |
| |
| 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 |
| {'p': {'q': 2, 'a': 2}, 'q': {'d': 1}, 'a': {'t': 4}, 'd': {'w': 2}, 't': {'q': 1, 'w': 1}, 'w': {}} |
| """ |
| R = _build_residual_network(G, demand, capacity, weight) |
|
|
| inf = float("inf") |
| |
| 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) |
| ) |
|
|
| |
| wmax = max(chain([-inf], (e["capacity"] for u, v, e in R.edges(data=True)))) |
| if wmax == -inf: |
| |
| 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: |
| |
| |
| 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 |
| |
| 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) |
| |
| |
| while S and T: |
| s = arbitrary_element(S) |
| t = None |
| |
| |
| 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: |
| |
| t = u |
| break |
| p_u = R_nodes[u]["potential"] |
| for v, es in R_succ[u].items(): |
| if v in d: |
| continue |
| wmin = inf |
| |
| 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 |
| |
| 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: |
| |
| while u != s: |
| v = u |
| u, k, e = pred[v] |
| e["flow"] += delta |
| R_succ[v][u][(k[0], not k[1])]["flow"] -= delta |
| |
| 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) |
| |
| d_t = d[t] |
| for u, d_u in d.items(): |
| R_nodes[u]["potential"] -= d_u - d_t |
| else: |
| |
| S_remove(s) |
| delta //= 2 |
|
|
| if any(R.nodes[u]["excess"] != 0 for u in R): |
| raise nx.NetworkXUnfeasible("No flow satisfying all demands.") |
|
|
| |
| 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) |
|
|