Mercurial > public > think_complexity
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 |