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

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

451 statements  

1""" 

2Generators for random graphs. 

3 

4""" 

5 

6import itertools 

7import math 

8from collections import defaultdict 

9 

10import networkx as nx 

11from networkx.utils import py_random_state 

12 

13from ..utils.misc import check_create_using 

14from .classic import complete_graph, empty_graph, path_graph, star_graph 

15from .degree_seq import degree_sequence_tree 

16 

17__all__ = [ 

18 "fast_gnp_random_graph", 

19 "gnp_random_graph", 

20 "dense_gnm_random_graph", 

21 "gnm_random_graph", 

22 "erdos_renyi_graph", 

23 "binomial_graph", 

24 "newman_watts_strogatz_graph", 

25 "watts_strogatz_graph", 

26 "connected_watts_strogatz_graph", 

27 "random_regular_graph", 

28 "barabasi_albert_graph", 

29 "dual_barabasi_albert_graph", 

30 "extended_barabasi_albert_graph", 

31 "powerlaw_cluster_graph", 

32 "random_lobster", 

33 "random_lobster_graph", 

34 "random_shell_graph", 

35 "random_powerlaw_tree", 

36 "random_powerlaw_tree_sequence", 

37 "random_kernel_graph", 

38] 

39 

40 

41@py_random_state(2) 

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

43def fast_gnp_random_graph(n, p, seed=None, directed=False, *, create_using=None): 

44 """Returns a $G_{n,p}$ random graph, also known as an Erdős-Rényi graph or 

45 a binomial graph. 

46 

47 Parameters 

48 ---------- 

49 n : int 

50 The number of nodes. 

51 p : float 

52 Probability for edge creation. 

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

54 Indicator of random number generation state. 

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

56 directed : bool, optional (default=False) 

57 If True, this function returns a directed graph. 

58 create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph) 

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

60 Multigraph types are not supported and raise a ``NetworkXError``. 

61 By default NetworkX Graph or DiGraph are used depending on `directed`. 

62 

63 Notes 

64 ----- 

65 The $G_{n,p}$ graph algorithm chooses each of the $[n (n - 1)] / 2$ 

66 (undirected) or $n (n - 1)$ (directed) possible edges with probability $p$. 

67 

68 This algorithm [1]_ runs in $O(n + m)$ time, where `m` is the expected number of 

69 edges, which equals $p n (n - 1) / 2$. This should be faster than 

70 :func:`gnp_random_graph` when $p$ is small and the expected number of edges 

71 is small (that is, the graph is sparse). 

72 

73 See Also 

74 -------- 

75 gnp_random_graph 

76 

77 References 

78 ---------- 

79 .. [1] Vladimir Batagelj and Ulrik Brandes, 

80 "Efficient generation of large random networks", 

81 Phys. Rev. E, 71, 036113, 2005. 

82 """ 

83 default = nx.DiGraph if directed else nx.Graph 

84 create_using = check_create_using( 

85 create_using, directed=directed, multigraph=False, default=default 

86 ) 

87 if p <= 0 or p >= 1: 

88 return nx.gnp_random_graph( 

89 n, p, seed=seed, directed=directed, create_using=create_using 

90 ) 

91 

92 G = empty_graph(n, create_using=create_using) 

93 

94 lp = math.log(1.0 - p) 

95 

96 if directed: 

97 v = 1 

98 w = -1 

99 while v < n: 

100 lr = math.log(1.0 - seed.random()) 

101 w = w + 1 + int(lr / lp) 

102 while w >= v and v < n: 

103 w = w - v 

104 v = v + 1 

105 if v < n: 

106 G.add_edge(w, v) 

107 

108 # Nodes in graph are from 0,n-1 (start with v as the second node index). 

109 v = 1 

110 w = -1 

111 while v < n: 

112 lr = math.log(1.0 - seed.random()) 

113 w = w + 1 + int(lr / lp) 

114 while w >= v and v < n: 

115 w = w - v 

116 v = v + 1 

117 if v < n: 

118 G.add_edge(v, w) 

119 return G 

120 

121 

122@py_random_state(2) 

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

124def gnp_random_graph(n, p, seed=None, directed=False, *, create_using=None): 

125 """Returns a $G_{n,p}$ random graph, also known as an Erdős-Rényi graph 

126 or a binomial graph. 

127 

128 The $G_{n,p}$ model chooses each of the possible edges with probability $p$. 

129 

130 Parameters 

131 ---------- 

132 n : int 

133 The number of nodes. 

134 p : float 

135 Probability for edge creation. 

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

137 Indicator of random number generation state. 

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

139 directed : bool, optional (default=False) 

140 If True, this function returns a directed graph. 

141 create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph) 

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

143 Multigraph types are not supported and raise a ``NetworkXError``. 

144 By default NetworkX Graph or DiGraph are used depending on `directed`. 

145 

146 See Also 

147 -------- 

148 fast_gnp_random_graph 

149 

150 Notes 

151 ----- 

152 This algorithm [2]_ runs in $O(n^2)$ time. For sparse graphs (that is, for 

153 small values of $p$), :func:`fast_gnp_random_graph` is a faster algorithm. 

154 

155 :func:`binomial_graph` and :func:`erdos_renyi_graph` are 

156 aliases for :func:`gnp_random_graph`. 

157 

158 >>> nx.binomial_graph is nx.gnp_random_graph 

159 True 

160 >>> nx.erdos_renyi_graph is nx.gnp_random_graph 

161 True 

162 

163 References 

164 ---------- 

165 .. [1] P. Erdős and A. Rényi, On Random Graphs, Publ. Math. 6, 290 (1959). 

166 .. [2] E. N. Gilbert, Random Graphs, Ann. Math. Stat., 30, 1141 (1959). 

167 """ 

168 default = nx.DiGraph if directed else nx.Graph 

169 create_using = check_create_using( 

170 create_using, directed=directed, multigraph=False, default=default 

171 ) 

172 if p >= 1: 

173 return complete_graph(n, create_using=create_using) 

174 

175 G = nx.empty_graph(n, create_using=create_using) 

176 if p <= 0: 

177 return G 

178 

179 edgetool = itertools.permutations if directed else itertools.combinations 

180 for e in edgetool(range(n), 2): 

181 if seed.random() < p: 

182 G.add_edge(*e) 

183 return G 

184 

185 

186# add some aliases to common names 

187binomial_graph = gnp_random_graph 

188erdos_renyi_graph = gnp_random_graph 

189 

190 

191@py_random_state(2) 

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

193def dense_gnm_random_graph(n, m, seed=None, *, create_using=None): 

194 """Returns a $G_{n,m}$ random graph. 

195 

196 In the $G_{n,m}$ model, a graph is chosen uniformly at random from the set 

197 of all graphs with $n$ nodes and $m$ edges. 

198 

199 This algorithm should be faster than :func:`gnm_random_graph` for dense 

200 graphs. 

201 

202 Parameters 

203 ---------- 

204 n : int 

205 The number of nodes. 

206 m : int 

207 The number of edges. 

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

209 Indicator of random number generation state. 

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

211 create_using : Graph constructor, optional (default=nx.Graph) 

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

213 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

214 

215 See Also 

216 -------- 

217 gnm_random_graph 

218 

219 Notes 

220 ----- 

221 Algorithm by Keith M. Briggs Mar 31, 2006. 

222 Inspired by Knuth's Algorithm S (Selection sampling technique), 

223 in section 3.4.2 of [1]_. 

224 

225 References 

226 ---------- 

227 .. [1] Donald E. Knuth, The Art of Computer Programming, 

228 Volume 2/Seminumerical algorithms, Third Edition, Addison-Wesley, 1997. 

229 """ 

230 create_using = check_create_using(create_using, directed=False, multigraph=False) 

231 mmax = n * (n - 1) // 2 

232 if m >= mmax: 

233 return complete_graph(n, create_using) 

234 G = empty_graph(n, create_using) 

235 

236 if n == 1: 

237 return G 

238 

239 u = 0 

240 v = 1 

241 t = 0 

242 k = 0 

243 while True: 

244 if seed.randrange(mmax - t) < m - k: 

245 G.add_edge(u, v) 

246 k += 1 

247 if k == m: 

248 return G 

249 t += 1 

250 v += 1 

251 if v == n: # go to next row of adjacency matrix 

252 u += 1 

253 v = u + 1 

254 

255 

256@py_random_state(2) 

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

258def gnm_random_graph(n, m, seed=None, directed=False, *, create_using=None): 

259 """Returns a $G_{n,m}$ random graph. 

260 

261 In the $G_{n,m}$ model, a graph is chosen uniformly at random from the set 

262 of all graphs with $n$ nodes and $m$ edges. 

263 

264 This algorithm should be faster than :func:`dense_gnm_random_graph` for 

265 sparse graphs. 

266 

267 Parameters 

268 ---------- 

269 n : int 

270 The number of nodes. 

271 m : int 

272 The number of edges. 

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

274 Indicator of random number generation state. 

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

276 directed : bool, optional (default=False) 

277 If True return a directed graph 

278 create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph) 

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

280 Multigraph types are not supported and raise a ``NetworkXError``. 

281 By default NetworkX Graph or DiGraph are used depending on `directed`. 

282 

283 See also 

284 -------- 

285 dense_gnm_random_graph 

286 

287 """ 

288 default = nx.DiGraph if directed else nx.Graph 

289 create_using = check_create_using( 

290 create_using, directed=directed, multigraph=False, default=default 

291 ) 

292 if n == 1: 

293 return nx.empty_graph(n, create_using=create_using) 

294 max_edges = n * (n - 1) if directed else n * (n - 1) / 2.0 

295 if m >= max_edges: 

296 return complete_graph(n, create_using=create_using) 

297 

298 G = nx.empty_graph(n, create_using=create_using) 

299 nlist = list(G) 

300 edge_count = 0 

301 while edge_count < m: 

302 # generate random edge,u,v 

303 u = seed.choice(nlist) 

304 v = seed.choice(nlist) 

305 if u == v or G.has_edge(u, v): 

306 continue 

307 else: 

308 G.add_edge(u, v) 

309 edge_count = edge_count + 1 

310 return G 

311 

312 

313@py_random_state(3) 

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

315def newman_watts_strogatz_graph(n, k, p, seed=None, *, create_using=None): 

316 """Returns a Newman–Watts–Strogatz small-world graph. 

317 

318 Parameters 

319 ---------- 

320 n : int 

321 The number of nodes. 

322 k : int 

323 Each node is joined with its `k` nearest neighbors in a ring 

324 topology. 

325 p : float 

326 The probability of adding a new edge for each edge. 

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

328 Indicator of random number generation state. 

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

330 create_using : Graph constructor, optional (default=nx.Graph) 

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

332 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

333 

334 Notes 

335 ----- 

336 First create a ring over $n$ nodes [1]_. Then each node in the ring is 

337 connected with its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ 

338 is odd). Then shortcuts are created by adding new edges as follows: for 

339 each edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest 

340 neighbors" with probability $p$ add a new edge $(u, w)$ with 

341 randomly-chosen existing node $w$. In contrast with 

342 :func:`watts_strogatz_graph`, no edges are removed. 

343 

344 See Also 

345 -------- 

346 watts_strogatz_graph 

347 

348 References 

349 ---------- 

350 .. [1] M. E. J. Newman and D. J. Watts, 

351 Renormalization group analysis of the small-world network model, 

352 Physics Letters A, 263, 341, 1999. 

353 https://doi.org/10.1016/S0375-9601(99)00757-4 

354 """ 

355 create_using = check_create_using(create_using, directed=False, multigraph=False) 

356 if k > n: 

357 raise nx.NetworkXError("k>=n, choose smaller k or larger n") 

358 

359 # If k == n the graph return is a complete graph 

360 if k == n: 

361 return nx.complete_graph(n, create_using) 

362 

363 G = empty_graph(n, create_using) 

364 nlist = list(G.nodes()) 

365 fromv = nlist 

366 # connect the k/2 neighbors 

367 for j in range(1, k // 2 + 1): 

368 tov = fromv[j:] + fromv[0:j] # the first j are now last 

369 for i in range(len(fromv)): 

370 G.add_edge(fromv[i], tov[i]) 

371 # for each edge u-v, with probability p, randomly select existing 

372 # node w and add new edge u-w 

373 e = list(G.edges()) 

374 for u, v in e: 

375 if seed.random() < p: 

376 w = seed.choice(nlist) 

377 # no self-loops and reject if edge u-w exists 

378 # is that the correct NWS model? 

379 while w == u or G.has_edge(u, w): 

380 w = seed.choice(nlist) 

381 if G.degree(u) >= n - 1: 

382 break # skip this rewiring 

383 else: 

384 G.add_edge(u, w) 

385 return G 

386 

387 

388@py_random_state(3) 

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

390def watts_strogatz_graph(n, k, p, seed=None, *, create_using=None): 

391 """Returns a Watts–Strogatz small-world graph. 

392 

393 Parameters 

394 ---------- 

395 n : int 

396 The number of nodes 

397 k : int 

398 Each node is joined with its `k` nearest neighbors in a ring 

399 topology. 

400 p : float 

401 The probability of rewiring each edge 

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

403 Indicator of random number generation state. 

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

405 create_using : Graph constructor, optional (default=nx.Graph) 

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

407 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

408 

409 See Also 

410 -------- 

411 newman_watts_strogatz_graph 

412 connected_watts_strogatz_graph 

413 

414 Notes 

415 ----- 

416 First create a ring over $n$ nodes [1]_. Then each node in the ring is joined 

417 to its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ is odd). 

418 Then shortcuts are created by replacing some edges as follows: for each 

419 edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest neighbors" 

420 with probability $p$ replace it with a new edge $(u, w)$ with uniformly 

421 random choice of existing node $w$. 

422 

423 In contrast with :func:`newman_watts_strogatz_graph`, the random rewiring 

424 does not increase the number of edges. The rewired graph is not guaranteed 

425 to be connected as in :func:`connected_watts_strogatz_graph`. 

426 

427 References 

428 ---------- 

429 .. [1] Duncan J. Watts and Steven H. Strogatz, 

430 Collective dynamics of small-world networks, 

431 Nature, 393, pp. 440--442, 1998. 

432 """ 

433 create_using = check_create_using(create_using, directed=False, multigraph=False) 

434 if k > n: 

435 raise nx.NetworkXError("k>n, choose smaller k or larger n") 

436 

437 # If k == n, the graph is complete not Watts-Strogatz 

438 if k == n: 

439 G = nx.complete_graph(n, create_using) 

440 return G 

441 

442 G = nx.empty_graph(n, create_using=create_using) 

443 nodes = list(range(n)) # nodes are labeled 0 to n-1 

444 # connect each node to k/2 neighbors 

445 for j in range(1, k // 2 + 1): 

446 targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list 

447 G.add_edges_from(zip(nodes, targets)) 

448 # rewire edges from each node 

449 # loop over all nodes in order (label) and neighbors in order (distance) 

450 # no self loops or multiple edges allowed 

451 for j in range(1, k // 2 + 1): # outer loop is neighbors 

452 targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list 

453 # inner loop in node order 

454 for u, v in zip(nodes, targets): 

455 if seed.random() < p: 

456 w = seed.choice(nodes) 

457 # Enforce no self-loops or multiple edges 

458 while w == u or G.has_edge(u, w): 

459 w = seed.choice(nodes) 

460 if G.degree(u) >= n - 1: 

461 break # skip this rewiring 

462 else: 

463 G.remove_edge(u, v) 

464 G.add_edge(u, w) 

465 return G 

466 

467 

468@py_random_state(4) 

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

470def connected_watts_strogatz_graph(n, k, p, tries=100, seed=None, *, create_using=None): 

471 """Returns a connected Watts–Strogatz small-world graph. 

472 

473 Attempts to generate a connected graph by repeated generation of 

474 Watts–Strogatz small-world graphs. An exception is raised if the maximum 

475 number of tries is exceeded. 

476 

477 Parameters 

478 ---------- 

479 n : int 

480 The number of nodes 

481 k : int 

482 Each node is joined with its `k` nearest neighbors in a ring 

483 topology. 

484 p : float 

485 The probability of rewiring each edge 

486 tries : int 

487 Number of attempts to generate a connected graph. 

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

489 Indicator of random number generation state. 

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

491 create_using : Graph constructor, optional (default=nx.Graph) 

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

493 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

494 

495 Notes 

496 ----- 

497 First create a ring over $n$ nodes [1]_. Then each node in the ring is joined 

498 to its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ is odd). 

499 Then shortcuts are created by replacing some edges as follows: for each 

500 edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest neighbors" 

501 with probability $p$ replace it with a new edge $(u, w)$ with uniformly 

502 random choice of existing node $w$. 

503 The entire process is repeated until a connected graph results. 

504 

505 See Also 

506 -------- 

507 newman_watts_strogatz_graph 

508 watts_strogatz_graph 

509 

510 References 

511 ---------- 

512 .. [1] Duncan J. Watts and Steven H. Strogatz, 

513 Collective dynamics of small-world networks, 

514 Nature, 393, pp. 440--442, 1998. 

515 """ 

516 for i in range(tries): 

517 # seed is an RNG so should change sequence each call 

518 G = watts_strogatz_graph(n, k, p, seed, create_using=create_using) 

519 if nx.is_connected(G): 

520 return G 

521 raise nx.NetworkXError("Maximum number of tries exceeded") 

522 

523 

524@py_random_state(2) 

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

526def random_regular_graph(d, n, seed=None, *, create_using=None): 

527 r"""Returns a random $d$-regular graph on $n$ nodes. 

528 

529 A regular graph is a graph where each node has the same number of neighbors. 

530 

531 The resulting graph has no self-loops or parallel edges. 

532 

533 Parameters 

534 ---------- 

535 d : int 

536 The degree of each node. 

537 n : integer 

538 The number of nodes. The value of $n \times d$ must be even. 

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

540 Indicator of random number generation state. 

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

542 create_using : Graph constructor, optional (default=nx.Graph) 

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

544 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

545 

546 Notes 

547 ----- 

548 The nodes are numbered from $0$ to $n - 1$. 

549 

550 Kim and Vu's paper [2]_ shows that this algorithm samples in an 

551 asymptotically uniform way from the space of random graphs when 

552 $d = O(n^{1 / 3 - \epsilon})$. 

553 

554 Raises 

555 ------ 

556 

557 NetworkXError 

558 If $n \times d$ is odd or $d$ is greater than or equal to $n$. 

559 

560 References 

561 ---------- 

562 .. [1] A. Steger and N. Wormald, 

563 Generating random regular graphs quickly, 

564 Probability and Computing 8 (1999), 377-396, 1999. 

565 https://doi.org/10.1017/S0963548399003867 

566 

567 .. [2] Jeong Han Kim and Van H. Vu, 

568 Generating random regular graphs, 

569 Proceedings of the thirty-fifth ACM symposium on Theory of computing, 

570 San Diego, CA, USA, pp 213--222, 2003. 

571 http://portal.acm.org/citation.cfm?id=780542.780576 

572 """ 

573 create_using = check_create_using(create_using, directed=False, multigraph=False) 

574 if (n * d) % 2 != 0: 

575 raise nx.NetworkXError("n * d must be even") 

576 

577 if not 0 <= d < n: 

578 raise nx.NetworkXError("the 0 <= d < n inequality must be satisfied") 

579 

580 G = nx.empty_graph(n, create_using=create_using) 

581 

582 if d == 0: 

583 return G 

584 

585 def _suitable(edges, potential_edges): 

586 # Helper subroutine to check if there are suitable edges remaining 

587 # If False, the generation of the graph has failed 

588 if not potential_edges: 

589 return True 

590 for s1 in potential_edges: 

591 for s2 in potential_edges: 

592 # Two iterators on the same dictionary are guaranteed 

593 # to visit it in the same order if there are no 

594 # intervening modifications. 

595 if s1 == s2: 

596 # Only need to consider s1-s2 pair one time 

597 break 

598 if s1 > s2: 

599 s1, s2 = s2, s1 

600 if (s1, s2) not in edges: 

601 return True 

602 return False 

603 

604 def _try_creation(): 

605 # Attempt to create an edge set 

606 

607 edges = set() 

608 stubs = list(range(n)) * d 

609 

610 while stubs: 

611 potential_edges = defaultdict(lambda: 0) 

612 seed.shuffle(stubs) 

613 stubiter = iter(stubs) 

614 for s1, s2 in zip(stubiter, stubiter): 

615 if s1 > s2: 

616 s1, s2 = s2, s1 

617 if s1 != s2 and ((s1, s2) not in edges): 

618 edges.add((s1, s2)) 

619 else: 

620 potential_edges[s1] += 1 

621 potential_edges[s2] += 1 

622 

623 if not _suitable(edges, potential_edges): 

624 return None # failed to find suitable edge set 

625 

626 stubs = [ 

627 node 

628 for node, potential in potential_edges.items() 

629 for _ in range(potential) 

630 ] 

631 return edges 

632 

633 # Even though a suitable edge set exists, 

634 # the generation of such a set is not guaranteed. 

635 # Try repeatedly to find one. 

636 edges = _try_creation() 

637 while edges is None: 

638 edges = _try_creation() 

639 G.add_edges_from(edges) 

640 

641 return G 

642 

643 

644def _random_subset(seq, m, rng): 

645 """Return m unique elements from seq. 

646 

647 This differs from random.sample which can return repeated 

648 elements if seq holds repeated elements. 

649 

650 Note: rng is a random.Random or numpy.random.RandomState instance. 

651 """ 

652 targets = set() 

653 while len(targets) < m: 

654 x = rng.choice(seq) 

655 targets.add(x) 

656 return targets 

657 

658 

659@py_random_state(2) 

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

661def barabasi_albert_graph(n, m, seed=None, initial_graph=None, *, create_using=None): 

662 """Returns a random graph using Barabási–Albert preferential attachment 

663 

664 A graph of $n$ nodes is grown by attaching new nodes each with $m$ 

665 edges that are preferentially attached to existing nodes with high degree. 

666 

667 Parameters 

668 ---------- 

669 n : int 

670 Number of nodes 

671 m : int 

672 Number of edges to attach from a new node to existing nodes 

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

674 Indicator of random number generation state. 

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

676 initial_graph : Graph or None (default) 

677 Initial network for Barabási–Albert algorithm. 

678 It should be a connected graph for most use cases. 

679 A copy of `initial_graph` is used. 

680 If None, starts from a star graph on (m+1) nodes. 

681 create_using : Graph constructor, optional (default=nx.Graph) 

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

683 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

684 

685 Returns 

686 ------- 

687 G : Graph 

688 

689 Raises 

690 ------ 

691 NetworkXError 

692 If `m` does not satisfy ``1 <= m < n``, or 

693 the initial graph number of nodes m0 does not satisfy ``m <= m0 <= n``. 

694 

695 References 

696 ---------- 

697 .. [1] A. L. Barabási and R. Albert "Emergence of scaling in 

698 random networks", Science 286, pp 509-512, 1999. 

699 """ 

700 create_using = check_create_using(create_using, directed=False, multigraph=False) 

701 if m < 1 or m >= n: 

702 raise nx.NetworkXError( 

703 f"Barabási–Albert network must have m >= 1 and m < n, m = {m}, n = {n}" 

704 ) 

705 

706 if initial_graph is None: 

707 # Default initial graph : star graph on (m + 1) nodes 

708 G = star_graph(m, create_using) 

709 else: 

710 if len(initial_graph) < m or len(initial_graph) > n: 

711 raise nx.NetworkXError( 

712 f"Barabási–Albert initial graph needs between m={m} and n={n} nodes" 

713 ) 

714 G = initial_graph.copy() 

715 

716 # List of existing nodes, with nodes repeated once for each adjacent edge 

717 repeated_nodes = [n for n, d in G.degree() for _ in range(d)] 

718 # Start adding the other n - m0 nodes. 

719 source = len(G) 

720 while source < n: 

721 # Now choose m unique nodes from the existing nodes 

722 # Pick uniformly from repeated_nodes (preferential attachment) 

723 targets = _random_subset(repeated_nodes, m, seed) 

724 # Add edges to m nodes from the source. 

725 G.add_edges_from(zip([source] * m, targets)) 

726 # Add one node to the list for each new edge just created. 

727 repeated_nodes.extend(targets) 

728 # And the new node "source" has m edges to add to the list. 

729 repeated_nodes.extend([source] * m) 

730 

731 source += 1 

732 return G 

733 

734 

735@py_random_state(4) 

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

737def dual_barabasi_albert_graph( 

738 n, m1, m2, p, seed=None, initial_graph=None, *, create_using=None 

739): 

740 """Returns a random graph using dual Barabási–Albert preferential attachment 

741 

742 A graph of $n$ nodes is grown by attaching new nodes each with either $m_1$ 

743 edges (with probability $p$) or $m_2$ edges (with probability $1-p$) that 

744 are preferentially attached to existing nodes with high degree. 

745 

746 Parameters 

747 ---------- 

748 n : int 

749 Number of nodes 

750 m1 : int 

751 Number of edges to link each new node to existing nodes with probability $p$ 

752 m2 : int 

753 Number of edges to link each new node to existing nodes with probability $1-p$ 

754 p : float 

755 The probability of attaching $m_1$ edges (as opposed to $m_2$ edges) 

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

757 Indicator of random number generation state. 

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

759 initial_graph : Graph or None (default) 

760 Initial network for Barabási–Albert algorithm. 

761 A copy of `initial_graph` is used. 

762 It should be connected for most use cases. 

763 If None, starts from an star graph on max(m1, m2) + 1 nodes. 

764 create_using : Graph constructor, optional (default=nx.Graph) 

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

766 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

767 

768 Returns 

769 ------- 

770 G : Graph 

771 

772 Raises 

773 ------ 

774 NetworkXError 

775 If `m1` and `m2` do not satisfy ``1 <= m1,m2 < n``, or 

776 `p` does not satisfy ``0 <= p <= 1``, or 

777 the initial graph number of nodes m0 does not satisfy m1, m2 <= m0 <= n. 

778 

779 References 

780 ---------- 

781 .. [1] N. Moshiri "The dual-Barabasi-Albert model", arXiv:1810.10538. 

782 """ 

783 create_using = check_create_using(create_using, directed=False, multigraph=False) 

784 if m1 < 1 or m1 >= n: 

785 raise nx.NetworkXError( 

786 f"Dual Barabási–Albert must have m1 >= 1 and m1 < n, m1 = {m1}, n = {n}" 

787 ) 

788 if m2 < 1 or m2 >= n: 

789 raise nx.NetworkXError( 

790 f"Dual Barabási–Albert must have m2 >= 1 and m2 < n, m2 = {m2}, n = {n}" 

791 ) 

792 if p < 0 or p > 1: 

793 raise nx.NetworkXError( 

794 f"Dual Barabási–Albert network must have 0 <= p <= 1, p = {p}" 

795 ) 

796 

797 # For simplicity, if p == 0 or 1, just return BA 

798 if p == 1: 

799 return barabasi_albert_graph(n, m1, seed, create_using=create_using) 

800 elif p == 0: 

801 return barabasi_albert_graph(n, m2, seed, create_using=create_using) 

802 

803 if initial_graph is None: 

804 # Default initial graph : star graph on max(m1, m2) nodes 

805 G = star_graph(max(m1, m2), create_using) 

806 else: 

807 if len(initial_graph) < max(m1, m2) or len(initial_graph) > n: 

808 raise nx.NetworkXError( 

809 f"Barabási–Albert initial graph must have between " 

810 f"max(m1, m2) = {max(m1, m2)} and n = {n} nodes" 

811 ) 

812 G = initial_graph.copy() 

813 

814 # Target nodes for new edges 

815 targets = list(G) 

816 # List of existing nodes, with nodes repeated once for each adjacent edge 

817 repeated_nodes = [n for n, d in G.degree() for _ in range(d)] 

818 # Start adding the remaining nodes. 

819 source = len(G) 

820 while source < n: 

821 # Pick which m to use (m1 or m2) 

822 if seed.random() < p: 

823 m = m1 

824 else: 

825 m = m2 

826 # Now choose m unique nodes from the existing nodes 

827 # Pick uniformly from repeated_nodes (preferential attachment) 

828 targets = _random_subset(repeated_nodes, m, seed) 

829 # Add edges to m nodes from the source. 

830 G.add_edges_from(zip([source] * m, targets)) 

831 # Add one node to the list for each new edge just created. 

832 repeated_nodes.extend(targets) 

833 # And the new node "source" has m edges to add to the list. 

834 repeated_nodes.extend([source] * m) 

835 

836 source += 1 

837 return G 

838 

839 

840@py_random_state(4) 

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

842def extended_barabasi_albert_graph(n, m, p, q, seed=None, *, create_using=None): 

843 """Returns an extended Barabási–Albert model graph. 

844 

845 An extended Barabási–Albert model graph is a random graph constructed 

846 using preferential attachment. The extended model allows new edges, 

847 rewired edges or new nodes. Based on the probabilities $p$ and $q$ 

848 with $p + q < 1$, the growing behavior of the graph is determined as: 

849 

850 1) With $p$ probability, $m$ new edges are added to the graph, 

851 starting from randomly chosen existing nodes and attached preferentially at the 

852 other end. 

853 

854 2) With $q$ probability, $m$ existing edges are rewired 

855 by randomly choosing an edge and rewiring one end to a preferentially chosen node. 

856 

857 3) With $(1 - p - q)$ probability, $m$ new nodes are added to the graph 

858 with edges attached preferentially. 

859 

860 When $p = q = 0$, the model behaves just like the Barabási–Alber model. 

861 

862 Parameters 

863 ---------- 

864 n : int 

865 Number of nodes 

866 m : int 

867 Number of edges with which a new node attaches to existing nodes 

868 p : float 

869 Probability value for adding an edge between existing nodes. p + q < 1 

870 q : float 

871 Probability value of rewiring of existing edges. p + q < 1 

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

873 Indicator of random number generation state. 

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

875 create_using : Graph constructor, optional (default=nx.Graph) 

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

877 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

878 

879 Returns 

880 ------- 

881 G : Graph 

882 

883 Raises 

884 ------ 

885 NetworkXError 

886 If `m` does not satisfy ``1 <= m < n`` or ``1 >= p + q`` 

887 

888 References 

889 ---------- 

890 .. [1] Albert, R., & Barabási, A. L. (2000) 

891 Topology of evolving networks: local events and universality 

892 Physical review letters, 85(24), 5234. 

893 """ 

894 create_using = check_create_using(create_using, directed=False, multigraph=False) 

895 if m < 1 or m >= n: 

896 msg = f"Extended Barabasi-Albert network needs m>=1 and m<n, m={m}, n={n}" 

897 raise nx.NetworkXError(msg) 

898 if p + q >= 1: 

899 msg = f"Extended Barabasi-Albert network needs p + q <= 1, p={p}, q={q}" 

900 raise nx.NetworkXError(msg) 

901 

902 # Add m initial nodes (m0 in barabasi-speak) 

903 G = empty_graph(m, create_using) 

904 

905 # List of nodes to represent the preferential attachment random selection. 

906 # At the creation of the graph, all nodes are added to the list 

907 # so that even nodes that are not connected have a chance to get selected, 

908 # for rewiring and adding of edges. 

909 # With each new edge, nodes at the ends of the edge are added to the list. 

910 attachment_preference = [] 

911 attachment_preference.extend(range(m)) 

912 

913 # Start adding the other n-m nodes. The first node is m. 

914 new_node = m 

915 while new_node < n: 

916 a_probability = seed.random() 

917 

918 # Total number of edges of a Clique of all the nodes 

919 clique_degree = len(G) - 1 

920 clique_size = (len(G) * clique_degree) / 2 

921 

922 # Adding m new edges, if there is room to add them 

923 if a_probability < p and G.size() <= clique_size - m: 

924 # Select the nodes where an edge can be added 

925 eligible_nodes = [nd for nd, deg in G.degree() if deg < clique_degree] 

926 for i in range(m): 

927 # Choosing a random source node from eligible_nodes 

928 src_node = seed.choice(eligible_nodes) 

929 

930 # Picking a possible node that is not 'src_node' or 

931 # neighbor with 'src_node', with preferential attachment 

932 prohibited_nodes = list(G[src_node]) 

933 prohibited_nodes.append(src_node) 

934 # This will raise an exception if the sequence is empty 

935 dest_node = seed.choice( 

936 [nd for nd in attachment_preference if nd not in prohibited_nodes] 

937 ) 

938 # Adding the new edge 

939 G.add_edge(src_node, dest_node) 

940 

941 # Appending both nodes to add to their preferential attachment 

942 attachment_preference.append(src_node) 

943 attachment_preference.append(dest_node) 

944 

945 # Adjusting the eligible nodes. Degree may be saturated. 

946 if G.degree(src_node) == clique_degree: 

947 eligible_nodes.remove(src_node) 

948 if G.degree(dest_node) == clique_degree and dest_node in eligible_nodes: 

949 eligible_nodes.remove(dest_node) 

950 

951 # Rewiring m edges, if there are enough edges 

952 elif p <= a_probability < (p + q) and m <= G.size() < clique_size: 

953 # Selecting nodes that have at least 1 edge but that are not 

954 # fully connected to ALL other nodes (center of star). 

955 # These nodes are the pivot nodes of the edges to rewire 

956 eligible_nodes = [nd for nd, deg in G.degree() if 0 < deg < clique_degree] 

957 for i in range(m): 

958 # Choosing a random source node 

959 node = seed.choice(eligible_nodes) 

960 

961 # The available nodes do have a neighbor at least. 

962 nbr_nodes = list(G[node]) 

963 

964 # Choosing the other end that will get detached 

965 src_node = seed.choice(nbr_nodes) 

966 

967 # Picking a target node that is not 'node' or 

968 # neighbor with 'node', with preferential attachment 

969 nbr_nodes.append(node) 

970 dest_node = seed.choice( 

971 [nd for nd in attachment_preference if nd not in nbr_nodes] 

972 ) 

973 # Rewire 

974 G.remove_edge(node, src_node) 

975 G.add_edge(node, dest_node) 

976 

977 # Adjusting the preferential attachment list 

978 attachment_preference.remove(src_node) 

979 attachment_preference.append(dest_node) 

980 

981 # Adjusting the eligible nodes. 

982 # nodes may be saturated or isolated. 

983 if G.degree(src_node) == 0 and src_node in eligible_nodes: 

984 eligible_nodes.remove(src_node) 

985 if dest_node in eligible_nodes: 

986 if G.degree(dest_node) == clique_degree: 

987 eligible_nodes.remove(dest_node) 

988 else: 

989 if G.degree(dest_node) == 1: 

990 eligible_nodes.append(dest_node) 

991 

992 # Adding new node with m edges 

993 else: 

994 # Select the edges' nodes by preferential attachment 

995 targets = _random_subset(attachment_preference, m, seed) 

996 G.add_edges_from(zip([new_node] * m, targets)) 

997 

998 # Add one node to the list for each new edge just created. 

999 attachment_preference.extend(targets) 

1000 # The new node has m edges to it, plus itself: m + 1 

1001 attachment_preference.extend([new_node] * (m + 1)) 

1002 new_node += 1 

1003 return G 

1004 

1005 

1006@py_random_state(3) 

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

1008def powerlaw_cluster_graph(n, m, p, seed=None, *, create_using=None): 

1009 """Holme and Kim algorithm for growing graphs with powerlaw 

1010 degree distribution and approximate average clustering. 

1011 

1012 Parameters 

1013 ---------- 

1014 n : int 

1015 the number of nodes 

1016 m : int 

1017 the number of random edges to add for each new node 

1018 p : float, 

1019 Probability of adding a triangle after adding a random edge 

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

1021 Indicator of random number generation state. 

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

1023 create_using : Graph constructor, optional (default=nx.Graph) 

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

1025 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

1026 

1027 Notes 

1028 ----- 

1029 The average clustering has a hard time getting above a certain 

1030 cutoff that depends on `m`. This cutoff is often quite low. The 

1031 transitivity (fraction of triangles to possible triangles) seems to 

1032 decrease with network size. 

1033 

1034 It is essentially the Barabási–Albert (BA) growth model with an 

1035 extra step that each random edge is followed by a chance of 

1036 making an edge to one of its neighbors too (and thus a triangle). 

1037 

1038 This algorithm improves on BA in the sense that it enables a 

1039 higher average clustering to be attained if desired. 

1040 

1041 It seems possible to have a disconnected graph with this algorithm 

1042 since the initial `m` nodes may not be all linked to a new node 

1043 on the first iteration like the BA model. 

1044 

1045 Raises 

1046 ------ 

1047 NetworkXError 

1048 If `m` does not satisfy ``1 <= m <= n`` or `p` does not 

1049 satisfy ``0 <= p <= 1``. 

1050 

1051 References 

1052 ---------- 

1053 .. [1] P. Holme and B. J. Kim, 

1054 "Growing scale-free networks with tunable clustering", 

1055 Phys. Rev. E, 65, 026107, 2002. 

1056 """ 

1057 create_using = check_create_using(create_using, directed=False, multigraph=False) 

1058 if m < 1 or n < m: 

1059 raise nx.NetworkXError(f"NetworkXError must have m>1 and m<n, m={m},n={n}") 

1060 

1061 if p > 1 or p < 0: 

1062 raise nx.NetworkXError(f"NetworkXError p must be in [0,1], p={p}") 

1063 

1064 G = empty_graph(m, create_using) # add m initial nodes (m0 in barabasi-speak) 

1065 repeated_nodes = list(G) # list of existing nodes to sample from 

1066 # with nodes repeated once for each adjacent edge 

1067 source = m # next node is m 

1068 while source < n: # Now add the other n-1 nodes 

1069 possible_targets = _random_subset(repeated_nodes, m, seed) 

1070 # do one preferential attachment for new node 

1071 target = possible_targets.pop() 

1072 G.add_edge(source, target) 

1073 repeated_nodes.append(target) # add one node to list for each new link 

1074 count = 1 

1075 while count < m: # add m-1 more new links 

1076 if seed.random() < p: # clustering step: add triangle 

1077 neighborhood = [ 

1078 nbr 

1079 for nbr in G.neighbors(target) 

1080 if not G.has_edge(source, nbr) and nbr != source 

1081 ] 

1082 if neighborhood: # if there is a neighbor without a link 

1083 nbr = seed.choice(neighborhood) 

1084 G.add_edge(source, nbr) # add triangle 

1085 repeated_nodes.append(nbr) 

1086 count = count + 1 

1087 continue # go to top of while loop 

1088 # else do preferential attachment step if above fails 

1089 target = possible_targets.pop() 

1090 G.add_edge(source, target) 

1091 repeated_nodes.append(target) 

1092 count = count + 1 

1093 

1094 repeated_nodes.extend([source] * m) # add source node to list m times 

1095 source += 1 

1096 return G 

1097 

1098 

1099@py_random_state(3) 

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

1101def random_lobster_graph(n, p1, p2, seed=None, *, create_using=None): 

1102 """Returns a random lobster graph. 

1103 

1104 A lobster is a tree that reduces to a caterpillar when pruning all 

1105 leaf nodes. A caterpillar is a tree that reduces to a path graph 

1106 when pruning all leaf nodes; setting `p2` to zero produces a caterpillar. 

1107 

1108 This implementation iterates on the probabilities `p1` and `p2` to add 

1109 edges at levels 1 and 2, respectively. Graphs are therefore constructed 

1110 iteratively with uniform randomness at each level rather than being selected 

1111 uniformly at random from the set of all possible lobsters. 

1112 

1113 Parameters 

1114 ---------- 

1115 n : int 

1116 The expected number of nodes in the backbone 

1117 p1 : float 

1118 Probability of adding an edge to the backbone 

1119 p2 : float 

1120 Probability of adding an edge one level beyond backbone 

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

1122 Indicator of random number generation state. 

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

1124 create_using : Graph constructor, optional (default=nx.Graph) 

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

1126 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

1127 

1128 Raises 

1129 ------ 

1130 NetworkXError 

1131 If `p1` or `p2` parameters are >= 1 because the while loops would never finish. 

1132 """ 

1133 create_using = check_create_using(create_using, directed=False, multigraph=False) 

1134 p1, p2 = abs(p1), abs(p2) 

1135 if any(p >= 1 for p in [p1, p2]): 

1136 raise nx.NetworkXError("Probability values for `p1` and `p2` must both be < 1.") 

1137 

1138 # a necessary ingredient in any self-respecting graph library 

1139 llen = int(2 * seed.random() * n + 0.5) 

1140 L = path_graph(llen, create_using) 

1141 # build caterpillar: add edges to path graph with probability p1 

1142 current_node = llen - 1 

1143 for n in range(llen): 

1144 while seed.random() < p1: # add fuzzy caterpillar parts 

1145 current_node += 1 

1146 L.add_edge(n, current_node) 

1147 cat_node = current_node 

1148 while seed.random() < p2: # add crunchy lobster bits 

1149 current_node += 1 

1150 L.add_edge(cat_node, current_node) 

1151 return L # voila, un lobster! 

1152 

1153 

1154@py_random_state(3) 

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

1156def random_lobster(n, p1, p2, seed=None, *, create_using=None): 

1157 """ 

1158 .. deprecated:: 3.5 

1159 `random_lobster` is a deprecated alias 

1160 for `random_lobster_graph`. 

1161 Use `random_lobster_graph` instead. 

1162 """ 

1163 import warnings 

1164 

1165 warnings.warn( 

1166 "`random_lobster` is deprecated, use `random_lobster_graph` instead.", 

1167 category=DeprecationWarning, 

1168 stacklevel=2, 

1169 ) 

1170 return random_lobster_graph(n, p1, p2, seed=seed, create_using=create_using) 

1171 

1172 

1173@py_random_state(1) 

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

1175def random_shell_graph(constructor, seed=None, *, create_using=None): 

1176 """Returns a random shell graph for the constructor given. 

1177 

1178 Parameters 

1179 ---------- 

1180 constructor : list of three-tuples 

1181 Represents the parameters for a shell, starting at the center 

1182 shell. Each element of the list must be of the form `(n, m, 

1183 d)`, where `n` is the number of nodes in the shell, `m` is 

1184 the number of edges in the shell, and `d` is the ratio of 

1185 inter-shell (next) edges to intra-shell edges. If `d` is zero, 

1186 there will be no intra-shell edges, and if `d` is one there 

1187 will be all possible intra-shell edges. 

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

1189 Indicator of random number generation state. 

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

1191 create_using : Graph constructor, optional (default=nx.Graph) 

1192 Graph type to create. Graph instances are not supported. 

1193 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

1194 

1195 Examples 

1196 -------- 

1197 >>> constructor = [(10, 20, 0.8), (20, 40, 0.8)] 

1198 >>> G = nx.random_shell_graph(constructor) 

1199 

1200 """ 

1201 create_using = check_create_using(create_using, directed=False, multigraph=False) 

1202 G = empty_graph(0, create_using) 

1203 

1204 glist = [] 

1205 intra_edges = [] 

1206 nnodes = 0 

1207 # create gnm graphs for each shell 

1208 for n, m, d in constructor: 

1209 inter_edges = int(m * d) 

1210 intra_edges.append(m - inter_edges) 

1211 g = nx.convert_node_labels_to_integers( 

1212 gnm_random_graph(n, inter_edges, seed=seed, create_using=G.__class__), 

1213 first_label=nnodes, 

1214 ) 

1215 glist.append(g) 

1216 nnodes += n 

1217 G = nx.operators.union(G, g) 

1218 

1219 # connect the shells randomly 

1220 for gi in range(len(glist) - 1): 

1221 nlist1 = list(glist[gi]) 

1222 nlist2 = list(glist[gi + 1]) 

1223 total_edges = intra_edges[gi] 

1224 edge_count = 0 

1225 while edge_count < total_edges: 

1226 u = seed.choice(nlist1) 

1227 v = seed.choice(nlist2) 

1228 if u == v or G.has_edge(u, v): 

1229 continue 

1230 else: 

1231 G.add_edge(u, v) 

1232 edge_count = edge_count + 1 

1233 return G 

1234 

1235 

1236@py_random_state(2) 

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

1238def random_powerlaw_tree(n, gamma=3, seed=None, tries=100, *, create_using=None): 

1239 """Returns a tree with a power law degree distribution. 

1240 

1241 Parameters 

1242 ---------- 

1243 n : int 

1244 The number of nodes. 

1245 gamma : float 

1246 Exponent of the power law. 

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

1248 Indicator of random number generation state. 

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

1250 tries : int 

1251 Number of attempts to adjust the sequence to make it a tree. 

1252 create_using : Graph constructor, optional (default=nx.Graph) 

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

1254 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

1255 

1256 Raises 

1257 ------ 

1258 NetworkXError 

1259 If no valid sequence is found within the maximum number of 

1260 attempts. 

1261 

1262 Notes 

1263 ----- 

1264 A trial power law degree sequence is chosen and then elements are 

1265 swapped with new elements from a powerlaw distribution until the 

1266 sequence makes a tree (by checking, for example, that the number of 

1267 edges is one smaller than the number of nodes). 

1268 

1269 """ 

1270 create_using = check_create_using(create_using, directed=False, multigraph=False) 

1271 # This call may raise a NetworkXError if the number of tries is succeeded. 

1272 seq = random_powerlaw_tree_sequence(n, gamma=gamma, seed=seed, tries=tries) 

1273 G = degree_sequence_tree(seq, create_using) 

1274 return G 

1275 

1276 

1277@py_random_state(2) 

1278@nx._dispatchable(graphs=None) 

1279def random_powerlaw_tree_sequence(n, gamma=3, seed=None, tries=100): 

1280 """Returns a degree sequence for a tree with a power law distribution. 

1281 

1282 Parameters 

1283 ---------- 

1284 n : int, 

1285 The number of nodes. 

1286 gamma : float 

1287 Exponent of the power law. 

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

1289 Indicator of random number generation state. 

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

1291 tries : int 

1292 Number of attempts to adjust the sequence to make it a tree. 

1293 

1294 Raises 

1295 ------ 

1296 NetworkXError 

1297 If no valid sequence is found within the maximum number of 

1298 attempts. 

1299 

1300 Notes 

1301 ----- 

1302 A trial power law degree sequence is chosen and then elements are 

1303 swapped with new elements from a power law distribution until 

1304 the sequence makes a tree (by checking, for example, that the number of 

1305 edges is one smaller than the number of nodes). 

1306 

1307 """ 

1308 # get trial sequence 

1309 z = nx.utils.powerlaw_sequence(n, exponent=gamma, seed=seed) 

1310 # round to integer values in the range [0,n] 

1311 zseq = [min(n, max(round(s), 0)) for s in z] 

1312 

1313 # another sequence to swap values from 

1314 z = nx.utils.powerlaw_sequence(tries, exponent=gamma, seed=seed) 

1315 # round to integer values in the range [0,n] 

1316 swap = [min(n, max(round(s), 0)) for s in z] 

1317 

1318 for _ in swap: 

1319 valid, _ = nx.utils.is_valid_tree_degree_sequence(zseq) 

1320 if valid: 

1321 return zseq 

1322 index = seed.randint(0, n - 1) 

1323 zseq[index] = swap.pop() 

1324 

1325 raise nx.NetworkXError( 

1326 f"Exceeded max ({tries}) attempts for a valid tree sequence." 

1327 ) 

1328 

1329 

1330@py_random_state(3) 

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

1332def random_kernel_graph( 

1333 n, kernel_integral, kernel_root=None, seed=None, *, create_using=None 

1334): 

1335 r"""Returns an random graph based on the specified kernel. 

1336 

1337 The algorithm chooses each of the $[n(n-1)]/2$ possible edges with 

1338 probability specified by a kernel $\kappa(x,y)$ [1]_. The kernel 

1339 $\kappa(x,y)$ must be a symmetric (in $x,y$), non-negative, 

1340 bounded function. 

1341 

1342 Parameters 

1343 ---------- 

1344 n : int 

1345 The number of nodes 

1346 kernel_integral : function 

1347 Function that returns the definite integral of the kernel $\kappa(x,y)$, 

1348 $F(y,a,b) := \int_a^b \kappa(x,y)dx$ 

1349 kernel_root: function (optional) 

1350 Function that returns the root $b$ of the equation $F(y,a,b) = r$. 

1351 If None, the root is found using :func:`scipy.optimize.brentq` 

1352 (this requires SciPy). 

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

1354 Indicator of random number generation state. 

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

1356 create_using : Graph constructor, optional (default=nx.Graph) 

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

1358 Multigraph and directed types are not supported and raise a ``NetworkXError``. 

1359 

1360 Notes 

1361 ----- 

1362 The kernel is specified through its definite integral which must be 

1363 provided as one of the arguments. If the integral and root of the 

1364 kernel integral can be found in $O(1)$ time then this algorithm runs in 

1365 time $O(n+m)$ where m is the expected number of edges [2]_. 

1366 

1367 The nodes are set to integers from $0$ to $n-1$. 

1368 

1369 Examples 

1370 -------- 

1371 Generate an Erdős–Rényi random graph $G(n,c/n)$, with kernel 

1372 $\kappa(x,y)=c$ where $c$ is the mean expected degree. 

1373 

1374 >>> def integral(u, w, z): 

1375 ... return c * (z - w) 

1376 >>> def root(u, w, r): 

1377 ... return r / c + w 

1378 >>> c = 1 

1379 >>> graph = nx.random_kernel_graph(1000, integral, root) 

1380 

1381 See Also 

1382 -------- 

1383 gnp_random_graph 

1384 expected_degree_graph 

1385 

1386 References 

1387 ---------- 

1388 .. [1] Bollobás, Béla, Janson, S. and Riordan, O. 

1389 "The phase transition in inhomogeneous random graphs", 

1390 *Random Structures Algorithms*, 31, 3--122, 2007. 

1391 

1392 .. [2] Hagberg A, Lemons N (2015), 

1393 "Fast Generation of Sparse Random Kernel Graphs". 

1394 PLoS ONE 10(9): e0135177, 2015. doi:10.1371/journal.pone.0135177 

1395 """ 

1396 create_using = check_create_using(create_using, directed=False, multigraph=False) 

1397 if kernel_root is None: 

1398 import scipy as sp 

1399 

1400 def kernel_root(y, a, r): 

1401 def my_function(b): 

1402 return kernel_integral(y, a, b) - r 

1403 

1404 return sp.optimize.brentq(my_function, a, 1) 

1405 

1406 graph = nx.empty_graph(create_using=create_using) 

1407 graph.add_nodes_from(range(n)) 

1408 (i, j) = (1, 1) 

1409 while i < n: 

1410 r = -math.log(1 - seed.random()) # (1-seed.random()) in (0, 1] 

1411 if kernel_integral(i / n, j / n, 1) <= r: 

1412 i, j = i + 1, i + 1 

1413 else: 

1414 j = math.ceil(n * kernel_root(i / n, j / n, r)) 

1415 graph.add_edge(i - 1, j - 1) 

1416 return graph