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

432 statements  

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

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 .classic import complete_graph, empty_graph, path_graph, star_graph 

14from .degree_seq import degree_sequence_tree 

15 

16__all__ = [ 

17 "fast_gnp_random_graph", 

18 "gnp_random_graph", 

19 "dense_gnm_random_graph", 

20 "gnm_random_graph", 

21 "erdos_renyi_graph", 

22 "binomial_graph", 

23 "newman_watts_strogatz_graph", 

24 "watts_strogatz_graph", 

25 "connected_watts_strogatz_graph", 

26 "random_regular_graph", 

27 "barabasi_albert_graph", 

28 "dual_barabasi_albert_graph", 

29 "extended_barabasi_albert_graph", 

30 "powerlaw_cluster_graph", 

31 "random_lobster", 

32 "random_shell_graph", 

33 "random_powerlaw_tree", 

34 "random_powerlaw_tree_sequence", 

35 "random_kernel_graph", 

36] 

37 

38 

39@py_random_state(2) 

40@nx._dispatch(graphs=None) 

41def fast_gnp_random_graph(n, p, seed=None, directed=False): 

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

43 a binomial graph. 

44 

45 Parameters 

46 ---------- 

47 n : int 

48 The number of nodes. 

49 p : float 

50 Probability for edge creation. 

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

52 Indicator of random number generation state. 

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

54 directed : bool, optional (default=False) 

55 If True, this function returns a directed graph. 

56 

57 Notes 

58 ----- 

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

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

61 

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

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

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

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

66 

67 See Also 

68 -------- 

69 gnp_random_graph 

70 

71 References 

72 ---------- 

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

74 "Efficient generation of large random networks", 

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

76 """ 

77 G = empty_graph(n) 

78 

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

80 return nx.gnp_random_graph(n, p, seed=seed, directed=directed) 

81 

82 lp = math.log(1.0 - p) 

83 

84 if directed: 

85 G = nx.DiGraph(G) 

86 v = 1 

87 w = -1 

88 while v < n: 

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

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

91 while w >= v and v < n: 

92 w = w - v 

93 v = v + 1 

94 if v < n: 

95 G.add_edge(w, v) 

96 

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

98 v = 1 

99 w = -1 

100 while v < n: 

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

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

103 while w >= v and v < n: 

104 w = w - v 

105 v = v + 1 

106 if v < n: 

107 G.add_edge(v, w) 

108 return G 

109 

110 

111@py_random_state(2) 

112@nx._dispatch(graphs=None) 

113def gnp_random_graph(n, p, seed=None, directed=False): 

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

115 or a binomial graph. 

116 

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

118 

119 Parameters 

120 ---------- 

121 n : int 

122 The number of nodes. 

123 p : float 

124 Probability for edge creation. 

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

126 Indicator of random number generation state. 

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

128 directed : bool, optional (default=False) 

129 If True, this function returns a directed graph. 

130 

131 See Also 

132 -------- 

133 fast_gnp_random_graph 

134 

135 Notes 

136 ----- 

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

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

139 

140 :func:`binomial_graph` and :func:`erdos_renyi_graph` are 

141 aliases for :func:`gnp_random_graph`. 

142 

143 >>> nx.binomial_graph is nx.gnp_random_graph 

144 True 

145 >>> nx.erdos_renyi_graph is nx.gnp_random_graph 

146 True 

147 

148 References 

149 ---------- 

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

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

152 """ 

153 if directed: 

154 edges = itertools.permutations(range(n), 2) 

155 G = nx.DiGraph() 

156 else: 

157 edges = itertools.combinations(range(n), 2) 

158 G = nx.Graph() 

159 G.add_nodes_from(range(n)) 

160 if p <= 0: 

161 return G 

162 if p >= 1: 

163 return complete_graph(n, create_using=G) 

164 

165 for e in edges: 

166 if seed.random() < p: 

167 G.add_edge(*e) 

168 return G 

169 

170 

171# add some aliases to common names 

172binomial_graph = gnp_random_graph 

173erdos_renyi_graph = gnp_random_graph 

174 

175 

176@py_random_state(2) 

177@nx._dispatch(graphs=None) 

178def dense_gnm_random_graph(n, m, seed=None): 

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

180 

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

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

183 

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

185 graphs. 

186 

187 Parameters 

188 ---------- 

189 n : int 

190 The number of nodes. 

191 m : int 

192 The number of edges. 

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

194 Indicator of random number generation state. 

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

196 

197 See Also 

198 -------- 

199 gnm_random_graph 

200 

201 Notes 

202 ----- 

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

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

205 in section 3.4.2 of [1]_. 

206 

207 References 

208 ---------- 

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

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

211 """ 

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

213 if m >= mmax: 

214 G = complete_graph(n) 

215 else: 

216 G = empty_graph(n) 

217 

218 if n == 1 or m >= mmax: 

219 return G 

220 

221 u = 0 

222 v = 1 

223 t = 0 

224 k = 0 

225 while True: 

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

227 G.add_edge(u, v) 

228 k += 1 

229 if k == m: 

230 return G 

231 t += 1 

232 v += 1 

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

234 u += 1 

235 v = u + 1 

236 

237 

238@py_random_state(2) 

239@nx._dispatch(graphs=None) 

240def gnm_random_graph(n, m, seed=None, directed=False): 

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

242 

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

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

245 

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

247 sparse graphs. 

248 

249 Parameters 

250 ---------- 

251 n : int 

252 The number of nodes. 

253 m : int 

254 The number of edges. 

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

256 Indicator of random number generation state. 

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

258 directed : bool, optional (default=False) 

259 If True return a directed graph 

260 

261 See also 

262 -------- 

263 dense_gnm_random_graph 

264 

265 """ 

266 if directed: 

267 G = nx.DiGraph() 

268 else: 

269 G = nx.Graph() 

270 G.add_nodes_from(range(n)) 

271 

272 if n == 1: 

273 return G 

274 max_edges = n * (n - 1) 

275 if not directed: 

276 max_edges /= 2.0 

277 if m >= max_edges: 

278 return complete_graph(n, create_using=G) 

279 

280 nlist = list(G) 

281 edge_count = 0 

282 while edge_count < m: 

283 # generate random edge,u,v 

284 u = seed.choice(nlist) 

285 v = seed.choice(nlist) 

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

287 continue 

288 else: 

289 G.add_edge(u, v) 

290 edge_count = edge_count + 1 

291 return G 

292 

293 

294@py_random_state(3) 

295@nx._dispatch(graphs=None) 

296def newman_watts_strogatz_graph(n, k, p, seed=None): 

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

298 

299 Parameters 

300 ---------- 

301 n : int 

302 The number of nodes. 

303 k : int 

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

305 topology. 

306 p : float 

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

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

309 Indicator of random number generation state. 

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

311 

312 Notes 

313 ----- 

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

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

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

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

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

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

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

321 

322 See Also 

323 -------- 

324 watts_strogatz_graph 

325 

326 References 

327 ---------- 

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

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

330 Physics Letters A, 263, 341, 1999. 

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

332 """ 

333 if k > n: 

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

335 

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

337 if k == n: 

338 return nx.complete_graph(n) 

339 

340 G = empty_graph(n) 

341 nlist = list(G.nodes()) 

342 fromv = nlist 

343 # connect the k/2 neighbors 

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

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

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

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

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

349 # node w and add new edge u-w 

350 e = list(G.edges()) 

351 for u, v in e: 

352 if seed.random() < p: 

353 w = seed.choice(nlist) 

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

355 # is that the correct NWS model? 

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

357 w = seed.choice(nlist) 

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

359 break # skip this rewiring 

360 else: 

361 G.add_edge(u, w) 

362 return G 

363 

364 

365@py_random_state(3) 

366@nx._dispatch(graphs=None) 

367def watts_strogatz_graph(n, k, p, seed=None): 

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

369 

370 Parameters 

371 ---------- 

372 n : int 

373 The number of nodes 

374 k : int 

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

376 topology. 

377 p : float 

378 The probability of rewiring each edge 

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

380 Indicator of random number generation state. 

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

382 

383 See Also 

384 -------- 

385 newman_watts_strogatz_graph 

386 connected_watts_strogatz_graph 

387 

388 Notes 

389 ----- 

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

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

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

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

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

395 random choice of existing node $w$. 

396 

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

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

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

400 

401 References 

402 ---------- 

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

404 Collective dynamics of small-world networks, 

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

406 """ 

407 if k > n: 

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

409 

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

411 if k == n: 

412 return nx.complete_graph(n) 

413 

414 G = nx.Graph() 

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

416 # connect each node to k/2 neighbors 

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

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

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

420 # rewire edges from each node 

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

422 # no self loops or multiple edges allowed 

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

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

425 # inner loop in node order 

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

427 if seed.random() < p: 

428 w = seed.choice(nodes) 

429 # Enforce no self-loops or multiple edges 

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

431 w = seed.choice(nodes) 

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

433 break # skip this rewiring 

434 else: 

435 G.remove_edge(u, v) 

436 G.add_edge(u, w) 

437 return G 

438 

439 

440@py_random_state(4) 

441@nx._dispatch(graphs=None) 

442def connected_watts_strogatz_graph(n, k, p, tries=100, seed=None): 

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

444 

445 Attempts to generate a connected graph by repeated generation of 

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

447 number of tries is exceeded. 

448 

449 Parameters 

450 ---------- 

451 n : int 

452 The number of nodes 

453 k : int 

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

455 topology. 

456 p : float 

457 The probability of rewiring each edge 

458 tries : int 

459 Number of attempts to generate a connected graph. 

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

461 Indicator of random number generation state. 

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

463 

464 Notes 

465 ----- 

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

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

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

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

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

471 random choice of existing node $w$. 

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

473 

474 See Also 

475 -------- 

476 newman_watts_strogatz_graph 

477 watts_strogatz_graph 

478 

479 References 

480 ---------- 

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

482 Collective dynamics of small-world networks, 

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

484 """ 

485 for i in range(tries): 

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

487 G = watts_strogatz_graph(n, k, p, seed) 

488 if nx.is_connected(G): 

489 return G 

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

491 

492 

493@py_random_state(2) 

494@nx._dispatch(graphs=None) 

495def random_regular_graph(d, n, seed=None): 

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

497 

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

499 

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

501 

502 Parameters 

503 ---------- 

504 d : int 

505 The degree of each node. 

506 n : integer 

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

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

509 Indicator of random number generation state. 

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

511 

512 Notes 

513 ----- 

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

515 

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

517 asymptotically uniform way from the space of random graphs when 

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

519 

520 Raises 

521 ------ 

522 

523 NetworkXError 

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

525 

526 References 

527 ---------- 

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

529 Generating random regular graphs quickly, 

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

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

532 

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

534 Generating random regular graphs, 

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

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

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

538 """ 

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

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

541 

542 if not 0 <= d < n: 

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

544 

545 if d == 0: 

546 return empty_graph(n) 

547 

548 def _suitable(edges, potential_edges): 

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

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

551 if not potential_edges: 

552 return True 

553 for s1 in potential_edges: 

554 for s2 in potential_edges: 

555 # Two iterators on the same dictionary are guaranteed 

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

557 # intervening modifications. 

558 if s1 == s2: 

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

560 break 

561 if s1 > s2: 

562 s1, s2 = s2, s1 

563 if (s1, s2) not in edges: 

564 return True 

565 return False 

566 

567 def _try_creation(): 

568 # Attempt to create an edge set 

569 

570 edges = set() 

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

572 

573 while stubs: 

574 potential_edges = defaultdict(lambda: 0) 

575 seed.shuffle(stubs) 

576 stubiter = iter(stubs) 

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

578 if s1 > s2: 

579 s1, s2 = s2, s1 

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

581 edges.add((s1, s2)) 

582 else: 

583 potential_edges[s1] += 1 

584 potential_edges[s2] += 1 

585 

586 if not _suitable(edges, potential_edges): 

587 return None # failed to find suitable edge set 

588 

589 stubs = [ 

590 node 

591 for node, potential in potential_edges.items() 

592 for _ in range(potential) 

593 ] 

594 return edges 

595 

596 # Even though a suitable edge set exists, 

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

598 # Try repeatedly to find one. 

599 edges = _try_creation() 

600 while edges is None: 

601 edges = _try_creation() 

602 

603 G = nx.Graph() 

604 G.add_edges_from(edges) 

605 

606 return G 

607 

608 

609def _random_subset(seq, m, rng): 

610 """Return m unique elements from seq. 

611 

612 This differs from random.sample which can return repeated 

613 elements if seq holds repeated elements. 

614 

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

616 """ 

617 targets = set() 

618 while len(targets) < m: 

619 x = rng.choice(seq) 

620 targets.add(x) 

621 return targets 

622 

623 

624@py_random_state(2) 

625@nx._dispatch(graphs=None) 

626def barabasi_albert_graph(n, m, seed=None, initial_graph=None): 

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

628 

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

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

631 

632 Parameters 

633 ---------- 

634 n : int 

635 Number of nodes 

636 m : int 

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

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

639 Indicator of random number generation state. 

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

641 initial_graph : Graph or None (default) 

642 Initial network for Barabási–Albert algorithm. 

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

644 A copy of `initial_graph` is used. 

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

646 

647 Returns 

648 ------- 

649 G : Graph 

650 

651 Raises 

652 ------ 

653 NetworkXError 

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

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

656 

657 References 

658 ---------- 

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

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

661 """ 

662 

663 if m < 1 or m >= n: 

664 raise nx.NetworkXError( 

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

666 ) 

667 

668 if initial_graph is None: 

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

670 G = star_graph(m) 

671 else: 

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

673 raise nx.NetworkXError( 

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

675 ) 

676 G = initial_graph.copy() 

677 

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

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

680 # Start adding the other n - m0 nodes. 

681 source = len(G) 

682 while source < n: 

683 # Now choose m unique nodes from the existing nodes 

684 # Pick uniformly from repeated_nodes (preferential attachment) 

685 targets = _random_subset(repeated_nodes, m, seed) 

686 # Add edges to m nodes from the source. 

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

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

689 repeated_nodes.extend(targets) 

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

691 repeated_nodes.extend([source] * m) 

692 

693 source += 1 

694 return G 

695 

696 

697@py_random_state(4) 

698@nx._dispatch(graphs=None) 

699def dual_barabasi_albert_graph(n, m1, m2, p, seed=None, initial_graph=None): 

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

701 

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

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

704 are preferentially attached to existing nodes with high degree. 

705 

706 Parameters 

707 ---------- 

708 n : int 

709 Number of nodes 

710 m1 : int 

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

712 m2 : int 

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

714 p : float 

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

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

717 Indicator of random number generation state. 

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

719 initial_graph : Graph or None (default) 

720 Initial network for Barabási–Albert algorithm. 

721 A copy of `initial_graph` is used. 

722 It should be connected for most use cases. 

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

724 

725 Returns 

726 ------- 

727 G : Graph 

728 

729 Raises 

730 ------ 

731 NetworkXError 

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

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

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

735 

736 References 

737 ---------- 

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

739 """ 

740 

741 if m1 < 1 or m1 >= n: 

742 raise nx.NetworkXError( 

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

744 ) 

745 if m2 < 1 or m2 >= n: 

746 raise nx.NetworkXError( 

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

748 ) 

749 if p < 0 or p > 1: 

750 raise nx.NetworkXError( 

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

752 ) 

753 

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

755 if p == 1: 

756 return barabasi_albert_graph(n, m1, seed) 

757 elif p == 0: 

758 return barabasi_albert_graph(n, m2, seed) 

759 

760 if initial_graph is None: 

761 # Default initial graph : empty graph on max(m1, m2) nodes 

762 G = star_graph(max(m1, m2)) 

763 else: 

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

765 raise nx.NetworkXError( 

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

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

768 ) 

769 G = initial_graph.copy() 

770 

771 # Target nodes for new edges 

772 targets = list(G) 

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

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

775 # Start adding the remaining nodes. 

776 source = len(G) 

777 while source < n: 

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

779 if seed.random() < p: 

780 m = m1 

781 else: 

782 m = m2 

783 # Now choose m unique nodes from the existing nodes 

784 # Pick uniformly from repeated_nodes (preferential attachment) 

785 targets = _random_subset(repeated_nodes, m, seed) 

786 # Add edges to m nodes from the source. 

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

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

789 repeated_nodes.extend(targets) 

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

791 repeated_nodes.extend([source] * m) 

792 

793 source += 1 

794 return G 

795 

796 

797@py_random_state(4) 

798@nx._dispatch(graphs=None) 

799def extended_barabasi_albert_graph(n, m, p, q, seed=None): 

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

801 

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

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

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

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

806 

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

808 starting from randomly chosen existing nodes and attached preferentially at the other end. 

809 

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

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

812 

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

814 with edges attached preferentially. 

815 

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

817 

818 Parameters 

819 ---------- 

820 n : int 

821 Number of nodes 

822 m : int 

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

824 p : float 

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

826 q : float 

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

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

829 Indicator of random number generation state. 

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

831 

832 Returns 

833 ------- 

834 G : Graph 

835 

836 Raises 

837 ------ 

838 NetworkXError 

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

840 

841 References 

842 ---------- 

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

844 Topology of evolving networks: local events and universality 

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

846 """ 

847 if m < 1 or m >= n: 

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

849 raise nx.NetworkXError(msg) 

850 if p + q >= 1: 

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

852 raise nx.NetworkXError(msg) 

853 

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

855 G = empty_graph(m) 

856 

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

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

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

860 # for rewiring and adding of edges. 

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

862 attachment_preference = [] 

863 attachment_preference.extend(range(m)) 

864 

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

866 new_node = m 

867 while new_node < n: 

868 a_probability = seed.random() 

869 

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

871 clique_degree = len(G) - 1 

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

873 

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

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

876 # Select the nodes where an edge can be added 

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

878 for i in range(m): 

879 # Choosing a random source node from eligible_nodes 

880 src_node = seed.choice(eligible_nodes) 

881 

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

883 # neighbor with 'src_node', with preferential attachment 

884 prohibited_nodes = list(G[src_node]) 

885 prohibited_nodes.append(src_node) 

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

887 dest_node = seed.choice( 

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

889 ) 

890 # Adding the new edge 

891 G.add_edge(src_node, dest_node) 

892 

893 # Appending both nodes to add to their preferential attachment 

894 attachment_preference.append(src_node) 

895 attachment_preference.append(dest_node) 

896 

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

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

899 eligible_nodes.remove(src_node) 

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

901 eligible_nodes.remove(dest_node) 

902 

903 # Rewiring m edges, if there are enough edges 

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

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

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

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

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

909 for i in range(m): 

910 # Choosing a random source node 

911 node = seed.choice(eligible_nodes) 

912 

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

914 neighbor_nodes = list(G[node]) 

915 

916 # Choosing the other end that will get detached 

917 src_node = seed.choice(neighbor_nodes) 

918 

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

920 # neighbor with 'node', with preferential attachment 

921 neighbor_nodes.append(node) 

922 dest_node = seed.choice( 

923 [nd for nd in attachment_preference if nd not in neighbor_nodes] 

924 ) 

925 # Rewire 

926 G.remove_edge(node, src_node) 

927 G.add_edge(node, dest_node) 

928 

929 # Adjusting the preferential attachment list 

930 attachment_preference.remove(src_node) 

931 attachment_preference.append(dest_node) 

932 

933 # Adjusting the eligible nodes. 

934 # nodes may be saturated or isolated. 

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

936 eligible_nodes.remove(src_node) 

937 if dest_node in eligible_nodes: 

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

939 eligible_nodes.remove(dest_node) 

940 else: 

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

942 eligible_nodes.append(dest_node) 

943 

944 # Adding new node with m edges 

945 else: 

946 # Select the edges' nodes by preferential attachment 

947 targets = _random_subset(attachment_preference, m, seed) 

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

949 

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

951 attachment_preference.extend(targets) 

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

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

954 new_node += 1 

955 return G 

956 

957 

958@py_random_state(3) 

959@nx._dispatch(graphs=None) 

960def powerlaw_cluster_graph(n, m, p, seed=None): 

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

962 degree distribution and approximate average clustering. 

963 

964 Parameters 

965 ---------- 

966 n : int 

967 the number of nodes 

968 m : int 

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

970 p : float, 

971 Probability of adding a triangle after adding a random edge 

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

973 Indicator of random number generation state. 

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

975 

976 Notes 

977 ----- 

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

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

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

981 decrease with network size. 

982 

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

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

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

986 

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

988 higher average clustering to be attained if desired. 

989 

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

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

992 on the first iteration like the BA model. 

993 

994 Raises 

995 ------ 

996 NetworkXError 

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

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

999 

1000 References 

1001 ---------- 

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

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

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

1005 """ 

1006 

1007 if m < 1 or n < m: 

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

1009 

1010 if p > 1 or p < 0: 

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

1012 

1013 G = empty_graph(m) # add m initial nodes (m0 in barabasi-speak) 

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

1015 # with nodes repeated once for each adjacent edge 

1016 source = m # next node is m 

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

1018 possible_targets = _random_subset(repeated_nodes, m, seed) 

1019 # do one preferential attachment for new node 

1020 target = possible_targets.pop() 

1021 G.add_edge(source, target) 

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

1023 count = 1 

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

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

1026 neighborhood = [ 

1027 nbr 

1028 for nbr in G.neighbors(target) 

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

1030 ] 

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

1032 nbr = seed.choice(neighborhood) 

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

1034 repeated_nodes.append(nbr) 

1035 count = count + 1 

1036 continue # go to top of while loop 

1037 # else do preferential attachment step if above fails 

1038 target = possible_targets.pop() 

1039 G.add_edge(source, target) 

1040 repeated_nodes.append(target) 

1041 count = count + 1 

1042 

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

1044 source += 1 

1045 return G 

1046 

1047 

1048@py_random_state(3) 

1049@nx._dispatch(graphs=None) 

1050def random_lobster(n, p1, p2, seed=None): 

1051 """Returns a random lobster graph. 

1052 

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

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

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

1056 

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

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

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

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

1061 

1062 Parameters 

1063 ---------- 

1064 n : int 

1065 The expected number of nodes in the backbone 

1066 p1 : float 

1067 Probability of adding an edge to the backbone 

1068 p2 : float 

1069 Probability of adding an edge one level beyond backbone 

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

1071 Indicator of random number generation state. 

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

1073 

1074 Raises 

1075 ------ 

1076 NetworkXError 

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

1078 """ 

1079 p1, p2 = abs(p1), abs(p2) 

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

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

1082 

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

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

1085 L = path_graph(llen) 

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

1087 current_node = llen - 1 

1088 for n in range(llen): 

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

1090 current_node += 1 

1091 L.add_edge(n, current_node) 

1092 cat_node = current_node 

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

1094 current_node += 1 

1095 L.add_edge(cat_node, current_node) 

1096 return L # voila, un lobster! 

1097 

1098 

1099@py_random_state(1) 

1100@nx._dispatch(graphs=None) 

1101def random_shell_graph(constructor, seed=None): 

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

1103 

1104 Parameters 

1105 ---------- 

1106 constructor : list of three-tuples 

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

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

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

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

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

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

1113 will be all possible intra-shell edges. 

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

1115 Indicator of random number generation state. 

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

1117 

1118 Examples 

1119 -------- 

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

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

1122 

1123 """ 

1124 G = empty_graph(0) 

1125 

1126 glist = [] 

1127 intra_edges = [] 

1128 nnodes = 0 

1129 # create gnm graphs for each shell 

1130 for n, m, d in constructor: 

1131 inter_edges = int(m * d) 

1132 intra_edges.append(m - inter_edges) 

1133 g = nx.convert_node_labels_to_integers( 

1134 gnm_random_graph(n, inter_edges, seed=seed), first_label=nnodes 

1135 ) 

1136 glist.append(g) 

1137 nnodes += n 

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

1139 

1140 # connect the shells randomly 

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

1142 nlist1 = list(glist[gi]) 

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

1144 total_edges = intra_edges[gi] 

1145 edge_count = 0 

1146 while edge_count < total_edges: 

1147 u = seed.choice(nlist1) 

1148 v = seed.choice(nlist2) 

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

1150 continue 

1151 else: 

1152 G.add_edge(u, v) 

1153 edge_count = edge_count + 1 

1154 return G 

1155 

1156 

1157@py_random_state(2) 

1158@nx._dispatch(graphs=None) 

1159def random_powerlaw_tree(n, gamma=3, seed=None, tries=100): 

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

1161 

1162 Parameters 

1163 ---------- 

1164 n : int 

1165 The number of nodes. 

1166 gamma : float 

1167 Exponent of the power law. 

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

1169 Indicator of random number generation state. 

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

1171 tries : int 

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

1173 

1174 Raises 

1175 ------ 

1176 NetworkXError 

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

1178 attempts. 

1179 

1180 Notes 

1181 ----- 

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

1183 swapped with new elements from a powerlaw distribution until the 

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

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

1186 

1187 """ 

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

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

1190 G = degree_sequence_tree(seq) 

1191 return G 

1192 

1193 

1194@py_random_state(2) 

1195@nx._dispatch(graphs=None) 

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

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

1198 

1199 Parameters 

1200 ---------- 

1201 n : int, 

1202 The number of nodes. 

1203 gamma : float 

1204 Exponent of the power law. 

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

1206 Indicator of random number generation state. 

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

1208 tries : int 

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

1210 

1211 Raises 

1212 ------ 

1213 NetworkXError 

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

1215 attempts. 

1216 

1217 Notes 

1218 ----- 

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

1220 swapped with new elements from a power law distribution until 

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

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

1223 

1224 """ 

1225 # get trial sequence 

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

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

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

1229 

1230 # another sequence to swap values from 

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

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

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

1234 

1235 for deg in swap: 

1236 # If this degree sequence can be the degree sequence of a tree, return 

1237 # it. It can be a tree if the number of edges is one fewer than the 

1238 # number of nodes, or in other words, `n - sum(zseq) / 2 == 1`. We 

1239 # use an equivalent condition below that avoids floating point 

1240 # operations. 

1241 if 2 * n - sum(zseq) == 2: 

1242 return zseq 

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

1244 zseq[index] = swap.pop() 

1245 

1246 raise nx.NetworkXError( 

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

1248 ) 

1249 

1250 

1251@py_random_state(3) 

1252@nx._dispatch(graphs=None) 

1253def random_kernel_graph(n, kernel_integral, kernel_root=None, seed=None): 

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

1255 

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

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

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

1259 bounded function. 

1260 

1261 Parameters 

1262 ---------- 

1263 n : int 

1264 The number of nodes 

1265 kernel_integral : function 

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

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

1268 kernel_root: function (optional) 

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

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

1271 (this requires SciPy). 

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

1273 Indicator of random number generation state. 

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

1275 

1276 Notes 

1277 ----- 

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

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

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

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

1282 

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

1284 

1285 Examples 

1286 -------- 

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

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

1289 

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

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

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

1293 ... return r / c + w 

1294 >>> c = 1 

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

1296 

1297 See Also 

1298 -------- 

1299 gnp_random_graph 

1300 expected_degree_graph 

1301 

1302 References 

1303 ---------- 

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

1305 "The phase transition in inhomogeneous random graphs", 

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

1307 

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

1309 "Fast Generation of Sparse Random Kernel Graphs". 

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

1311 """ 

1312 if kernel_root is None: 

1313 import scipy as sp 

1314 

1315 def kernel_root(y, a, r): 

1316 def my_function(b): 

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

1318 

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

1320 

1321 graph = nx.Graph() 

1322 graph.add_nodes_from(range(n)) 

1323 (i, j) = (1, 1) 

1324 while i < n: 

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

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

1327 i, j = i + 1, i + 1 

1328 else: 

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

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

1331 return graph