changeset 32:a13c00c0dfe5

Chapter 5, exercise 6, #1: Barab?si and Albert.
author Brian Neal <bgneal@gmail.com>
date Wed, 09 Jan 2013 20:09:19 -0600
parents a2358c64d9af
children cfb7f28678c7
files ch5ex6.py
diffstat 1 files changed, 142 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ch5ex6.py	Wed Jan 09 20:09:19 2013 -0600
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+"""Chapter 5.5, exercise 6 in Allen Downey's Think Complexity book.
+
+1. Read Barabási and Albert’s paper and implement their algorithm for generating
+   graphs. See if you can replicate their Figure 2(A), which shows P(k) versus
+   k for a graph with 150 000 vertices.
+
+"""
+import collections
+import random
+import sys
+
+from matplotlib import pyplot
+
+from Graph import Graph, Vertex, Edge
+
+
+class BAGraph(Graph):
+    """BAGraph implements the algorithm described in the Barabási and
+    Albert paper "Emergence of Scaling in Random Networks" from Science
+    Magazine Vol. 286, 15 October 1999.
+
+    """
+    def __init__(self, m0, m):
+        """Create a graph with m0 vertices and m connecting edges.
+
+        """
+        self.m0 = m0
+        self.m = m
+        self.histogram = []
+
+        initial_vertices = [Vertex() for i in xrange(m0)]
+
+        super(BAGraph, self).__init__(vs=initial_vertices)
+
+        # Add initial edges between nodes randomly
+        n = 0
+        while n != m:
+            # pick two vertices at random
+            v = random.choice(initial_vertices)
+            w = random.choice(initial_vertices)
+
+            # if they aren't the same and don't already have an edge, add one
+            if v is not w and not self.get_edge(v, w):
+                self.add_edge(Edge(v, w))
+                n += 1
+
+    def add_edge(self, edge):
+        """Our version of add_edge() adds the two vertices on either end of the
+        edge to a histogram. This allows us to more easily pick popular vertices
+        when adding edges as part of the step() method.
+
+        """
+        super(BAGraph, self).add_edge(edge)     # invoke base class version
+
+        v, w = edge
+        self.histogram.append(v)
+        self.histogram.append(w)
+
+    def step(self):
+        """This method adds a new vertex to the graph, then adds m edges that
+        link the new vertex to m different vertices already present in the
+        graph. Preferential treatment is given towards vertices that already
+        have high connectivity.
+
+        The paper describes this preferential choosing as creating a probability
+        P that a new vertex will be connected to vertex i depends on the
+        connectivity ki of that vertex, so that P(ki) = ki / sum(kj).
+
+        We implement that by keeping a list of vertices (histogram), where we
+        insert a vertex into this list whenever it gets an edge added to it. We
+        then randomly choose a vertex from this list. Thus the vertices with
+        more edges will be more likely chosen.
+
+        """
+        # pick m unique vertices to attach to the new vertex
+        vs = set()
+        while len(vs) < self.m:
+            w = random.choice(self.histogram)
+            if w not in vs:
+                vs.add(w)
+
+        # Add the new vertex to the graph and create edges
+        v = Vertex()
+        self.add_vertex(v)
+        for w in vs:
+            self.add_edge(Edge(v, w))
+
+    def get_p(self):
+        """This method returns a dictionary of probabilities where each key is
+        the connectivity k and the value is the probability [0-1] for this
+        graph.
+
+        """
+        # First, for each vertex, count up how many neighbors it has
+        vs = self.vertices()
+
+        c = collections.Counter()
+        for v in vs:
+            n = len(self.out_vertices(v))
+            c[n] += 1
+
+        n = len(vs)
+        if n > 0:
+            for k in c:
+                c[k] = float(c[k]) / n
+
+        return c
+
+
+def main(script, m0, m, n):
+
+    # create a BAGraph with parameters m0 & m:
+    m0, m, n = int(m0), int(m), int(n)
+    g = BAGraph(m0, m)
+
+    # step the graph n times:
+    for i in xrange(n):
+        g.step()
+
+    # retrieve probabilities
+    p = g.get_p()
+
+    # plot P(k) versus k on a log-log scale
+
+    vals = p.items()
+    vals.sort(key=lambda t: t[0])
+    x, y = zip(*vals)
+
+    pyplot.clf()
+    pyplot.xscale('log')
+    pyplot.yscale('log')
+    pyplot.title('P(k) versus k')
+    pyplot.xlabel('k')
+    pyplot.ylabel('P(k)')
+    pyplot.plot(x, y, label='P(k) vs. k', color='green', linewidth=3)
+    pyplot.legend(loc='upper right')
+    pyplot.show()
+
+
+if __name__ == '__main__':
+    main(*sys.argv)