├── README.md └── robustness.py /README.md: -------------------------------------------------------------------------------- 1 | ## Attack Robustness and Centrality of Complex Networks 2 | 3 | This file describes our Python based software for evaluating the robustness 4 | of complex networks under different kinds of targeted attacks as described 5 | in [our paper](http://dx.doi.org/10.1371/journal.pone.0059613). 6 | 7 | `robustness.py`: This script performs robustness analysis on the given 8 | network, which involves removing nodes from the network at random, or in 9 | reverse order of centrality measures (degree, betweenness, closeness, and 10 | eigenvector), and comparing the size of the largest component in the 11 | network to the fraction of nodes removed. The script is run as 12 | 13 | ```bash 14 | > python robustness.py 15 | ``` 16 | 17 | where `infile` is the name of the network file in gml format, `outfile` is the 18 | name of the output (pdf) file in which the results of the analysis is 19 | saved, and `recalculate` (True of False) specifies if the targeted attack is 20 | simultaneous (False), or sequential (True). 21 | 22 | ## Software Dependencies 23 | 24 | * [Python](https://www.python.org/) 25 | * [igraph](http://igraph.org/) 26 | * [NetworkX](https://networkx.github.io/) 27 | * [NumPy](http://www.numpy.org/) 28 | * [Matplotlib](http://matplotlib.org/) 29 | 30 | ## Contact 31 | 32 | If you have any questions about the software, please email 33 | swami.iyer@gmail.com. 34 | -------------------------------------------------------------------------------- /robustness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | This script performs robustness analysis on the given network, 5 | which involves removing nodes from the network at random, or in reverse 6 | order of centrality measures (degree, betweenness, closeness, and 7 | eigenvector), and comparing the size of the largest component in the 8 | network to the fraction of nodes removed. 9 | 10 | Usage: python robustness.py 11 | 12 | where infile is the name of the network file in gml format, outfile is the 13 | name of the output (pdf) file in which the results of the analysis is 14 | saved, and recalculate (True of False) specifies if the targeted attack is 15 | simultaneous (False), or sequential (True). 16 | """ 17 | 18 | import igraph, networkx, numpy, operator, pylab, random, sys 19 | 20 | def betweenness(infile, recalculate = False): 21 | """ 22 | Performs robustness analysis based on betweenness centrality, 23 | on the network specified by infile using sequential (recalculate = True) 24 | or simultaneous (recalculate = False) approach. Returns a list 25 | with fraction of nodes removed, a list with the corresponding sizes of 26 | the largest component of the network, and the overall vulnerability 27 | of the network. 28 | """ 29 | 30 | g = networkx.read_gml(infile) 31 | m = networkx.betweenness_centrality(g) 32 | l = sorted(m.items(), key = operator.itemgetter(1), reverse = True) 33 | x = [] 34 | y = [] 35 | largest_component = max(networkx.connected_components(g), key = len) 36 | n = len(g.nodes()) 37 | x.append(0) 38 | y.append(len(largest_component) * 1. / n) 39 | R = 0.0 40 | for i in range(1, n): 41 | g.remove_node(l.pop(0)[0]) 42 | if recalculate: 43 | m = networkx.betweenness_centrality(g) 44 | l = sorted(m.items(), key = operator.itemgetter(1), 45 | reverse = True) 46 | largest_component = max(networkx.connected_components(g), key = len) 47 | x.append(i * 1. / n) 48 | R += len(largest_component) * 1. / n 49 | y.append(len(largest_component) * 1. / n) 50 | return x, y, 0.5 - R / n 51 | 52 | def betweenness_fracture(infile, outfile, fraction, recalculate = False): 53 | """ 54 | Removes given fraction of nodes from infile network in reverse order of 55 | betweenness centrality (with or without recalculation of centrality values 56 | after each node removal) and saves the network in outfile. 57 | """ 58 | 59 | g = networkx.read_gml(infile) 60 | m = networkx.betweenness_centrality(g) 61 | l = sorted(m.items(), key = operator.itemgetter(1), reverse = True) 62 | largest_component = max(networkx.connected_components(g), key = len) 63 | n = len(g.nodes()) 64 | for i in range(1, n): 65 | g.remove_node(l.pop(0)[0]) 66 | if recalculate: 67 | m = networkx.betweenness_centrality(g) 68 | l = sorted(m.items(), key = operator.itemgetter(1), 69 | reverse = True) 70 | largest_component = max(networkx.connected_components(g), key = len) 71 | if i * 1. / n >= fraction: 72 | break 73 | components = networkx.connected_components(g) 74 | component_id = 1 75 | for component in components: 76 | for node in component: 77 | g.node[node]["component"] = component_id 78 | component_id += 1 79 | networkx.write_gml(g, outfile) 80 | 81 | def closeness(infile, recalculate = False): 82 | """ 83 | Performs robustness analysis based on closeness centrality, 84 | on the network specified by infile using sequential (recalculate = True) 85 | or simultaneous (recalculate = False) approach. Returns a list 86 | with fraction of nodes removed, a list with the corresponding sizes of 87 | the largest component of the network, and the overall vulnerability 88 | of the network. 89 | """ 90 | 91 | g = networkx.read_gml(infile) 92 | m = networkx.closeness_centrality(g) 93 | l = sorted(m.items(), key = operator.itemgetter(1), reverse = True) 94 | x = [] 95 | y = [] 96 | largest_component = max(networkx.connected_components(g), key = len) 97 | n = len(g.nodes()) 98 | x.append(0) 99 | y.append(len(largest_component) * 1. / n) 100 | R = 0.0 101 | for i in range(1, n): 102 | g.remove_node(l.pop(0)[0]) 103 | if recalculate: 104 | m = networkx.closeness_centrality(g) 105 | l = sorted(m.items(), key = operator.itemgetter(1), 106 | reverse = True) 107 | largest_component = max(networkx.connected_components(g), key = len) 108 | x.append(i * 1. / n) 109 | R += len(largest_component) * 1. / n 110 | y.append(len(largest_component) * 1. / n) 111 | return x, y, 0.5 - R / n 112 | 113 | def closeness_fracture(infile, outfile, fraction, recalculate = False): 114 | """ 115 | Removes given fraction of nodes from infile network in reverse order of 116 | closeness centrality (with or without recalculation of centrality values 117 | after each node removal) and saves the network in outfile. 118 | """ 119 | 120 | g = networkx.read_gml(infile) 121 | m = networkx.closeness_centrality(g) 122 | l = sorted(m.items(), key = operator.itemgetter(1), reverse = True) 123 | largest_component = max(networkx.connected_components(g), key = len) 124 | n = len(g.nodes()) 125 | for i in range(1, n): 126 | g.remove_node(l.pop(0)[0]) 127 | if recalculate: 128 | m = networkx.closeness_centrality(g) 129 | l = sorted(m.items(), key = operator.itemgetter(1), 130 | reverse = True) 131 | largest_component = max(networkx.connected_components(g), key = len) 132 | if i * 1. / n >= fraction: 133 | break 134 | components = networkx.connected_components(g) 135 | component_id = 1 136 | for component in components: 137 | for node in component: 138 | g.node[node]["component"] = component_id 139 | component_id += 1 140 | networkx.write_gml(g, outfile) 141 | 142 | def degree(infile, recalculate = False): 143 | """ 144 | Performs robustness analysis based on degree centrality, 145 | on the network specified by infile using sequential (recalculate = True) 146 | or simultaneous (recalculate = False) approach. Returns a list 147 | with fraction of nodes removed, a list with the corresponding sizes of 148 | the largest component of the network, and the overall vulnerability 149 | of the network. 150 | """ 151 | 152 | g = networkx.read_gml(infile) 153 | m = networkx.degree_centrality(g) 154 | l = sorted(m.items(), key = operator.itemgetter(1), reverse = True) 155 | x = [] 156 | y = [] 157 | largest_component = max(networkx.connected_components(g), key = len) 158 | n = len(g.nodes()) 159 | x.append(0) 160 | y.append(len(largest_component) * 1. / n) 161 | R = 0.0 162 | for i in range(1, n - 1): 163 | g.remove_node(l.pop(0)[0]) 164 | if recalculate: 165 | m = networkx.degree_centrality(g) 166 | l = sorted(m.items(), key = operator.itemgetter(1), 167 | reverse = True) 168 | largest_component = max(networkx.connected_components(g), key = len) 169 | x.append(i * 1. / n) 170 | R += len(largest_component) * 1. / n 171 | y.append(len(largest_component) * 1. / n) 172 | return x, y, 0.5 - R / n 173 | 174 | def degree_fracture(infile, outfile, fraction, recalculate = False): 175 | """ 176 | Removes given fraction of nodes from infile network in reverse order of 177 | degree centrality (with or without recalculation of centrality values 178 | after each node removal) and saves the network in outfile. 179 | """ 180 | 181 | g = networkx.read_gml(infile) 182 | m = networkx.degree_centrality(g) 183 | l = sorted(m.items(), key = operator.itemgetter(1), reverse = True) 184 | largest_component = max(networkx.connected_components(g), key = len) 185 | n = len(g.nodes()) 186 | for i in range(1, n - 1): 187 | g.remove_node(l.pop(0)[0]) 188 | if recalculate: 189 | m = networkx.degree_centrality(g) 190 | l = sorted(m.items(), key = operator.itemgetter(1), 191 | reverse = True) 192 | largest_component = max(networkx.connected_components(g), key = len) 193 | if i * 1. / n >= fraction: 194 | break 195 | components = networkx.connected_components(g) 196 | component_id = 1 197 | for component in components: 198 | for node in component: 199 | g.node[node]["component"] = component_id 200 | component_id += 1 201 | networkx.write_gml(g, outfile) 202 | 203 | def eigenvector(infile, recalculate = False): 204 | """ 205 | Performs robustness analysis based on eigenvector centrality, 206 | on the network specified by infile using sequential (recalculate = True) 207 | or simultaneous (recalculate = False) approach. Returns a list 208 | with fraction of nodes removed, a list with the corresponding sizes of 209 | the largest component of the network, and the overall vulnerability 210 | of the network. 211 | """ 212 | 213 | def indexof(g, s): 214 | vs = g.vs() 215 | for i in range(0, len(vs)): 216 | v = vs[i] 217 | if v["label"] == s: 218 | return i 219 | return None 220 | 221 | g = igraph.Graph.Read_GML(infile) 222 | vs = g.vs() 223 | m = {} 224 | el = g.eigenvector_centrality() 225 | for i in range(0, len(vs)): 226 | m[vs[i]["label"]] = float(el[i]) 227 | l = m.items() 228 | l = sorted(l, key = operator.itemgetter(1), reverse = True) 229 | x = [] 230 | y = [] 231 | largest_component = g.components().giant().vcount() 232 | n = g.vcount() 233 | x.append(0) 234 | y.append(largest_component * 1. / n) 235 | R = 0.0 236 | for i in range(1, n): 237 | g.delete_vertices(indexof(g, l.pop(0)[0])) 238 | if recalculate: 239 | m = {} 240 | el = g.eigenvector_centrality() 241 | for j in range(0, len(vs)): 242 | m[vs[j]["label"]] = float(el[j]) 243 | l = m.items() 244 | l = sorted(l, key = operator.itemgetter(1), reverse = True) 245 | largest_component = g.components().giant().vcount() 246 | x.append(i * 1. / n) 247 | R += largest_component * 1. / n 248 | y.append(largest_component * 1. / n) 249 | return x, y, 0.5 - R / n 250 | 251 | def eigenvector_fracture(infile, outfile, fraction, recalculate = False): 252 | """ 253 | Removes given fraction of nodes from infile network in reverse order of 254 | eigenvector centrality (with or without recalculation of centrality values 255 | after each node removal) and saves the network in outfile. 256 | """ 257 | 258 | def indexof(g, s): 259 | vs = g.vs() 260 | for i in range(0, len(vs)): 261 | v = vs[i] 262 | if v["label"] == s: 263 | return i 264 | return None 265 | 266 | g = igraph.Graph.Read_GML(infile) 267 | vs = g.vs() 268 | m = {} 269 | el = g.eigenvector_centrality() 270 | for i in range(0, len(vs)): 271 | m[vs[i]["label"]] = float(el[i]) 272 | l = m.items() 273 | l = sorted(l, key = operator.itemgetter(1), reverse = True) 274 | largest_component = g.components().giant().vcount() 275 | n = g.vcount() 276 | for i in range(1, n): 277 | g.delete_vertices(indexof(g, l.pop(0)[0])) 278 | if recalculate: 279 | m = {} 280 | el = g.eigenvector_centrality() 281 | for j in range(0, len(vs)): 282 | m[vs[j]["label"]] = float(el[j]) 283 | l = m.items() 284 | l = sorted(l, key = operator.itemgetter(1), reverse = True) 285 | largest_component = g.components().giant().vcount() 286 | if i * 1. / n >= fraction: 287 | break 288 | components = g.components() 289 | component_id = 1 290 | for component in components: 291 | for node in component: 292 | vs[node]["component"] = component_id 293 | component_id += 1 294 | g.write_gml(outfile) 295 | 296 | def rand(infile): 297 | """ 298 | Performs robustness analysis based on random attack, on the network 299 | specified by infile. Returns a list with fraction of nodes removed, a 300 | list with the corresponding sizes of the largest component of the 301 | network, and the overall vulnerability of the network. 302 | """ 303 | 304 | g = networkx.read_gml(infile) 305 | l = [(node, 0) for node in g.nodes()] 306 | random.shuffle(l) 307 | x = [] 308 | y = [] 309 | largest_component = max(networkx.connected_components(g), key = len) 310 | n = len(g.nodes()) 311 | x.append(0) 312 | y.append(len(largest_component) * 1. / n) 313 | R = 0.0 314 | for i in range(1, n): 315 | g.remove_node(l.pop(0)[0]) 316 | largest_component = max(networkx.connected_components(g), key = len) 317 | x.append(i * 1. / n) 318 | R += len(largest_component) * 1. / n 319 | y.append(len(largest_component) * 1. / n) 320 | return x, y, 0.5 - R / n 321 | 322 | def main(argv): 323 | """ 324 | Entry point. 325 | """ 326 | 327 | if len(argv) != 3: 328 | print "python robustness.py " 329 | sys.exit(0) 330 | 331 | infile = argv[0] 332 | outfile = argv[1] 333 | if argv[2] == "True": 334 | recalculate = True 335 | else: 336 | recalculate = False 337 | x1, y1, VD = degree(infile, recalculate) 338 | x2, y2, VB = betweenness(infile, recalculate) 339 | x3, y3, VC = closeness(infile, recalculate) 340 | x4, y4, VE = eigenvector(infile, recalculate) 341 | x5, y5, VR = rand(infile) 342 | 343 | pylab.figure(1, dpi = 500) 344 | pylab.xlabel(r"Fraction of vertices removed ($\rho$)") 345 | pylab.ylabel(r"Fractional size of largest component ($\sigma$)") 346 | pylab.plot(x1, y1, "b-", alpha = 0.6, linewidth = 2.0) 347 | pylab.plot(x2, y2, "g-", alpha = 0.6, linewidth = 2.0) 348 | pylab.plot(x3, y3, "r-", alpha = 0.6, linewidth = 2.0) 349 | pylab.plot(x4, y4, "c-", alpha = 0.6, linewidth = 2.0) 350 | pylab.plot(x5, y5, "k-", alpha = 0.6, linewidth = 2.0) 351 | pylab.legend((r"Degree ($V = %4.3f$)" %(VD), 352 | "Betweenness ($V = %4.3f$)" %(VB), 353 | "Closeness ($V = %4.3f$)" %(VC), 354 | "Eigenvector ($V = %4.3f$)" %(VE), 355 | "Random ($V = %4.3f$)" %(VR)), 356 | loc = "upper right", shadow = False) 357 | 358 | # Inset showing vulnerability values. 359 | labels = [r"$D$", r"$B$", r"$C$", r"$E$", r"$R$"] 360 | V = [VD, VB, VC, VE, VR] 361 | xlocations = numpy.array(range(len(V)))+0.2 362 | width = 0.2 363 | inset = pylab.axes([0.735, 0.45, 0.15, 0.15]) 364 | pylab.bar(xlocations, V, color = ["b", "g", "r", "c", "k"], 365 | alpha = 0.6, width = width) 366 | pylab.yticks([0.0, 0.25, 0.5]) 367 | pylab.xticks(xlocations + width / 2, labels) 368 | pylab.xlim(0, xlocations[-1] + width * 2) 369 | pylab.ylabel(r"$V$") 370 | 371 | pylab.savefig(outfile, format = "pdf") 372 | pylab.close(1) 373 | 374 | if __name__ == "__main__": 375 | main(sys.argv[1:]) 376 | 377 | --------------------------------------------------------------------------------