Repository: networkx/networkx

Issue:
networkx.algorithms.maximal_matching does not always return the correct result.
networkx.algorithms.maximal_matching seems to sometimes generate bogus results.

For example the test case in https://gist.github.com/simonwagner/7679793#file-bug_matching-py will return `set([(303, 312), (308, 312)])` as matching - although that is clearly false.

Interestingly enough, it will return the correct result for exactly the same graph, if one recreates it from its edges. I would therefore conclude, that the bug stems from an incorrect handling of the vector ordering in an edge [(a,b) vs (b,a)] in an undirected graph.



Discussion:

Resolving patch:
diff --git a/networkx/algorithms/clique.py b/networkx/algorithms/clique.py
--- a/networkx/algorithms/clique.py
+++ b/networkx/algorithms/clique.py
@@ -26,6 +26,115 @@
            'number_of_cliques', 'cliques_containing_node',
            'project_down', 'project_up']
 
+@not_implemented_for('directed')
+def get_all_cliques(G):
+    """Returns all cliques in an undirected graph.
+
+    This method returns cliques of size (cardinality) 
+    k = 1, 2, 3, ..., maxDegree - 1.
+
+    Where maxDegree is the maximal degree of any node in the graph.
+
+    Keyword arguments
+    -----------------
+    G: undirected graph
+
+    Returns
+    -------
+    generator of lists: generator of list for each clique.
+
+    Notes
+    -----
+    To obtain a list of all cliques, use list(get_all_cliques(G)).
+
+    Based on the algorithm published by Zhang et al. (2005) [1]_ 
+    and adapted to output all cliques discovered.
+    
+    This algorithm is not suitable for directed graphs.
+
+    This algorithm ignores self-loops and parallel edges as
+    clique is not conventionally defined with such edges.
+
+    There are often many cliques in graphs. 
+    This algorithm however, hopefully, does not run out of memory
+    since it only keeps candidate sublists in memory and 
+    continuously removes exhausted sublists.
+
+    References
+    ----------
+    .. [1] Yun Zhang, Abu-Khzam, F.N., Baldwin, N.E., Chesler, E.J., 
+           Langston, M.A., Samatova, N.F., 
+           Genome-Scale Computational Approaches to Memory-Intensive 
+           Applications in Systems Biology 
+           Supercomputing, 2005. Proceedings of the ACM/IEEE SC 2005 
+           Conference , vol., no., pp. 12, 12-18 Nov. 2005
+           doi: 10.1109/SC.2005.29
+           http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1559964&isnumber=33129
+    """
+
+    def greater_neighbors(G, a_node):
+        """Helper method used in get_all_cliques"""
+        nodes_sorted = sorted(G.nodes())
+        a_node_index = nodes_sorted.index(a_node)
+        
+        neighbors_of_a_node = []
+
+        for another_node_index, another_node in enumerate(nodes_sorted):
+            if another_node_index > a_node_index and another_node in G.neighbors(a_node):
+                neighbors_of_a_node.append(another_node)
+    
+        return tuple(neighbors_of_a_node)
+
+    # sorted list of nodes in graph
+    nodes_sorted = sorted(G.nodes())
+
+    # starting point: build all 2-clique sublists
+    clique_sublists = []
+    for a_node_index, a_node in enumerate(nodes_sorted):
+        clique_sublist = {}
+        # sublist base, sb
+        clique_sublist['sb'] = [a_node]
+        # common neighbors, cn
+        clique_sublist['cn'] = greater_neighbors(G, a_node)
+        clique_sublists.append(clique_sublist)
+
+    # output cliques of size k = 1
+    for node in nodes_sorted:
+        yield [node]
+
+    # output cliques of size k >= 2
+    while clique_sublists:
+        a_sublist = clique_sublists.pop(0)
+        for node_added in a_sublist['cn']:
+            neighbors_of_node_added = greater_neighbors(G, node_added)
+
+            current_sublist_base = [] + a_sublist['sb'] + [node_added]
+            current_sublist_cn = tuple(sorted(set(neighbors_of_node_added).intersection(a_sublist['cn'])))
+
+            #print 'clique: '+str(current_sublist_base)
+            yield [node for node in current_sublist_base]
+
+            for node in current_sublist_cn:
+                new_sublist_base = [] + current_sublist_base 
+                new_sublist_base.append(node)
+                #print 'current_sublist_based =',str(current_sublist_base)
+                #print 'new_sublist_base =',str(new_sublist_base)
+                new_sublist_cn = tuple(sorted(set(current_sublist_cn).intersection(greater_neighbors(G, node))))
+    
+                if len(new_sublist_cn) == 0:
+                    #print 'clique: '+str(new_sublist_base)
+                    yield [n for n in new_sublist_base]
+                elif len(new_sublist_cn) == 1:
+                    #print 'clique: '+str(new_sublist_base)
+                    #print 'new_sublist_base + list(new_sublist_cn):',new_sublist_base+list(new_sublist_cn)
+                    yield [n for n in new_sublist_base]
+                    #print 'clique: '+str(new_sublist_base+new_sublist_cn)
+                    
+                    yield [n for n in new_sublist_base + list(new_sublist_cn)]
+                else:
+                    #print 'candidate sublist: '+str([new_sublist_base, new_sublist_cn])
+                    clique_sublists.append({'sb': new_sublist_base, 'cn': new_sublist_cn})
+
 
 @not_implemented_for('directed')
 def find_cliques(G):


Test patch:
diff --git a/networkx/tests/test_cliques.py b/networkx/tests/test_cliques.py
new file mode 100644
--- /dev/null
+++ b/networkx/tests/test_cliques.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+"""Cliques
+=======
+"""
+
+from nose.tools import *
+from networkx import *
+from networkx.algorithms.clique import get_all_cliques
+
+class TestCliques():
+    def test_paper_figure_4(self):
+        # Same graph as given in Fig. 4 of paper get_all_cliques is
+        # based on.
+        # http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1559964&isnumber=33129
+        G = Graph()
+        edges_fig_4 = [('a','b'),('a','c'),('a','d'),('a','e'),
+                       ('b','c'),('b','d'),('b','e'),
+                       ('c','d'),('c','e'),
+                       ('d','e'),
+                       ('f','b'),('f','c'),('f','g'),
+                       ('g','f'),('g','c'),('g','d'),('g','e')]
+        G.add_edges_from(edges_fig_4)
+
+        cliques = list(get_all_cliques(G))
+        expected_cliques = [['a'],
+                            ['b'],
+                            ['c'],
+                            ['d'],
+                            ['e'],
+                            ['f'],
+                            ['g'],
+                            ['a', 'b'],
+                            ['a', 'b', 'd'],
+                            ['a', 'b', 'd', 'e'],
+                            ['a', 'b', 'e'],
+                            ['a', 'c'],
+                            ['a', 'c', 'd'],
+                            ['a', 'c', 'd', 'e'],
+                            ['a', 'c', 'e'],
+                            ['a', 'd'],
+                            ['a', 'd', 'e'],
+                            ['a', 'e'],
+                            ['b', 'c'],
+                            ['b', 'c', 'd'],
+                            ['b', 'c', 'd', 'e'],
+                            ['b', 'c', 'e'],
+                            ['b', 'c', 'f'],
+                            ['b', 'd'],
+                            ['b', 'd', 'e'],
+                            ['b', 'e'],
+                            ['b', 'f'],
+                            ['c', 'd'],
+                            ['c', 'd', 'e'],
+                            ['c', 'd', 'e', 'g'],
+                            ['c', 'd', 'g'],
+                            ['c', 'e'],
+                            ['c', 'e', 'g'],
+                            ['c', 'f'],
+                            ['c', 'f', 'g'],
+                            ['c', 'g'],
+                            ['d', 'e'],
+                            ['d', 'e', 'g'],
+                            ['d', 'g'],
+                            ['e', 'g'],
+                            ['f', 'g'],
+                            ['a', 'b', 'c', 'd'],
+                            ['a', 'b', 'c', 'd', 'e'],
+                            ['a', 'b', 'c', 'e']]
+        
+        assert_equal(cliques, expected_cliques)