Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/generators/internet_as_graphs.py: 13%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

184 statements  

1"""Generates graphs resembling the Internet Autonomous System network""" 

2 

3import networkx as nx 

4from networkx.utils import py_random_state 

5 

6__all__ = ["random_internet_as_graph"] 

7 

8 

9def uniform_int_from_avg(a, m, seed): 

10 """Pick a random integer with uniform probability. 

11 

12 Returns a random integer uniformly taken from a distribution with 

13 minimum value 'a' and average value 'm', X~U(a,b), E[X]=m, X in N where 

14 b = 2*m - a. 

15 

16 Notes 

17 ----- 

18 p = (b-floor(b))/2 

19 X = X1 + X2; X1~U(a,floor(b)), X2~B(p) 

20 E[X] = E[X1] + E[X2] = (floor(b)+a)/2 + (b-floor(b))/2 = (b+a)/2 = m 

21 """ 

22 

23 from math import floor 

24 

25 assert m >= a 

26 b = 2 * m - a 

27 p = (b - floor(b)) / 2 

28 X1 = round(seed.random() * (floor(b) - a) + a) 

29 if seed.random() < p: 

30 X2 = 1 

31 else: 

32 X2 = 0 

33 return X1 + X2 

34 

35 

36@py_random_state("seed") 

37def choose_pref_attach(degs, seed): 

38 """Pick a random value, with a probability given by its weight. 

39 

40 Returns a random choice among degs keys, each of which has a 

41 probability proportional to the corresponding dictionary value. 

42 

43 Parameters 

44 ---------- 

45 degs: dictionary 

46 It contains the possible values (keys) and the corresponding 

47 probabilities (values) 

48 seed: random state 

49 

50 Returns 

51 ------- 

52 v: object 

53 A key of degs or None if degs is empty 

54 """ 

55 

56 if len(degs) == 0: 

57 return None 

58 s = sum(degs.values()) 

59 if s == 0: 

60 return seed.choice(list(degs.keys())) 

61 v = seed.random() * s 

62 

63 nodes = list(degs.keys()) 

64 i = 0 

65 acc = degs[nodes[i]] 

66 while v > acc: 

67 i += 1 

68 acc += degs[nodes[i]] 

69 return nodes[i] 

70 

71 

72class AS_graph_generator: 

73 """Generates random internet AS graphs.""" 

74 

75 @py_random_state("seed") 

76 def __init__(self, n, seed): 

77 """Initializes variables. Immediate numbers are taken from [1]. 

78 

79 Parameters 

80 ---------- 

81 n: integer 

82 Number of graph nodes 

83 seed: random state 

84 Indicator of random number generation state. 

85 See :ref:`Randomness<randomness>`. 

86 

87 Returns 

88 ------- 

89 GG: AS_graph_generator object 

90 

91 References 

92 ---------- 

93 [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of 

94 BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas 

95 in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010. 

96 """ 

97 

98 self.seed = seed 

99 self.n_t = min(n, round(self.seed.random() * 2 + 4)) # num of T nodes 

100 self.n_m = round(0.15 * n) # number of M nodes 

101 self.n_cp = round(0.05 * n) # number of CP nodes 

102 self.n_c = max(0, n - self.n_t - self.n_m - self.n_cp) # number of C nodes 

103 

104 self.d_m = 2 + (2.5 * n) / 10000 # average multihoming degree for M nodes 

105 self.d_cp = 2 + (1.5 * n) / 10000 # avg multihoming degree for CP nodes 

106 self.d_c = 1 + (5 * n) / 100000 # average multihoming degree for C nodes 

107 

108 self.p_m_m = 1 + (2 * n) / 10000 # avg num of peer edges between M and M 

109 self.p_cp_m = 0.2 + (2 * n) / 10000 # avg num of peer edges between CP, M 

110 self.p_cp_cp = 0.05 + (2 * n) / 100000 # avg num of peer edges btwn CP, CP 

111 

112 self.t_m = 0.375 # probability M's provider is T 

113 self.t_cp = 0.375 # probability CP's provider is T 

114 self.t_c = 0.125 # probability C's provider is T 

115 

116 def t_graph(self): 

117 """Generates the core mesh network of tier one nodes of a AS graph. 

118 

119 Returns 

120 ------- 

121 G: Networkx Graph 

122 Core network 

123 """ 

124 

125 self.G = nx.Graph() 

126 for i in range(self.n_t): 

127 self.G.add_node(i, type="T") 

128 for r in self.regions: 

129 self.regions[r].add(i) 

130 for j in self.G.nodes(): 

131 if i != j: 

132 self.add_edge(i, j, "peer") 

133 self.customers[i] = set() 

134 self.providers[i] = set() 

135 return self.G 

136 

137 def add_edge(self, i, j, kind): 

138 if kind == "transit": 

139 customer = str(i) 

140 else: 

141 customer = "none" 

142 self.G.add_edge(i, j, type=kind, customer=customer) 

143 

144 def choose_peer_pref_attach(self, node_list): 

145 """Pick a node with a probability weighted by its peer degree. 

146 

147 Pick a node from node_list with preferential attachment 

148 computed only on their peer degree 

149 """ 

150 

151 d = {} 

152 for n in node_list: 

153 d[n] = self.G.nodes[n]["peers"] 

154 return choose_pref_attach(d, self.seed) 

155 

156 def choose_node_pref_attach(self, node_list): 

157 """Pick a node with a probability weighted by its degree. 

158 

159 Pick a node from node_list with preferential attachment 

160 computed on their degree 

161 """ 

162 

163 degs = dict(self.G.degree(node_list)) 

164 return choose_pref_attach(degs, self.seed) 

165 

166 def add_customer(self, i, j): 

167 """Keep the dictionaries 'customers' and 'providers' consistent.""" 

168 

169 self.customers[j].add(i) 

170 self.providers[i].add(j) 

171 for z in self.providers[j]: 

172 self.customers[z].add(i) 

173 self.providers[i].add(z) 

174 

175 def add_node(self, i, kind, reg2prob, avg_deg, t_edge_prob): 

176 """Add a node and its customer transit edges to the graph. 

177 

178 Parameters 

179 ---------- 

180 i: object 

181 Identifier of the new node 

182 kind: string 

183 Type of the new node. Options are: 'M' for middle node, 'CP' for 

184 content provider and 'C' for customer. 

185 reg2prob: float 

186 Probability the new node can be in two different regions. 

187 avg_deg: float 

188 Average number of transit nodes of which node i is customer. 

189 t_edge_prob: float 

190 Probability node i establish a customer transit edge with a tier 

191 one (T) node 

192 

193 Returns 

194 ------- 

195 i: object 

196 Identifier of the new node 

197 """ 

198 

199 regs = 1 # regions in which node resides 

200 if self.seed.random() < reg2prob: # node is in two regions 

201 regs = 2 

202 node_options = set() 

203 

204 self.G.add_node(i, type=kind, peers=0) 

205 self.customers[i] = set() 

206 self.providers[i] = set() 

207 self.nodes[kind].add(i) 

208 for r in self.seed.sample(list(self.regions), regs): 

209 node_options = node_options.union(self.regions[r]) 

210 self.regions[r].add(i) 

211 

212 edge_num = uniform_int_from_avg(1, avg_deg, self.seed) 

213 

214 t_options = node_options.intersection(self.nodes["T"]) 

215 m_options = node_options.intersection(self.nodes["M"]) 

216 if i in m_options: 

217 m_options.remove(i) 

218 d = 0 

219 while d < edge_num and (len(t_options) > 0 or len(m_options) > 0): 

220 if len(m_options) == 0 or ( 

221 len(t_options) > 0 and self.seed.random() < t_edge_prob 

222 ): # add edge to a T node 

223 j = self.choose_node_pref_attach(t_options) 

224 t_options.remove(j) 

225 else: 

226 j = self.choose_node_pref_attach(m_options) 

227 m_options.remove(j) 

228 self.add_edge(i, j, "transit") 

229 self.add_customer(i, j) 

230 d += 1 

231 

232 return i 

233 

234 def add_m_peering_link(self, m, to_kind): 

235 """Add a peering link between two middle tier (M) nodes. 

236 

237 Target node j is drawn considering a preferential attachment based on 

238 other M node peering degree. 

239 

240 Parameters 

241 ---------- 

242 m: object 

243 Node identifier 

244 to_kind: string 

245 type for target node j (must be always M) 

246 

247 Returns 

248 ------- 

249 success: boolean 

250 """ 

251 

252 # candidates are of type 'M' and are not customers of m 

253 node_options = self.nodes["M"].difference(self.customers[m]) 

254 # candidates are not providers of m 

255 node_options = node_options.difference(self.providers[m]) 

256 # remove self 

257 if m in node_options: 

258 node_options.remove(m) 

259 

260 # remove candidates we are already connected to 

261 for j in self.G.neighbors(m): 

262 if j in node_options: 

263 node_options.remove(j) 

264 

265 if len(node_options) > 0: 

266 j = self.choose_peer_pref_attach(node_options) 

267 self.add_edge(m, j, "peer") 

268 self.G.nodes[m]["peers"] += 1 

269 self.G.nodes[j]["peers"] += 1 

270 return True 

271 else: 

272 return False 

273 

274 def add_cp_peering_link(self, cp, to_kind): 

275 """Add a peering link to a content provider (CP) node. 

276 

277 Target node j can be CP or M and it is drawn uniformly among the nodes 

278 belonging to the same region as cp. 

279 

280 Parameters 

281 ---------- 

282 cp: object 

283 Node identifier 

284 to_kind: string 

285 type for target node j (must be M or CP) 

286 

287 Returns 

288 ------- 

289 success: boolean 

290 """ 

291 

292 node_options = set() 

293 for r in self.regions: # options include nodes in the same region(s) 

294 if cp in self.regions[r]: 

295 node_options = node_options.union(self.regions[r]) 

296 

297 # options are restricted to the indicated kind ('M' or 'CP') 

298 node_options = self.nodes[to_kind].intersection(node_options) 

299 

300 # remove self 

301 if cp in node_options: 

302 node_options.remove(cp) 

303 

304 # remove nodes that are cp's providers 

305 node_options = node_options.difference(self.providers[cp]) 

306 

307 # remove nodes we are already connected to 

308 for j in self.G.neighbors(cp): 

309 if j in node_options: 

310 node_options.remove(j) 

311 

312 if len(node_options) > 0: 

313 j = self.seed.sample(list(node_options), 1)[0] 

314 self.add_edge(cp, j, "peer") 

315 self.G.nodes[cp]["peers"] += 1 

316 self.G.nodes[j]["peers"] += 1 

317 return True 

318 else: 

319 return False 

320 

321 def graph_regions(self, rn): 

322 """Initializes AS network regions. 

323 

324 Parameters 

325 ---------- 

326 rn: integer 

327 Number of regions 

328 """ 

329 

330 self.regions = {} 

331 for i in range(rn): 

332 self.regions["REG" + str(i)] = set() 

333 

334 def add_peering_links(self, from_kind, to_kind): 

335 """Utility function to add peering links among node groups.""" 

336 peer_link_method = None 

337 if from_kind == "M": 

338 peer_link_method = self.add_m_peering_link 

339 m = self.p_m_m 

340 if from_kind == "CP": 

341 peer_link_method = self.add_cp_peering_link 

342 if to_kind == "M": 

343 m = self.p_cp_m 

344 else: 

345 m = self.p_cp_cp 

346 

347 for i in self.nodes[from_kind]: 

348 num = uniform_int_from_avg(0, m, self.seed) 

349 for _ in range(num): 

350 peer_link_method(i, to_kind) 

351 

352 def generate(self): 

353 """Generates a random AS network graph as described in [1]. 

354 

355 Returns 

356 ------- 

357 G: Graph object 

358 

359 Notes 

360 ----- 

361 The process steps are the following: first we create the core network 

362 of tier one nodes, then we add the middle tier (M), the content 

363 provider (CP) and the customer (C) nodes along with their transit edges 

364 (link i,j means i is customer of j). Finally we add peering links 

365 between M nodes, between M and CP nodes and between CP node couples. 

366 For a detailed description of the algorithm, please refer to [1]. 

367 

368 References 

369 ---------- 

370 [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of 

371 BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas 

372 in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010. 

373 """ 

374 

375 self.graph_regions(5) 

376 self.customers = {} 

377 self.providers = {} 

378 self.nodes = {"T": set(), "M": set(), "CP": set(), "C": set()} 

379 

380 self.t_graph() 

381 self.nodes["T"] = set(self.G.nodes()) 

382 

383 i = len(self.nodes["T"]) 

384 for _ in range(self.n_m): 

385 self.nodes["M"].add(self.add_node(i, "M", 0.2, self.d_m, self.t_m)) 

386 i += 1 

387 for _ in range(self.n_cp): 

388 self.nodes["CP"].add(self.add_node(i, "CP", 0.05, self.d_cp, self.t_cp)) 

389 i += 1 

390 for _ in range(self.n_c): 

391 self.nodes["C"].add(self.add_node(i, "C", 0, self.d_c, self.t_c)) 

392 i += 1 

393 

394 self.add_peering_links("M", "M") 

395 self.add_peering_links("CP", "M") 

396 self.add_peering_links("CP", "CP") 

397 

398 return self.G 

399 

400 

401@py_random_state(1) 

402@nx._dispatchable(graphs=None, returns_graph=True) 

403def random_internet_as_graph(n, seed=None): 

404 """Generates a random undirected graph resembling the Internet AS network 

405 

406 Parameters 

407 ---------- 

408 n: integer in [1000, 10000] 

409 Number of graph nodes 

410 seed : integer, random_state, or None (default) 

411 Indicator of random number generation state. 

412 See :ref:`Randomness<randomness>`. 

413 

414 Returns 

415 ------- 

416 G: Networkx Graph object 

417 A randomly generated undirected graph 

418 

419 Notes 

420 ----- 

421 This algorithm returns an undirected graph resembling the Internet 

422 Autonomous System (AS) network, it uses the approach by Elmokashfi et al. 

423 [1]_ and it grants the properties described in the related paper [1]_. 

424 

425 Each node models an autonomous system, with an attribute 'type' specifying 

426 its kind; tier-1 (T), mid-level (M), customer (C) or content-provider (CP). 

427 Each edge models an ADV communication link (hence, bidirectional) with 

428 attributes: 

429 

430 - type: transit|peer, the kind of commercial agreement between nodes; 

431 - customer: <node id>, the identifier of the node acting as customer 

432 ('none' if type is peer). 

433 

434 References 

435 ---------- 

436 .. [1] A. Elmokashfi, A. Kvalbein and C. Dovrolis, "On the Scalability of 

437 BGP: The Role of Topology Growth," in IEEE Journal on Selected Areas 

438 in Communications, vol. 28, no. 8, pp. 1250-1261, October 2010. 

439 """ 

440 

441 GG = AS_graph_generator(n, seed) 

442 G = GG.generate() 

443 return G