Repository: networkx/networkx

Issue:
Min cost flow non-termination with negative cost cycle of finite capacity
The current min cost flow implementation does not terminate on a simple problem involving a negative cycle with finite capacity:

```
import networkx
G = networkx.DiGraph()
G.add_edge('a', 'b', capacity=1, weight=-1)
G.add_edge('b', 'a', capacity=1, weight=-1)
print networkx.min_cost_flow_cost(G)
```

Am I misinterpreting the guarantees stated in the code documentation?  (The documentation seems only to disallow negative cycles of infinite capacity, but seems to imply that negative cost cycles of finite capacity should be ok?)



Discussion:

Resolving patch:
diff --git a/networkx/algorithms/flow/maxflow.py b/networkx/algorithms/flow/maxflow.py
--- a/networkx/algorithms/flow/maxflow.py
+++ b/networkx/algorithms/flow/maxflow.py
@@ -216,7 +216,7 @@ def _create_flow_dict(G, H, capacity='capacity'):
                     flow[u][v] = abs(inf_capacity_flows[(u, v)] -
                                      inf_capacity_flows[(v, u)])
             else:
-                flow[u][v] = abs(G[u][v][capacity] - H[v][u][capacity])
+                flow[u][v] = G[u][v][capacity]
             flow[v][u] = flow[u][v]
 
     return flow
diff --git a/networkx/algorithms/flow/mincost.py b/networkx/algorithms/flow/mincost.py
--- a/networkx/algorithms/flow/mincost.py
+++ b/networkx/algorithms/flow/mincost.py
@@ -137,7 +137,8 @@ def _find_entering_edge(H, c, capacity = 'capacity'):
     return newEdge
 
 
-def _find_leaving_edge(H, T, cycle, newEdge, capacity = 'capacity'):
+def _find_leaving_edge(H, T, cycle, newEdge, capacity = 'capacity',
+                       reverse=False):
     """Find an edge that will leave the basis and the value by which we
     can increase or decrease the flow on that edge.
 
@@ -151,14 +152,26 @@ def _find_leaving_edge(H, T, cycle, newEdge, capacity = 'capacity'):
     eps = False
     leavingEdge = ()
 
-    # If cycle is a digon, newEdge is always a reverse edge (otherwise,
-    # there would be no leaving edge).
+    # If cycle is a digon.
     if len(cycle) == 3:
         u, v = newEdge
-        if H[u][v].get('flow', 0) > H[v][u].get('flow', 0):
-            return (v, u), H[v][u].get('flow', 0)
+        if capacity not in H[u][v] and capacity not in H[v][u]:
+            raise nx.NetworkXUnbounded(
+                    "Negative cost cycle of infinite capacity found. "
+                    + "Min cost flow unbounded below.")
+            
+        if reverse:
+            if H[u][v].get('flow', 0) > H[v][u].get('flow', 0):
+                return (v, u), H[v][u].get('flow', 0)
+            else:
+                return (u, v), H[u][v].get('flow', 0)
         else:
-            return (u, v), H[u][v].get('flow', 0)
+            uv_residual = H[u][v].get(capacity, 0) - H[u][v].get('flow', 0)
+            vu_residual = H[v][u].get(capacity, 0) - H[v][u].get('flow', 0)
+            if (uv_residual > vu_residual):
+                return (v, u), vu_residual
+            else:
+                return (u, v), uv_residual
 
     # Find the forward edge with the minimum value for capacity - 'flow'
     # and the reverse edge with the minimum value for 'flow'.
@@ -409,24 +422,29 @@ def network_simplex(G, demand = 'demand', capacity = 'capacity',
         path2 = path2[path2.index(join):]
         cycle = []
         if H[newEdge[0]][newEdge[1]].get('flow', 0) == 0:
+            reverse = False
             path2.reverse()
             cycle = path1 + path2
         else: # newEdge is at capacity
+            reverse = True
             path1.reverse()
             cycle = path2 + path1
 
         # Find the leaving edge. Will stop here if cycle is an infinite
         # capacity negative cost cycle.
         leavingEdge, eps = _find_leaving_edge(H, T, cycle, newEdge,
-                                              capacity = capacity)
+                                              capacity=capacity,
+                                              reverse=reverse)
 
         # Actual augmentation happens here. If eps = 0, don't bother.
         if eps:
             flowCost -= cycleCost * eps
             if len(cycle) == 3:
+                if reverse:
+                    eps = -eps
                 u, v = newEdge
-                H[u][v]['flow'] -= eps
-                H[v][u]['flow'] -= eps
+                H[u][v]['flow'] = H[u][v].get('flow', 0) + eps
+                H[v][u]['flow'] = H[v][u].get('flow', 0) + eps
             else:
                 for index, u in enumerate(cycle[:-1]):
                     v = cycle[index + 1]


Test patch:
diff --git a/networkx/algorithms/flow/tests/test_mincost.py b/networkx/algorithms/flow/tests/test_mincost.py
--- a/networkx/algorithms/flow/tests/test_mincost.py
+++ b/networkx/algorithms/flow/tests/test_mincost.py
@@ -251,6 +251,31 @@ def test_digon(self):
         assert_equal(nx.min_cost_flow(G), soln)
         assert_equal(nx.cost_of_flow(G, H), 2857140)
 
+    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)
+        assert_raises(nx.NetworkXUnbounded, nx.network_simplex, 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_equal(nx.min_cost_flow_cost(G), min_cost)
+
     def test_multidigraph(self):
         """Raise an exception for multidigraph."""
         G = nx.MultiDiGraph()