comparison SmallWorldGraph.py @ 36:305cc03c2750

Chapter 5.5, exercise 6, #3: compute WS clustering coefficient and characteristic length on a BA model graph.
author Brian Neal <bgneal@gmail.com>
date Thu, 10 Jan 2013 19:24:02 -0600
parents f6073c187926
children
comparison
equal deleted inserted replaced
35:10db8c3a6b83 36:305cc03c2750
1 """This module contains the SmallWorldGraph class for 4.4, exercise 4. 1 """This module contains the SmallWorldGraph class for 4.4, exercise 4.
2 2
3 """ 3 """
4 import heapq
5 import itertools
6 import random 4 import random
7 5
8 from Graph import Edge 6 from Graph import Edge
9 from RandomGraph import RandomGraph 7 from RandomGraph import RandomGraph
10 8
11
12 INFINITY = float('Inf')
13 9
14 class SmallWorldGraph(RandomGraph): 10 class SmallWorldGraph(RandomGraph):
15 """A small world graph for 4.4, exercise 4 in Think Complexity.""" 11 """A small world graph for 4.4, exercise 4 in Think Complexity."""
16 12
17 def __init__(self, vs, k, p): 13 def __init__(self, vs, k, p):
27 23
28 """ 24 """
29 super(SmallWorldGraph, self).__init__(vs=vs) 25 super(SmallWorldGraph, self).__init__(vs=vs)
30 self.add_regular_edges(k) 26 self.add_regular_edges(k)
31 self.rewire(p) 27 self.rewire(p)
32 28 self.set_edge_length(1)
33 # give each edge a default length of 1; this is used by the
34 # shortest_path_tree() method
35 for e in self.edges():
36 e.length = 1
37 29
38 def rewire(self, p): 30 def rewire(self, p):
39 """Assuming the graph is initially a regular graph, rewires the graph 31 """Assuming the graph is initially a regular graph, rewires the graph
40 using the Watts and Strogatz algorithm. 32 using the Watts and Strogatz algorithm.
41 33
56 x = random.choice(vertices) 48 x = random.choice(vertices)
57 if v is not x and not self.get_edge(v, x): 49 if v is not x and not self.get_edge(v, x):
58 self.add_edge(Edge(v, x)) 50 self.add_edge(Edge(v, x))
59 break 51 break
60 52
61 def clustering_coefficient(self):
62 """Compute the clustering coefficient for this graph as defined by Watts
63 and Strogatz.
64
65 """
66 cv = {}
67 for v in self:
68 # consider a node and its neighbors
69 nodes = self.out_vertices(v)
70 nodes.append(v)
71
72 # compute the maximum number of possible edges between these nodes
73 # if they were all connected to each other:
74 n = len(nodes)
75 if n == 1:
76 # edge case of only 1 node; handle this case to avoid division
77 # by zero in the general case
78 cv[v] = 1.0
79 continue
80
81 possible = n * (n - 1) / 2.0
82
83 # now compute how many edges actually exist between the nodes
84 actual = 0
85 for x, y in itertools.combinations(nodes, 2):
86 if self.get_edge(x, y):
87 actual += 1
88
89 # the fraction of actual / possible is this nodes C sub v value
90 cv[v] = actual / possible
91
92 # The clustering coefficient is the average of all C sub v values
93 if len(cv):
94 return sum(cv.values()) / float(len(cv))
95 return 0.0
96
97 def shortest_path_tree(self, source, hint=None):
98 """Finds the length of the shortest path from the source vertex to all
99 other vertices in the graph. This length is stored on the vertices as an
100 attribute named 'dist'. The algorithm used is Dijkstra's.
101
102 hint: if provided, must be a dictionary mapping tuples to already known
103 shortest path distances. This can be used to speed up the algorithm.
104
105 """
106 if not hint:
107 hint = {}
108
109 for v in self.vertices():
110 v.dist = hint.get((source, v), INFINITY)
111 source.dist = 0
112
113 queue = [v for v in self.vertices() if v.dist < INFINITY]
114 sort_flag = True
115 while len(queue):
116
117 if sort_flag:
118 queue.sort(key=lambda v: v.dist)
119 sort_flag = False
120
121 v = queue.pop(0)
122
123 # for each neighbor of v, see if we found a new shortest path
124 for w, e in self[v].iteritems():
125 d = v.dist + e.length
126 if d < w.dist:
127 w.dist = d
128 queue.append(w)
129 sort_flag = True
130
131 def shortest_path_tree2(self, source):
132 """Finds the length of the shortest path from the source vertex to all
133 other vertices in the graph. This length is stored on the vertices as an
134 attribute named 'dist'. The algorithm used is Dijkstra's with a Heap
135 used to sort/store pending nodes to be examined.
136
137 """
138 for v in self.vertices():
139 v.dist = INFINITY
140 source.dist = 0
141
142 queue = []
143 heapq.heappush(queue, (0, source))
144 while len(queue):
145
146 _, v = heapq.heappop(queue)
147
148 # for each neighbor of v, see if we found a new shortest path
149 for w, e in self[v].iteritems():
150 d = v.dist + e.length
151 if d < w.dist:
152 w.dist = d
153 heapq.heappush(queue, (d, w))
154
155 def all_pairs_floyd_warshall(self):
156 """Finds the shortest paths between all pairs of vertices using the
157 Floyd-Warshall algorithm.
158
159 http://en.wikipedia.org/wiki/Floyd-Warshall_algorithm
160
161 """
162 vertices = self.vertices()
163 dist = {}
164 for i in vertices:
165 for j in vertices:
166 if i is j:
167 dist[i, j] = 0.0
168 else:
169 e = self.get_edge(i, j)
170 dist[i, j] = e.length if e else INFINITY
171
172 for k in vertices:
173 for i in vertices:
174 for j in vertices:
175 d_ik = dist[i, k]
176 d_kj = dist[k, j]
177 new_cost = d_ik + d_kj
178 if new_cost < dist[i, j]:
179 dist[i, j] = new_cost
180
181 return dist
182
183 def big_l(self):
184 """Computes the "big-L" value for the graph as per Watts & Strogatz.
185
186 L is defined as the number of edges in the shortest path between
187 two vertices, averaged over all vertices.
188
189 Uses the shortest_path_tree() method, called once for every node.
190
191 """
192 d = {}
193 for v in self.vertices():
194 self.shortest_path_tree(v, d)
195 t = [((w, v), w.dist) for w in self.vertices() if v is not w]
196 d.update(t)
197
198 if len(d):
199 return sum(d.values()) / float(len(d))
200 return 0.0
201
202 def big_l2(self):
203 """Computes the "big-L" value for the graph as per Watts & Strogatz.
204
205 L is defined as the number of edges in the shortest path between
206 two vertices, averaged over all vertices.
207
208 Uses the all_pairs_floyd_warshall() method.
209
210 """
211 dist = self.all_pairs_floyd_warshall()
212 vertices = self.vertices()
213 result = [dist[i, j] for i in vertices for j in vertices if i is not j]
214
215 if len(result):
216 return sum(result) / float(len(result))
217 return 0.0
218
219 def big_l3(self):
220 """Computes the "big-L" value for the graph as per Watts & Strogatz.
221
222 L is defined as the number of edges in the shortest path between
223 two vertices, averaged over all vertices.
224
225 Uses the shortest_path_tree2() method, called once for every node.
226
227 """
228 d = {}
229 for v in self.vertices():
230 self.shortest_path_tree2(v)
231 t = [((v, w), w.dist) for w in self.vertices() if v is not w]
232 d.update(t)
233
234 if len(d):
235 return sum(d.values()) / float(len(d))
236 return 0.0
237