Skip to content

Commit

Permalink
Merge branch 'get-all-cliques' of git://github.com/waltherg/networkx …
Browse files Browse the repository at this point in the history
…into get-all-cliques
  • Loading branch information
ysitu committed Jun 8, 2014
2 parents 01e5405 + ac04340 commit 8280b7f
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 0 deletions.
109 changes: 109 additions & 0 deletions networkx/algorithms/clique.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
71 changes: 71 additions & 0 deletions networkx/tests/test_cliques.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 8280b7f

Please sign in to comment.