Repository: networkx/networkx

Issue:
Adds a large clique size heuristic function
This commit creates a new package, `networkx.algorithms.heuristic`, and
a new module within that package containing a function for finding the
size of a large clique in a graph.

This is the algorithm suggested in issue #773. I'm not certain if it is accurate, so please double check it.

There's no unit test because I'm not sure how to test it.



Discussion:

Resolving patch:
diff --git a/networkx/algorithms/approximation/__init__.py b/networkx/algorithms/approximation/__init__.py
--- a/networkx/algorithms/approximation/__init__.py
+++ b/networkx/algorithms/approximation/__init__.py
@@ -1,8 +1,22 @@
-'''
-    .. warning:: The approximation submodule is not imported automatically with networkx.
-    
-    Approximate algorithms can be imported with ``from networkx.algorithms import approximation``.
-'''
+# __init__.py - package containing heuristics for optimization problems
+#
+# Copyright 2016-2018 NetworkX developers.
+#
+# This file is part of NetworkX.
+#
+# NetworkX is distributed under a BSD license; see LICENSE.txt for more
+# information.
+"""Approximations of graph properties and Heuristic functions for optimization
+problems.
+
+    .. warning:: The approximation submodule is not imported in the top-level
+        ``networkx``.
+
+    These functions can be imported with
+    ``from networkx.algorithms import approximation``.
+
+"""
+
 from networkx.algorithms.approximation.clustering_coefficient import *
 from networkx.algorithms.approximation.clique import *
 from networkx.algorithms.approximation.connectivity import *
diff --git a/networkx/algorithms/approximation/clique.py b/networkx/algorithms/approximation/clique.py
--- a/networkx/algorithms/approximation/clique.py
+++ b/networkx/algorithms/approximation/clique.py
@@ -1,15 +1,22 @@
 # -*- coding: utf-8 -*-
-"""
-Cliques.
-"""
-#   Copyright (C) 2011-2012 by
+#   Copyright (C) 2011-2018 by
 #   Nicholas Mancuso <nick.mancuso@gmail.com>
 #   All rights reserved.
 #   BSD license.
+#   Copyright 2016-2018 NetworkX developers.
+#   NetworkX is distributed under a BSD license
+#
+# Authors:    Nicholas Mancuso (nick.mancuso@gmail.com)
+#             Jeffery Finkelstein <jeffrey.finkelstein@gmail.com>
+#             Dan Schult <dschult@colgate.edu>
+"""Functions for computing large cliques."""
+from operator import itemgetter
+
 import networkx as nx
+from networkx.utils import not_implemented_for
 from networkx.algorithms.approximation import ramsey
-__author__ = """Nicholas Mancuso (nick.mancuso@gmail.com)"""
-__all__ = ["clique_removal", "max_clique"]
+
+__all__ = ["clique_removal", "max_clique", "large_clique_size"]
 
 
 def max_clique(G):
@@ -31,7 +38,7 @@ def max_clique(G):
     Notes
     ------
     A clique in an undirected graph G = (V, E) is a subset of the vertex set
-    `C \subseteq V`, such that for every two vertices in C, there exists an edge
+    `C \subseteq V` such that for every two vertices in C there exists an edge
     connecting the two. This is equivalent to saying that the subgraph
     induced by C is complete (in some cases, the term clique may also refer
     to the subgraph).
@@ -75,7 +82,7 @@ def clique_removal(G):
     Returns
     -------
     max_ind_cliques : (set, list) tuple
-        Maximal independent set and list of maximal cliques (sets) in the graph.
+        2-tuple of Maximal Independent Set and list of maximal cliques (sets).
 
     References
     ----------
@@ -97,3 +104,68 @@ def clique_removal(G):
     # Determine the largest independent set as measured by cardinality.
     maxiset = max(isets, key=len)
     return maxiset, cliques
+
+
+@not_implemented_for('directed')
+@not_implemented_for('multigraph')
+def large_clique_size(G):
+    """Find the size of a large clique in a graph.
+
+    A *clique* is a subset of nodes in which each pair of nodes is
+    adjacent. This function is a heuristic for finding the size of a
+    large clique in the graph.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    Returns
+    -------
+    int
+       The size of a large clique in the graph.
+
+    Notes
+    -----
+    This implementation is from [1]_. Its worst case time complexity is
+    :math:`O(n d^2)`, where *n* is the number of nodes in the graph and
+    *d* is the maximum degree.
+
+    This function is a heuristic, which means it may work well in
+    practice, but there is no rigorous mathematical guarantee on the
+    ratio between the returned number and the actual largest clique size
+    in the graph.
+
+    References
+    ----------
+    .. [1] Pattabiraman, Bharath, et al.
+       "Fast Algorithms for the Maximum Clique Problem on Massive Graphs
+       with Applications to Overlapping Community Detection."
+       *Internet Mathematics* 11.4-5 (2015): 421--448.
+       <https://dx.doi.org/10.1080/15427951.2014.986778>
+
+    See also
+    --------
+
+    :func:`networkx.algorithms.approximation.clique.max_clique`
+        A function that returns an approximate maximum clique with a
+        guarantee on the approximation ratio.
+
+    :mod:`networkx.algorithms.clique`
+        Functions for finding the exact maximum clique in a graph.
+
+    """
+    degrees = G.degree
+    def _clique_heuristic(G, U, size, best_size):
+        if not U:
+            return max(best_size, size)
+        u = max(U, key=degrees)
+        U.remove(u)
+        N_prime = {v for v in G[u] if degrees[v] >= best_size}
+        return _clique_heuristic(G, U & N_prime, size + 1, best_size)
+
+    best_size = 0
+    nodes = (u for u in G if degrees[u] >= best_size)
+    for u in nodes:
+        neighbors = {v for v in G[u] if degrees[v] >= best_size}
+        best_size = _clique_heuristic(G, neighbors, 1, best_size)
+    return best_size


Test patch:
diff --git a/networkx/algorithms/approximation/tests/test_clique.py b/networkx/algorithms/approximation/tests/test_clique.py
--- a/networkx/algorithms/approximation/tests/test_clique.py
+++ b/networkx/algorithms/approximation/tests/test_clique.py
@@ -14,11 +14,12 @@
 
 from nose.tools import assert_greater
 from nose.tools import assert_true
-from nose.tools import eq_
+from nose.tools import assert_equal
 
 import networkx as nx
 from networkx.algorithms.approximation import max_clique
 from networkx.algorithms.approximation import clique_removal
+from networkx.algorithms.approximation import large_clique_size
 
 
 def is_independent_set(G, nodes):
@@ -79,13 +80,13 @@ class TestMaxClique(object):
 
     def test_null_graph(self):
         G = nx.null_graph()
-        eq_(len(max_clique(G)), 0)
+        assert_equal(len(max_clique(G)), 0)
 
     def test_complete_graph(self):
         graph = nx.complete_graph(30)
         # this should return the entire graph
         mc = max_clique(graph)
-        eq_(30, len(mc))
+        assert_equal(30, len(mc))
 
     def test_maximal_by_cardinality(self):
         """Tests that the maximal clique is computed according to maximum
@@ -102,3 +103,17 @@ def test_maximal_by_cardinality(self):
         G = nx.lollipop_graph(30, 2)
         clique = max_clique(G)
         assert_greater(len(clique), 2)
+
+
+def test_large_clique_size():
+    G = nx.complete_graph(9)
+    nx.add_cycle(G, [9, 10, 11])
+    G.add_edge(8, 9)
+    G.add_edge(1, 12)
+    G.add_node(13)
+
+    assert_equal(large_clique_size(G), 9)
+    G.remove_node(5)
+    assert_equal(large_clique_size(G), 8)
+    G.remove_edge(2, 3)
+    assert_equal(large_clique_size(G), 7)