Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/generators/directed.py: 17%

141 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1""" 

2Generators for some directed graphs, including growing network (GN) graphs and 

3scale-free graphs. 

4 

5""" 

6 

7import numbers 

8from collections import Counter 

9 

10import networkx as nx 

11from networkx.generators.classic import empty_graph 

12from networkx.utils import discrete_sequence, py_random_state, weighted_choice 

13 

14__all__ = [ 

15 "gn_graph", 

16 "gnc_graph", 

17 "gnr_graph", 

18 "random_k_out_graph", 

19 "scale_free_graph", 

20] 

21 

22 

23@py_random_state(3) 

24@nx._dispatch(graphs=None) 

25def gn_graph(n, kernel=None, create_using=None, seed=None): 

26 """Returns the growing network (GN) digraph with `n` nodes. 

27 

28 The GN graph is built by adding nodes one at a time with a link to one 

29 previously added node. The target node for the link is chosen with 

30 probability based on degree. The default attachment kernel is a linear 

31 function of the degree of a node. 

32 

33 The graph is always a (directed) tree. 

34 

35 Parameters 

36 ---------- 

37 n : int 

38 The number of nodes for the generated graph. 

39 kernel : function 

40 The attachment kernel. 

41 create_using : NetworkX graph constructor, optional (default DiGraph) 

42 Graph type to create. If graph instance, then cleared before populated. 

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

44 Indicator of random number generation state. 

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

46 

47 Examples 

48 -------- 

49 To create the undirected GN graph, use the :meth:`~DiGraph.to_directed` 

50 method:: 

51 

52 >>> D = nx.gn_graph(10) # the GN graph 

53 >>> G = D.to_undirected() # the undirected version 

54 

55 To specify an attachment kernel, use the `kernel` keyword argument:: 

56 

57 >>> D = nx.gn_graph(10, kernel=lambda x: x ** 1.5) # A_k = k^1.5 

58 

59 References 

60 ---------- 

61 .. [1] P. L. Krapivsky and S. Redner, 

62 Organization of Growing Random Networks, 

63 Phys. Rev. E, 63, 066123, 2001. 

64 """ 

65 G = empty_graph(1, create_using, default=nx.DiGraph) 

66 if not G.is_directed(): 

67 raise nx.NetworkXError("create_using must indicate a Directed Graph") 

68 

69 if kernel is None: 

70 

71 def kernel(x): 

72 return x 

73 

74 if n == 1: 

75 return G 

76 

77 G.add_edge(1, 0) # get started 

78 ds = [1, 1] # degree sequence 

79 

80 for source in range(2, n): 

81 # compute distribution from kernel and degree 

82 dist = [kernel(d) for d in ds] 

83 # choose target from discrete distribution 

84 target = discrete_sequence(1, distribution=dist, seed=seed)[0] 

85 G.add_edge(source, target) 

86 ds.append(1) # the source has only one link (degree one) 

87 ds[target] += 1 # add one to the target link degree 

88 return G 

89 

90 

91@py_random_state(3) 

92@nx._dispatch(graphs=None) 

93def gnr_graph(n, p, create_using=None, seed=None): 

94 """Returns the growing network with redirection (GNR) digraph with `n` 

95 nodes and redirection probability `p`. 

96 

97 The GNR graph is built by adding nodes one at a time with a link to one 

98 previously added node. The previous target node is chosen uniformly at 

99 random. With probability `p` the link is instead "redirected" to the 

100 successor node of the target. 

101 

102 The graph is always a (directed) tree. 

103 

104 Parameters 

105 ---------- 

106 n : int 

107 The number of nodes for the generated graph. 

108 p : float 

109 The redirection probability. 

110 create_using : NetworkX graph constructor, optional (default DiGraph) 

111 Graph type to create. If graph instance, then cleared before populated. 

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

113 Indicator of random number generation state. 

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

115 

116 Examples 

117 -------- 

118 To create the undirected GNR graph, use the :meth:`~DiGraph.to_directed` 

119 method:: 

120 

121 >>> D = nx.gnr_graph(10, 0.5) # the GNR graph 

122 >>> G = D.to_undirected() # the undirected version 

123 

124 References 

125 ---------- 

126 .. [1] P. L. Krapivsky and S. Redner, 

127 Organization of Growing Random Networks, 

128 Phys. Rev. E, 63, 066123, 2001. 

129 """ 

130 G = empty_graph(1, create_using, default=nx.DiGraph) 

131 if not G.is_directed(): 

132 raise nx.NetworkXError("create_using must indicate a Directed Graph") 

133 

134 if n == 1: 

135 return G 

136 

137 for source in range(1, n): 

138 target = seed.randrange(0, source) 

139 if seed.random() < p and target != 0: 

140 target = next(G.successors(target)) 

141 G.add_edge(source, target) 

142 return G 

143 

144 

145@py_random_state(2) 

146@nx._dispatch(graphs=None) 

147def gnc_graph(n, create_using=None, seed=None): 

148 """Returns the growing network with copying (GNC) digraph with `n` nodes. 

149 

150 The GNC graph is built by adding nodes one at a time with a link to one 

151 previously added node (chosen uniformly at random) and to all of that 

152 node's successors. 

153 

154 Parameters 

155 ---------- 

156 n : int 

157 The number of nodes for the generated graph. 

158 create_using : NetworkX graph constructor, optional (default DiGraph) 

159 Graph type to create. If graph instance, then cleared before populated. 

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

161 Indicator of random number generation state. 

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

163 

164 References 

165 ---------- 

166 .. [1] P. L. Krapivsky and S. Redner, 

167 Network Growth by Copying, 

168 Phys. Rev. E, 71, 036118, 2005k.}, 

169 """ 

170 G = empty_graph(1, create_using, default=nx.DiGraph) 

171 if not G.is_directed(): 

172 raise nx.NetworkXError("create_using must indicate a Directed Graph") 

173 

174 if n == 1: 

175 return G 

176 

177 for source in range(1, n): 

178 target = seed.randrange(0, source) 

179 for succ in G.successors(target): 

180 G.add_edge(source, succ) 

181 G.add_edge(source, target) 

182 return G 

183 

184 

185@py_random_state(6) 

186@nx._dispatch(graphs=None) 

187def scale_free_graph( 

188 n, 

189 alpha=0.41, 

190 beta=0.54, 

191 gamma=0.05, 

192 delta_in=0.2, 

193 delta_out=0, 

194 seed=None, 

195 initial_graph=None, 

196): 

197 """Returns a scale-free directed graph. 

198 

199 Parameters 

200 ---------- 

201 n : integer 

202 Number of nodes in graph 

203 alpha : float 

204 Probability for adding a new node connected to an existing node 

205 chosen randomly according to the in-degree distribution. 

206 beta : float 

207 Probability for adding an edge between two existing nodes. 

208 One existing node is chosen randomly according the in-degree 

209 distribution and the other chosen randomly according to the out-degree 

210 distribution. 

211 gamma : float 

212 Probability for adding a new node connected to an existing node 

213 chosen randomly according to the out-degree distribution. 

214 delta_in : float 

215 Bias for choosing nodes from in-degree distribution. 

216 delta_out : float 

217 Bias for choosing nodes from out-degree distribution. 

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

219 Indicator of random number generation state. 

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

221 initial_graph : MultiDiGraph instance, optional 

222 Build the scale-free graph starting from this initial MultiDiGraph, 

223 if provided. 

224 

225 Returns 

226 ------- 

227 MultiDiGraph 

228 

229 Examples 

230 -------- 

231 Create a scale-free graph on one hundred nodes:: 

232 

233 >>> G = nx.scale_free_graph(100) 

234 

235 Notes 

236 ----- 

237 The sum of `alpha`, `beta`, and `gamma` must be 1. 

238 

239 References 

240 ---------- 

241 .. [1] B. Bollobás, C. Borgs, J. Chayes, and O. Riordan, 

242 Directed scale-free graphs, 

243 Proceedings of the fourteenth annual ACM-SIAM Symposium on 

244 Discrete Algorithms, 132--139, 2003. 

245 """ 

246 

247 def _choose_node(candidates, node_list, delta): 

248 if delta > 0: 

249 bias_sum = len(node_list) * delta 

250 p_delta = bias_sum / (bias_sum + len(candidates)) 

251 if seed.random() < p_delta: 

252 return seed.choice(node_list) 

253 return seed.choice(candidates) 

254 

255 if initial_graph is not None and hasattr(initial_graph, "_adj"): 

256 if not isinstance(initial_graph, nx.MultiDiGraph): 

257 raise nx.NetworkXError("initial_graph must be a MultiDiGraph.") 

258 G = initial_graph 

259 else: 

260 # Start with 3-cycle 

261 G = nx.MultiDiGraph([(0, 1), (1, 2), (2, 0)]) 

262 

263 if alpha <= 0: 

264 raise ValueError("alpha must be > 0.") 

265 if beta <= 0: 

266 raise ValueError("beta must be > 0.") 

267 if gamma <= 0: 

268 raise ValueError("gamma must be > 0.") 

269 

270 if abs(alpha + beta + gamma - 1.0) >= 1e-9: 

271 raise ValueError("alpha+beta+gamma must equal 1.") 

272 

273 if delta_in < 0: 

274 raise ValueError("delta_in must be >= 0.") 

275 

276 if delta_out < 0: 

277 raise ValueError("delta_out must be >= 0.") 

278 

279 # pre-populate degree states 

280 vs = sum((count * [idx] for idx, count in G.out_degree()), []) 

281 ws = sum((count * [idx] for idx, count in G.in_degree()), []) 

282 

283 # pre-populate node state 

284 node_list = list(G.nodes()) 

285 

286 # see if there already are number-based nodes 

287 numeric_nodes = [n for n in node_list if isinstance(n, numbers.Number)] 

288 if len(numeric_nodes) > 0: 

289 # set cursor for new nodes appropriately 

290 cursor = max(int(n.real) for n in numeric_nodes) + 1 

291 else: 

292 # or start at zero 

293 cursor = 0 

294 

295 while len(G) < n: 

296 r = seed.random() 

297 

298 # random choice in alpha,beta,gamma ranges 

299 if r < alpha: 

300 # alpha 

301 # add new node v 

302 v = cursor 

303 cursor += 1 

304 # also add to node state 

305 node_list.append(v) 

306 # choose w according to in-degree and delta_in 

307 w = _choose_node(ws, node_list, delta_in) 

308 

309 elif r < alpha + beta: 

310 # beta 

311 # choose v according to out-degree and delta_out 

312 v = _choose_node(vs, node_list, delta_out) 

313 # choose w according to in-degree and delta_in 

314 w = _choose_node(ws, node_list, delta_in) 

315 

316 else: 

317 # gamma 

318 # choose v according to out-degree and delta_out 

319 v = _choose_node(vs, node_list, delta_out) 

320 # add new node w 

321 w = cursor 

322 cursor += 1 

323 # also add to node state 

324 node_list.append(w) 

325 

326 # add edge to graph 

327 G.add_edge(v, w) 

328 

329 # update degree states 

330 vs.append(v) 

331 ws.append(w) 

332 

333 return G 

334 

335 

336@py_random_state(4) 

337@nx._dispatch(graphs=None) 

338def random_uniform_k_out_graph(n, k, self_loops=True, with_replacement=True, seed=None): 

339 """Returns a random `k`-out graph with uniform attachment. 

340 

341 A random `k`-out graph with uniform attachment is a multidigraph 

342 generated by the following algorithm. For each node *u*, choose 

343 `k` nodes *v* uniformly at random (with replacement). Add a 

344 directed edge joining *u* to *v*. 

345 

346 Parameters 

347 ---------- 

348 n : int 

349 The number of nodes in the returned graph. 

350 

351 k : int 

352 The out-degree of each node in the returned graph. 

353 

354 self_loops : bool 

355 If True, self-loops are allowed when generating the graph. 

356 

357 with_replacement : bool 

358 If True, neighbors are chosen with replacement and the 

359 returned graph will be a directed multigraph. Otherwise, 

360 neighbors are chosen without replacement and the returned graph 

361 will be a directed graph. 

362 

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

364 Indicator of random number generation state. 

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

366 

367 Returns 

368 ------- 

369 NetworkX graph 

370 A `k`-out-regular directed graph generated according to the 

371 above algorithm. It will be a multigraph if and only if 

372 `with_replacement` is True. 

373 

374 Raises 

375 ------ 

376 ValueError 

377 If `with_replacement` is False and `k` is greater than 

378 `n`. 

379 

380 See also 

381 -------- 

382 random_k_out_graph 

383 

384 Notes 

385 ----- 

386 The return digraph or multidigraph may not be strongly connected, or 

387 even weakly connected. 

388 

389 If `with_replacement` is True, this function is similar to 

390 :func:`random_k_out_graph`, if that function had parameter `alpha` 

391 set to positive infinity. 

392 

393 """ 

394 if with_replacement: 

395 create_using = nx.MultiDiGraph() 

396 

397 def sample(v, nodes): 

398 if not self_loops: 

399 nodes = nodes - {v} 

400 return (seed.choice(list(nodes)) for i in range(k)) 

401 

402 else: 

403 create_using = nx.DiGraph() 

404 

405 def sample(v, nodes): 

406 if not self_loops: 

407 nodes = nodes - {v} 

408 return seed.sample(list(nodes), k) 

409 

410 G = nx.empty_graph(n, create_using) 

411 nodes = set(G) 

412 for u in G: 

413 G.add_edges_from((u, v) for v in sample(u, nodes)) 

414 return G 

415 

416 

417@py_random_state(4) 

418@nx._dispatch(graphs=None) 

419def random_k_out_graph(n, k, alpha, self_loops=True, seed=None): 

420 """Returns a random `k`-out graph with preferential attachment. 

421 

422 A random `k`-out graph with preferential attachment is a 

423 multidigraph generated by the following algorithm. 

424 

425 1. Begin with an empty digraph, and initially set each node to have 

426 weight `alpha`. 

427 2. Choose a node `u` with out-degree less than `k` uniformly at 

428 random. 

429 3. Choose a node `v` from with probability proportional to its 

430 weight. 

431 4. Add a directed edge from `u` to `v`, and increase the weight 

432 of `v` by one. 

433 5. If each node has out-degree `k`, halt, otherwise repeat from 

434 step 2. 

435 

436 For more information on this model of random graph, see [1]. 

437 

438 Parameters 

439 ---------- 

440 n : int 

441 The number of nodes in the returned graph. 

442 

443 k : int 

444 The out-degree of each node in the returned graph. 

445 

446 alpha : float 

447 A positive :class:`float` representing the initial weight of 

448 each vertex. A higher number means that in step 3 above, nodes 

449 will be chosen more like a true uniformly random sample, and a 

450 lower number means that nodes are more likely to be chosen as 

451 their in-degree increases. If this parameter is not positive, a 

452 :exc:`ValueError` is raised. 

453 

454 self_loops : bool 

455 If True, self-loops are allowed when generating the graph. 

456 

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

458 Indicator of random number generation state. 

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

460 

461 Returns 

462 ------- 

463 :class:`~networkx.classes.MultiDiGraph` 

464 A `k`-out-regular multidigraph generated according to the above 

465 algorithm. 

466 

467 Raises 

468 ------ 

469 ValueError 

470 If `alpha` is not positive. 

471 

472 Notes 

473 ----- 

474 The returned multidigraph may not be strongly connected, or even 

475 weakly connected. 

476 

477 References 

478 ---------- 

479 [1]: Peterson, Nicholas R., and Boris Pittel. 

480 "Distance between two random `k`-out digraphs, with and without 

481 preferential attachment." 

482 arXiv preprint arXiv:1311.5961 (2013). 

483 <https://arxiv.org/abs/1311.5961> 

484 

485 """ 

486 if alpha < 0: 

487 raise ValueError("alpha must be positive") 

488 G = nx.empty_graph(n, create_using=nx.MultiDiGraph) 

489 weights = Counter({v: alpha for v in G}) 

490 for i in range(k * n): 

491 u = seed.choice([v for v, d in G.out_degree() if d < k]) 

492 # If self-loops are not allowed, make the source node `u` have 

493 # weight zero. 

494 if not self_loops: 

495 adjustment = Counter({u: weights[u]}) 

496 else: 

497 adjustment = Counter() 

498 v = weighted_choice(weights - adjustment, seed=seed) 

499 G.add_edge(u, v) 

500 weights[v] += 1 

501 return G