Repository: networkx/networkx

Issue:
hopcroft_karp_matching / to_vertex_cover bug
`to_vertex_cover` must give the **minimum vertex cover** as documented. Wasn't it fixed in #2384 ?
The following code gives random results:

```python
import networkx as nx

nodesU = ['r1','r2','r3','r4','r5','r6','r7','r8']
nodesV = ['c1','c2','c3','c4','c5','c6','c7','c8']
edges = [
        ('r1','c2'),('r1','c4'),('r1','c5'),('r1','c8'),
        ('r2','c1'),('r2','c2'),('r2','c3'),('r2','c6'),('r2','c7'),
        ('r3','c4'),('r3','c5'),
        ('r4','c2'),('r4','c8'),
        ('r5','c2'),('r5','c8'),
        ('r6','c1'),('r6','c2'),('r6','c3'),('r6','c5'),('r6','c6'),('r6','c7'),
        ('r7','c2'),('r7','c8'),
        ('r8','c2'),('r8','c4')]

G = nx.Graph()
G.add_nodes_from(nodesU, bipartite=0)
G.add_nodes_from(nodesV, bipartite=1)
G.add_edges_from(edges)
M = nx.bipartite.hopcroft_karp_matching(G, top_nodes = nodesU)
print('matchings M = ', M)
print('|M|  = ', len(M))

K = nx.bipartite.to_vertex_cover(G, M, top_nodes = nodesU)
print('minimum cover K = ', K)
print('|K|  = ', len(K))
```

Here are the results of multiple runs:
```
matchings M =  {'c1': 'r6', 'r7': 'c2', 'r5': 'c8', 'r1': 'c5', 'r8': 'c4', 'r6': 'c1', 'c5': 'r1', 'c8': 'r5', 'r2': 'c7', 'c7': 'r2', 'c2': 'r7', 'c4': 'r8'}
|M|  =  12
minimum cover K =  {'c1', 'c5', 'c7', 'c2', 'c8', 'c4'}
|K|  =  6

matchings M =  {'c6': 'r6', 'r6': 'c6', 'r1': 'c4', 'r4': 'c2', 'c3': 'r2', 'r5': 'c8', 'c5': 'r3', 'r3': 'c5', 'c8': 'r5', 'r2': 'c3', 'c4': 'r1', 'c2': 'r4'}
|M|  =  12
minimum cover K =  {'c6', 'r6', 'c4', 'c5', 'c2', 'c3', 'c8', 'r2'}
|K|  =  8

matchings M =  {'c8': 'r7', 'r7': 'c8', 'c1': 'r2', 'r3': 'c4', 'r6': 'c7', 'c5': 'r1', 'c2': 'r4', 'c7': 'r6', 'r4': 'c2', 'r2': 'c1', 'c4': 'r3', 'r1': 'c5'}
|M|  =  12
minimum cover K =  {'r2', 'c8', 'c4', 'c2', 'c5'}
|K|  =  5

matchings M =  {'r3': 'c4', 'r6': 'c1', 'r1': 'c5', 'c2': 'r7', 'r7': 'c2', 'c8': 'r5', 'r2': 'c7', 'c5': 'r1', 'r5': 'c8', 'c1': 'r6', 'c7': 'r2', 'c4': 'r3'}
|M|  =  12
minimum cover K =  {'c8', 'c5', 'c7', 'r6', 'c2', 'c4'}
|K|  =  6
```

I'm using networkx 2.2 on debian/python3 and windows miniconda3.
The graph is from: [http://tryalgo.org/en/matching/2016/08/05/konig/](http://tryalgo.org/en/matching/2016/08/05/konig/)

Also why `hopcroft_karp_matching` returns matchings and opposite edges ?


Discussion:
I'm not sure what is going on with your output. I am using python 3.6 and networkx 2.3 and the matchings do not switch c to r nodes like your output shows. Also, the output is consistent

    matchings M =  {'r6': 'c1', 'r8': 'c2', 'r1': 'c4', 'r4': 'c8', 'r3': 'c5', 'r2': 'c3', 'c3': 'r2', 'c4': 'r1', 'c8': 'r4', 'c5': 
    'r3', 'c2': 'r8', 'c1': 'r6'}
    |M|  =  12
    minimum cover K =  {'c4', 'c8', 'r1', 'c5', 'r3', 'c2', 'r2', 'r6'}
    |K|  =  8

Using a slightly smaller example I notice the same problem as @agreppin with Python 3.6.7 and networkx 2.3. .
According to Königs theorem (like referred in to_vertex_cover, see [Koenigs Theorem Wikipedia](https://en.wikipedia.org/wiki/K%C3%B6nig%27s_theorem_%28graph_theory%29#Proof) )
`|M|= |K| `should hold. This criterion is violated in some cases by the to_vertex_cover function. Here is a minimum working example:

```python
import networkx as nx
import sys
from networkx.algorithms.bipartite import to_vertex_cover
from networkx.algorithms.bipartite import maximum_matching

print('Python version: ', sys.version)
print('Using version ' + nx.__version__)

nodesU = ['r1','r3','r5','r7','r8']
nodesV = ['c2','c4','c5','c8']
edges = [('r1','c2'),('r1','c4'),('r1','c5'),('r1','c8'),
        ('r3','c4'),('r3','c5'),
        ('r5','c2'),('r5','c8'),
        ('r7','c2'),('r7','c8'),
        ('r8','c2'),('r8','c4')]

G = nx.Graph()
G.add_nodes_from(nodesU, bipartite=0)
G.add_nodes_from(nodesV, bipartite=1)
G.add_edges_from(edges)

print('Edges: ' + repr(list(G.edges())))
assert nx.is_bipartite(G)
assert nx.is_connected(G)

#---------------------------------------------------------------
# compute matching by maximum_matching
# this gives in some cases differing matchings
#---------------------------------------------------------------
matching = maximum_matching(G, top_nodes=nodesU)
print('Matching: ' + repr(matching))
for u, v in matching.items():
    assert matching[v] == u

vertex_cover = to_vertex_cover(G, matching, top_nodes=nodesU)

print('Vertex cover: ' + repr(vertex_cover))
for u, v in G.edges():
    assert u in vertex_cover or v in vertex_cover

#Matching contains each edge twice
assert len(matching) == 2*len(vertex_cover)

#---------------------------------------------------------------
# fixed matching -> to_vertex_cover WORKS
#---------------------------------------------------------------
matching1 = {'r1': 'c2', 'r7': 'c8', 'r8': 'c4', 'r3': 'c5', 'c8': 'r7', 'c5': 'r3', 'c4': 'r8', 'c2': 'r1'}
print('Matching: ' + repr(matching1))
for u, v in matching1.items():
    assert matching1[v] == u

vertex_cover = to_vertex_cover(G, matching1, top_nodes=nodesU)

print('Vertex cover: ' + repr(vertex_cover))
for u, v in G.edges():
    assert u in vertex_cover or v in vertex_cover

#Matching contains each edge twice
assert len(matching1) == 2*len(vertex_cover)

#--------------------------------------------------------------
# fixed matching -> to_vertex_cover DOES NOT WORK
#--------------------------------------------------------------
matching2 = {'r8': 'c2', 'r5': 'c8', 'r1': 'c4', 'r3': 'c5', 'c4': 'r1', 'c8': 'r5', 'c2': 'r8', 'c5': 'r3'}
print('Matching: ' + repr(matching2))
for u, v in matching2.items():
    assert matching2[v] == u

vertex_cover = to_vertex_cover(G, matching2, top_nodes=nodesU)

print('Vertex cover: ' + repr(vertex_cover))
for u, v in G.edges():
    assert u in vertex_cover or v in vertex_cover

#Matching contains each edge twice
assert len(matching2) == 2*len(vertex_cover)
```

This gives the output:
```
Python version:  3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0]
Using version 2.3
Edges: [('r1', 'c2'), ('r1', 'c4'), ('r1', 'c5'), ('r1', 'c8'), ('r3', 'c4'), ('r3', 'c5'), ('r5', 'c2'), ('r5', 'c8'), ('r7', 'c2'), ('r7', 'c8'), ('r8', 'c2'), ('r8', 'c4')]
Matching: {'r7': 'c2', 'r5': 'c8', 'r1': 'c4', 'r3': 'c5', 'c4': 'r1', 'c8': 'r5', 'c5': 'r3', 'c2': 'r7'}
Vertex cover: {'c4', 'c8', 'c5', 'c2'}
Matching: {'r1': 'c2', 'r7': 'c8', 'r8': 'c4', 'r3': 'c5', 'c8': 'r7', 'c5': 'r3', 'c4': 'r8', 'c2': 'r1'}
Vertex cover: {'c4', 'c8', 'c5', 'c2'}
Matching: {'r8': 'c2', 'r5': 'c8', 'r1': 'c4', 'r3': 'c5', 'c4': 'r1', 'c8': 'r5', 'c2': 'r8', 'c5': 'r3'}
Vertex cover: {'c5', 'r3', 'c4', 'c8', 'c2', 'r1'}
   assert len(matching2) == 2*len(vertex_cover)
AssertionError
```

The problem seems to be located in the _alternating_dfs inner function of _is_connected_by_alternating_path, like in #2384. The _alternating_dfs function is implemented with the help of a stack.

This stack is filled with the next vertices which should be visited and the set of valid edges on which the DFS could continue from this vertice. During the construction of the stack `next(edges)` is called. This iteratively gives back the set of valid edges for a vertex in the top nodes respectively in the bottom nodes. The implementation is done via a cyclic iterator. So for each vertex added to the stack the set of valid edges changes.

This is not the desired behaviour since for each vertex (e.g. a bottom vertice) the rotation of the cyclic iterator should remain in the same position until e.g. the next top vertex is reached and the set of valid edges changes. Given the `itertools.cycle` the rotation does not stop, when staying in one vertex and filling up the stack with the next coming vertices.

It would, for example, be possible to use a zero/one variable instead of a cyclic iterator. This variable would be added to the stack and indicates wether a vertex is in top_nodes or in bottom_nodes. The variable could be used as an index for the array `edges = [matched_edges, unmatched_edges]` and therefore gives back the valid set of edges for bottom respectively top nodes.

Resolving patch:
diff --git a/networkx/algorithms/bipartite/matching.py b/networkx/algorithms/bipartite/matching.py
--- a/networkx/algorithms/bipartite/matching.py
+++ b/networkx/algorithms/bipartite/matching.py
@@ -346,14 +346,14 @@ def _alternating_dfs(u, along_matched=True):
         will continue only through edges *not* in the given matching.
 
         """
-        if along_matched:
-            edges = itertools.cycle([matched_edges, unmatched_edges])
-        else:
-            edges = itertools.cycle([unmatched_edges, matched_edges])
         visited = set()
-        stack = [(u, iter(G[u]), next(edges))]
+        # Follow matched edges when depth is even,
+        # and follow unmatched edges when depth is odd.
+        initial_depth = 0 if along_matched else 1
+        stack = [(u, iter(G[u]), initial_depth)]
         while stack:
-            parent, children, valid_edges = stack[-1]
+            parent, children, depth = stack[-1]
+            valid_edges = matched_edges if depth % 2 else unmatched_edges
             try:
                 child = next(children)
                 if child not in visited:
@@ -361,7 +361,7 @@ def _alternating_dfs(u, along_matched=True):
                         if child in targets:
                             return True
                         visited.add(child)
-                        stack.append((child, iter(G[child]), next(edges)))
+                        stack.append((child, iter(G[child]), depth + 1))
             except StopIteration:
                 stack.pop()
         return False


Test patch:
diff --git a/networkx/algorithms/bipartite/tests/test_matching.py b/networkx/algorithms/bipartite/tests/test_matching.py
--- a/networkx/algorithms/bipartite/tests/test_matching.py
+++ b/networkx/algorithms/bipartite/tests/test_matching.py
@@ -180,6 +180,16 @@ def test_vertex_cover_issue_2384(self):
         for u, v in G.edges():
             assert u in vertex_cover or v in vertex_cover
 
+    def test_vertex_cover_issue_3306(self):
+        G = nx.Graph()
+        edges = [(0, 2), (1, 0), (1, 1), (1, 2), (2, 2)]
+        G.add_edges_from([((i, "L"), (j, "R")) for i, j in edges])
+
+        matching = maximum_matching(G)
+        vertex_cover = to_vertex_cover(G, matching)
+        for u, v in G.edges():
+            assert u in vertex_cover or v in vertex_cover
+
     def test_unorderable_nodes(self):
         a = object()
         b = object()