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 "random_k_lift",
39]
40
41
42@py_random_state(2)
43@nx._dispatchable(graphs=None, returns_graph=True)
44def fast_gnp_random_graph(n, p, seed=None, directed=False, *, create_using=None):
45 """Returns a $G_{n,p}$ random graph, also known as an Erdős-Rényi graph or
46 a binomial graph.
47
48 Parameters
49 ----------
50 n : int
51 The number of nodes.
52 p : float
53 Probability for edge creation.
54 seed : integer, random_state, or None (default)
55 Indicator of random number generation state.
56 See :ref:`Randomness<randomness>`.
57 directed : bool, optional (default=False)
58 If True, this function returns a directed graph.
59 create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph)
60 Graph type to create. If graph instance, then cleared before populated.
61 Multigraph types are not supported and raise a ``NetworkXError``.
62 By default NetworkX Graph or DiGraph are used depending on `directed`.
63
64 Notes
65 -----
66 The $G_{n,p}$ graph algorithm chooses each of the $[n (n - 1)] / 2$
67 (undirected) or $n (n - 1)$ (directed) possible edges with probability $p$.
68
69 This algorithm [1]_ runs in $O(n + m)$ time, where `m` is the expected number of
70 edges, which equals $p n (n - 1) / 2$. This should be faster than
71 :func:`gnp_random_graph` when $p$ is small and the expected number of edges
72 is small (that is, the graph is sparse).
73
74 See Also
75 --------
76 gnp_random_graph
77
78 References
79 ----------
80 .. [1] Vladimir Batagelj and Ulrik Brandes,
81 "Efficient generation of large random networks",
82 Phys. Rev. E, 71, 036113, 2005.
83 """
84 default = nx.DiGraph if directed else nx.Graph
85 create_using = check_create_using(
86 create_using, directed=directed, multigraph=False, default=default
87 )
88 if p <= 0 or p >= 1:
89 return nx.gnp_random_graph(
90 n, p, seed=seed, directed=directed, create_using=create_using
91 )
92
93 G = empty_graph(n, create_using=create_using)
94
95 lp = math.log(1.0 - p)
96
97 if directed:
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(w, v)
108
109 # Nodes in graph are from 0,n-1 (start with v as the second node index).
110 v = 1
111 w = -1
112 while v < n:
113 lr = math.log(1.0 - seed.random())
114 w = w + 1 + int(lr / lp)
115 while w >= v and v < n:
116 w = w - v
117 v = v + 1
118 if v < n:
119 G.add_edge(v, w)
120 return G
121
122
123@py_random_state(2)
124@nx._dispatchable(graphs=None, returns_graph=True)
125def gnp_random_graph(n, p, seed=None, directed=False, *, create_using=None):
126 """Returns a $G_{n,p}$ random graph, also known as an Erdős-Rényi graph
127 or a binomial graph.
128
129 The $G_{n,p}$ model chooses each of the possible edges with probability $p$.
130
131 Parameters
132 ----------
133 n : int
134 The number of nodes.
135 p : float
136 Probability for edge creation.
137 seed : integer, random_state, or None (default)
138 Indicator of random number generation state.
139 See :ref:`Randomness<randomness>`.
140 directed : bool, optional (default=False)
141 If True, this function returns a directed graph.
142 create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph)
143 Graph type to create. If graph instance, then cleared before populated.
144 Multigraph types are not supported and raise a ``NetworkXError``.
145 By default NetworkX Graph or DiGraph are used depending on `directed`.
146
147 See Also
148 --------
149 fast_gnp_random_graph
150
151 Notes
152 -----
153 This algorithm [2]_ runs in $O(n^2)$ time. For sparse graphs (that is, for
154 small values of $p$), :func:`fast_gnp_random_graph` is a faster algorithm.
155
156 :func:`binomial_graph` and :func:`erdos_renyi_graph` are
157 aliases for :func:`gnp_random_graph`.
158
159 >>> nx.binomial_graph is nx.gnp_random_graph
160 True
161 >>> nx.erdos_renyi_graph is nx.gnp_random_graph
162 True
163
164 References
165 ----------
166 .. [1] P. Erdős and A. Rényi, On Random Graphs, Publ. Math. 6, 290 (1959).
167 .. [2] E. N. Gilbert, Random Graphs, Ann. Math. Stat., 30, 1141 (1959).
168 """
169 default = nx.DiGraph if directed else nx.Graph
170 create_using = check_create_using(
171 create_using, directed=directed, multigraph=False, default=default
172 )
173 if p >= 1:
174 return complete_graph(n, create_using=create_using)
175
176 G = nx.empty_graph(n, create_using=create_using)
177 if p <= 0:
178 return G
179
180 edgetool = itertools.permutations if directed else itertools.combinations
181 for e in edgetool(range(n), 2):
182 if seed.random() < p:
183 G.add_edge(*e)
184 return G
185
186
187# add some aliases to common names
188binomial_graph = gnp_random_graph
189erdos_renyi_graph = gnp_random_graph
190
191
192@py_random_state(2)
193@nx._dispatchable(graphs=None, returns_graph=True)
194def dense_gnm_random_graph(n, m, seed=None, *, create_using=None):
195 """Returns a $G_{n,m}$ random graph.
196
197 In the $G_{n,m}$ model, a graph is chosen uniformly at random from the set
198 of all graphs with $n$ nodes and $m$ edges.
199
200 This algorithm should be faster than :func:`gnm_random_graph` for dense
201 graphs.
202
203 Parameters
204 ----------
205 n : int
206 The number of nodes.
207 m : int
208 The number of edges.
209 seed : integer, random_state, or None (default)
210 Indicator of random number generation state.
211 See :ref:`Randomness<randomness>`.
212 create_using : Graph constructor, optional (default=nx.Graph)
213 Graph type to create. If graph instance, then cleared before populated.
214 Multigraph and directed types are not supported and raise a ``NetworkXError``.
215
216 See Also
217 --------
218 gnm_random_graph
219
220 Notes
221 -----
222 Algorithm by Keith M. Briggs Mar 31, 2006.
223 Inspired by Knuth's Algorithm S (Selection sampling technique),
224 in section 3.4.2 of [1]_.
225
226 References
227 ----------
228 .. [1] Donald E. Knuth, The Art of Computer Programming,
229 Volume 2/Seminumerical algorithms, Third Edition, Addison-Wesley, 1997.
230 """
231 create_using = check_create_using(create_using, directed=False, multigraph=False)
232 mmax = n * (n - 1) // 2
233 if m >= mmax:
234 return complete_graph(n, create_using)
235 G = empty_graph(n, create_using)
236
237 if n == 1:
238 return G
239
240 u = 0
241 v = 1
242 t = 0
243 k = 0
244 while True:
245 if seed.randrange(mmax - t) < m - k:
246 G.add_edge(u, v)
247 k += 1
248 if k == m:
249 return G
250 t += 1
251 v += 1
252 if v == n: # go to next row of adjacency matrix
253 u += 1
254 v = u + 1
255
256
257@py_random_state(2)
258@nx._dispatchable(graphs=None, returns_graph=True)
259def gnm_random_graph(n, m, seed=None, directed=False, *, create_using=None):
260 """Returns a $G_{n,m}$ random graph.
261
262 In the $G_{n,m}$ model, a graph is chosen uniformly at random from the set
263 of all graphs with $n$ nodes and $m$ edges.
264
265 This algorithm should be faster than :func:`dense_gnm_random_graph` for
266 sparse graphs.
267
268 Parameters
269 ----------
270 n : int
271 The number of nodes.
272 m : int
273 The number of edges.
274 seed : integer, random_state, or None (default)
275 Indicator of random number generation state.
276 See :ref:`Randomness<randomness>`.
277 directed : bool, optional (default=False)
278 If True return a directed graph
279 create_using : Graph constructor, optional (default=nx.Graph or nx.DiGraph)
280 Graph type to create. If graph instance, then cleared before populated.
281 Multigraph types are not supported and raise a ``NetworkXError``.
282 By default NetworkX Graph or DiGraph are used depending on `directed`.
283
284 See also
285 --------
286 dense_gnm_random_graph
287
288 """
289 default = nx.DiGraph if directed else nx.Graph
290 create_using = check_create_using(
291 create_using, directed=directed, multigraph=False, default=default
292 )
293 if n == 1:
294 return nx.empty_graph(n, create_using=create_using)
295 max_edges = n * (n - 1) if directed else n * (n - 1) / 2.0
296 if m >= max_edges:
297 return complete_graph(n, create_using=create_using)
298
299 G = nx.empty_graph(n, create_using=create_using)
300 nlist = list(G)
301 edge_count = 0
302 while edge_count < m:
303 # generate random edge,u,v
304 u = seed.choice(nlist)
305 v = seed.choice(nlist)
306 if u == v or G.has_edge(u, v):
307 continue
308 else:
309 G.add_edge(u, v)
310 edge_count = edge_count + 1
311 return G
312
313
314@py_random_state(3)
315@nx._dispatchable(graphs=None, returns_graph=True)
316def newman_watts_strogatz_graph(n, k, p, seed=None, *, create_using=None):
317 """Returns a Newman–Watts–Strogatz small-world graph.
318
319 Parameters
320 ----------
321 n : int
322 The number of nodes.
323 k : int
324 Each node is joined with its `k` nearest neighbors in a ring
325 topology.
326 p : float
327 The probability of adding a new edge for each edge.
328 seed : integer, random_state, or None (default)
329 Indicator of random number generation state.
330 See :ref:`Randomness<randomness>`.
331 create_using : Graph constructor, optional (default=nx.Graph)
332 Graph type to create. If graph instance, then cleared before populated.
333 Multigraph and directed types are not supported and raise a ``NetworkXError``.
334
335 Notes
336 -----
337 First create a ring over $n$ nodes [1]_. Then each node in the ring is
338 connected with its $k$ nearest neighbors (or $k - 1$ neighbors if $k$
339 is odd). Then shortcuts are created by adding new edges as follows: for
340 each edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest
341 neighbors" with probability $p$ add a new edge $(u, w)$ with
342 randomly-chosen existing node $w$. In contrast with
343 :func:`watts_strogatz_graph`, no edges are removed.
344
345 See Also
346 --------
347 watts_strogatz_graph
348
349 References
350 ----------
351 .. [1] M. E. J. Newman and D. J. Watts,
352 Renormalization group analysis of the small-world network model,
353 Physics Letters A, 263, 341, 1999.
354 https://doi.org/10.1016/S0375-9601(99)00757-4
355 """
356 create_using = check_create_using(create_using, directed=False, multigraph=False)
357 if k > n:
358 raise nx.NetworkXError("k>=n, choose smaller k or larger n")
359
360 # If k == n the graph return is a complete graph
361 if k == n:
362 return nx.complete_graph(n, create_using)
363
364 G = empty_graph(n, create_using)
365 nlist = list(G.nodes())
366 fromv = nlist
367 # connect the k/2 neighbors
368 for j in range(1, k // 2 + 1):
369 tov = fromv[j:] + fromv[0:j] # the first j are now last
370 for i in range(len(fromv)):
371 G.add_edge(fromv[i], tov[i])
372 # for each edge u-v, with probability p, randomly select existing
373 # node w and add new edge u-w
374 e = list(G.edges())
375 for u, v in e:
376 if seed.random() < p:
377 w = seed.choice(nlist)
378 # no self-loops and reject if edge u-w exists
379 # is that the correct NWS model?
380 while w == u or G.has_edge(u, w):
381 w = seed.choice(nlist)
382 if G.degree(u) >= n - 1:
383 break # skip this rewiring
384 else:
385 G.add_edge(u, w)
386 return G
387
388
389@py_random_state(3)
390@nx._dispatchable(graphs=None, returns_graph=True)
391def watts_strogatz_graph(n, k, p, seed=None, *, create_using=None):
392 """Returns a Watts–Strogatz small-world graph.
393
394 Parameters
395 ----------
396 n : int
397 The number of nodes
398 k : int
399 Each node is joined with its `k` nearest neighbors in a ring
400 topology.
401 p : float
402 The probability of rewiring each edge
403 seed : integer, random_state, or None (default)
404 Indicator of random number generation state.
405 See :ref:`Randomness<randomness>`.
406 create_using : Graph constructor, optional (default=nx.Graph)
407 Graph type to create. If graph instance, then cleared before populated.
408 Multigraph and directed types are not supported and raise a ``NetworkXError``.
409
410 See Also
411 --------
412 newman_watts_strogatz_graph
413 connected_watts_strogatz_graph
414
415 Notes
416 -----
417 First create a ring over $n$ nodes [1]_. Then each node in the ring is joined
418 to its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ is odd).
419 Then shortcuts are created by replacing some edges as follows: for each
420 edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest neighbors"
421 with probability $p$ replace it with a new edge $(u, w)$ with uniformly
422 random choice of existing node $w$.
423
424 In contrast with :func:`newman_watts_strogatz_graph`, the random rewiring
425 does not increase the number of edges. The rewired graph is not guaranteed
426 to be connected as in :func:`connected_watts_strogatz_graph`.
427
428 References
429 ----------
430 .. [1] Duncan J. Watts and Steven H. Strogatz,
431 Collective dynamics of small-world networks,
432 Nature, 393, pp. 440--442, 1998.
433 """
434 create_using = check_create_using(create_using, directed=False, multigraph=False)
435 if k > n:
436 raise nx.NetworkXError("k>n, choose smaller k or larger n")
437
438 # If k == n, the graph is complete not Watts-Strogatz
439 if k == n:
440 G = nx.complete_graph(n, create_using)
441 return G
442
443 G = nx.empty_graph(n, create_using=create_using)
444 nodes = list(range(n)) # nodes are labeled 0 to n-1
445 # connect each node to k/2 neighbors
446 for j in range(1, k // 2 + 1):
447 targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list
448 G.add_edges_from(zip(nodes, targets))
449 # rewire edges from each node
450 # loop over all nodes in order (label) and neighbors in order (distance)
451 # no self loops or multiple edges allowed
452 for j in range(1, k // 2 + 1): # outer loop is neighbors
453 targets = nodes[j:] + nodes[0:j] # first j nodes are now last in list
454 # inner loop in node order
455 for u, v in zip(nodes, targets):
456 if seed.random() < p:
457 w = seed.choice(nodes)
458 # Enforce no self-loops or multiple edges
459 while w == u or G.has_edge(u, w):
460 w = seed.choice(nodes)
461 if G.degree(u) >= n - 1:
462 break # skip this rewiring
463 else:
464 G.remove_edge(u, v)
465 G.add_edge(u, w)
466 return G
467
468
469@py_random_state(4)
470@nx._dispatchable(graphs=None, returns_graph=True)
471def connected_watts_strogatz_graph(n, k, p, tries=100, seed=None, *, create_using=None):
472 """Returns a connected Watts–Strogatz small-world graph.
473
474 Attempts to generate a connected graph by repeated generation of
475 Watts–Strogatz small-world graphs. An exception is raised if the maximum
476 number of tries is exceeded.
477
478 Parameters
479 ----------
480 n : int
481 The number of nodes
482 k : int
483 Each node is joined with its `k` nearest neighbors in a ring
484 topology.
485 p : float
486 The probability of rewiring each edge
487 tries : int
488 Number of attempts to generate a connected graph.
489 seed : integer, random_state, or None (default)
490 Indicator of random number generation state.
491 See :ref:`Randomness<randomness>`.
492 create_using : Graph constructor, optional (default=nx.Graph)
493 Graph type to create. If graph instance, then cleared before populated.
494 Multigraph and directed types are not supported and raise a ``NetworkXError``.
495
496 Notes
497 -----
498 First create a ring over $n$ nodes [1]_. Then each node in the ring is joined
499 to its $k$ nearest neighbors (or $k - 1$ neighbors if $k$ is odd).
500 Then shortcuts are created by replacing some edges as follows: for each
501 edge $(u, v)$ in the underlying "$n$-ring with $k$ nearest neighbors"
502 with probability $p$ replace it with a new edge $(u, w)$ with uniformly
503 random choice of existing node $w$.
504 The entire process is repeated until a connected graph results.
505
506 See Also
507 --------
508 newman_watts_strogatz_graph
509 watts_strogatz_graph
510
511 References
512 ----------
513 .. [1] Duncan J. Watts and Steven H. Strogatz,
514 Collective dynamics of small-world networks,
515 Nature, 393, pp. 440--442, 1998.
516 """
517 for i in range(tries):
518 # seed is an RNG so should change sequence each call
519 G = watts_strogatz_graph(n, k, p, seed, create_using=create_using)
520 if nx.is_connected(G):
521 return G
522 raise nx.NetworkXError("Maximum number of tries exceeded")
523
524
525@py_random_state(2)
526@nx._dispatchable(graphs=None, returns_graph=True)
527def random_regular_graph(d, n, seed=None, *, create_using=None):
528 r"""Returns a random $d$-regular graph on $n$ nodes.
529
530 A regular graph is a graph where each node has the same number of neighbors.
531
532 The resulting graph has no self-loops or parallel edges.
533
534 Parameters
535 ----------
536 d : int
537 The degree of each node.
538 n : integer
539 The number of nodes. The value of $n \times d$ must be even.
540 seed : integer, random_state, or None (default)
541 Indicator of random number generation state.
542 See :ref:`Randomness<randomness>`.
543 create_using : Graph constructor, optional (default=nx.Graph)
544 Graph type to create. If graph instance, then cleared before populated.
545 Multigraph and directed types are not supported and raise a ``NetworkXError``.
546
547 Notes
548 -----
549 The nodes are numbered from $0$ to $n - 1$.
550
551 Kim and Vu's paper [2]_ shows that this algorithm samples in an
552 asymptotically uniform way from the space of random graphs when
553 $d = O(n^{1 / 3 - \epsilon})$.
554
555 Raises
556 ------
557
558 NetworkXError
559 If $n \times d$ is odd or $d$ is greater than or equal to $n$.
560
561 References
562 ----------
563 .. [1] A. Steger and N. Wormald,
564 Generating random regular graphs quickly,
565 Probability and Computing 8 (1999), 377-396, 1999.
566 https://doi.org/10.1017/S0963548399003867
567
568 .. [2] Jeong Han Kim and Van H. Vu,
569 Generating random regular graphs,
570 Proceedings of the thirty-fifth ACM symposium on Theory of computing,
571 San Diego, CA, USA, pp 213--222, 2003.
572 http://portal.acm.org/citation.cfm?id=780542.780576
573 """
574 create_using = check_create_using(create_using, directed=False, multigraph=False)
575 if (n * d) % 2 != 0:
576 raise nx.NetworkXError("n * d must be even")
577
578 if not 0 <= d < n:
579 raise nx.NetworkXError("the 0 <= d < n inequality must be satisfied")
580
581 G = nx.empty_graph(n, create_using=create_using)
582
583 if d == 0:
584 return G
585
586 def _suitable(edges, potential_edges):
587 # Helper subroutine to check if there are suitable edges remaining
588 # If False, the generation of the graph has failed
589 if not potential_edges:
590 return True
591 for s1 in potential_edges:
592 for s2 in potential_edges:
593 # Two iterators on the same dictionary are guaranteed
594 # to visit it in the same order if there are no
595 # intervening modifications.
596 if s1 == s2:
597 # Only need to consider s1-s2 pair one time
598 break
599 if s1 > s2:
600 s1, s2 = s2, s1
601 if (s1, s2) not in edges:
602 return True
603 return False
604
605 def _try_creation():
606 # Attempt to create an edge set
607
608 edges = set()
609 stubs = list(range(n)) * d
610
611 while stubs:
612 potential_edges = defaultdict(lambda: 0)
613 seed.shuffle(stubs)
614 stubiter = iter(stubs)
615 for s1, s2 in zip(stubiter, stubiter):
616 if s1 > s2:
617 s1, s2 = s2, s1
618 if s1 != s2 and ((s1, s2) not in edges):
619 edges.add((s1, s2))
620 else:
621 potential_edges[s1] += 1
622 potential_edges[s2] += 1
623
624 if not _suitable(edges, potential_edges):
625 return None # failed to find suitable edge set
626
627 stubs = [
628 node
629 for node, potential in potential_edges.items()
630 for _ in range(potential)
631 ]
632 return edges
633
634 # Even though a suitable edge set exists,
635 # the generation of such a set is not guaranteed.
636 # Try repeatedly to find one.
637 edges = _try_creation()
638 while edges is None:
639 edges = _try_creation()
640 G.add_edges_from(edges)
641
642 return G
643
644
645def _random_subset(seq, m, rng):
646 """Return m unique elements from seq.
647
648 This differs from random.sample which can return repeated
649 elements if seq holds repeated elements.
650
651 Note: rng is a random.Random or numpy.random.RandomState instance.
652 """
653 targets = set()
654 while len(targets) < m:
655 x = rng.choice(seq)
656 targets.add(x)
657 return targets
658
659
660@py_random_state(2)
661@nx._dispatchable(graphs=None, returns_graph=True)
662def barabasi_albert_graph(n, m, seed=None, initial_graph=None, *, create_using=None):
663 """Returns a random graph using Barabási–Albert preferential attachment
664
665 A graph of $n$ nodes is grown by attaching new nodes each with $m$
666 edges that are preferentially attached to existing nodes with high degree.
667
668 Parameters
669 ----------
670 n : int
671 Number of nodes
672 m : int
673 Number of edges to attach from a new node to existing nodes
674 seed : integer, random_state, or None (default)
675 Indicator of random number generation state.
676 See :ref:`Randomness<randomness>`.
677 initial_graph : Graph or None (default)
678 Initial network for Barabási–Albert algorithm.
679 It should be a connected graph for most use cases.
680 A copy of `initial_graph` is used.
681 If None, starts from a star graph on (m+1) nodes.
682 create_using : Graph constructor, optional (default=nx.Graph)
683 Graph type to create. If graph instance, then cleared before populated.
684 Multigraph and directed types are not supported and raise a ``NetworkXError``.
685
686 Returns
687 -------
688 G : Graph
689
690 Raises
691 ------
692 NetworkXError
693 If `m` does not satisfy ``1 <= m < n``, or
694 the initial graph number of nodes m0 does not satisfy ``m <= m0 <= n``.
695
696 References
697 ----------
698 .. [1] A. L. Barabási and R. Albert "Emergence of scaling in
699 random networks", Science 286, pp 509-512, 1999.
700 """
701 create_using = check_create_using(create_using, directed=False, multigraph=False)
702 if m < 1 or m >= n:
703 raise nx.NetworkXError(
704 f"Barabási–Albert network must have m >= 1 and m < n, m = {m}, n = {n}"
705 )
706
707 if initial_graph is None:
708 # Default initial graph : star graph on (m + 1) nodes
709 G = star_graph(m, create_using)
710 else:
711 if len(initial_graph) < m or len(initial_graph) > n:
712 raise nx.NetworkXError(
713 f"Barabási–Albert initial graph needs between m={m} and n={n} nodes"
714 )
715 G = initial_graph.copy()
716
717 # List of existing nodes, with nodes repeated once for each adjacent edge
718 repeated_nodes = [n for n, d in G.degree() for _ in range(d)]
719 # Start adding the other n - m0 nodes.
720 source = len(G)
721 while source < n:
722 # Now choose m unique nodes from the existing nodes
723 # Pick uniformly from repeated_nodes (preferential attachment)
724 targets = _random_subset(repeated_nodes, m, seed)
725 # Add edges to m nodes from the source.
726 G.add_edges_from(zip([source] * m, targets))
727 # Add one node to the list for each new edge just created.
728 repeated_nodes.extend(targets)
729 # And the new node "source" has m edges to add to the list.
730 repeated_nodes.extend([source] * m)
731
732 source += 1
733 return G
734
735
736@py_random_state(4)
737@nx._dispatchable(graphs=None, returns_graph=True)
738def dual_barabasi_albert_graph(
739 n, m1, m2, p, seed=None, initial_graph=None, *, create_using=None
740):
741 """Returns a random graph using dual Barabási–Albert preferential attachment
742
743 A graph of $n$ nodes is grown by attaching new nodes each with either $m_1$
744 edges (with probability $p$) or $m_2$ edges (with probability $1-p$) that
745 are preferentially attached to existing nodes with high degree.
746
747 Parameters
748 ----------
749 n : int
750 Number of nodes
751 m1 : int
752 Number of edges to link each new node to existing nodes with probability $p$
753 m2 : int
754 Number of edges to link each new node to existing nodes with probability $1-p$
755 p : float
756 The probability of attaching $m_1$ edges (as opposed to $m_2$ edges)
757 seed : integer, random_state, or None (default)
758 Indicator of random number generation state.
759 See :ref:`Randomness<randomness>`.
760 initial_graph : Graph or None (default)
761 Initial network for Barabási–Albert algorithm.
762 A copy of `initial_graph` is used.
763 It should be connected for most use cases.
764 If None, starts from an star graph on max(m1, m2) + 1 nodes.
765 create_using : Graph constructor, optional (default=nx.Graph)
766 Graph type to create. If graph instance, then cleared before populated.
767 Multigraph and directed types are not supported and raise a ``NetworkXError``.
768
769 Returns
770 -------
771 G : Graph
772
773 Raises
774 ------
775 NetworkXError
776 If `m1` and `m2` do not satisfy ``1 <= m1,m2 < n``, or
777 `p` does not satisfy ``0 <= p <= 1``, or
778 the initial graph number of nodes m0 does not satisfy m1, m2 <= m0 <= n.
779
780 References
781 ----------
782 .. [1] N. Moshiri "The dual-Barabasi-Albert model", arXiv:1810.10538.
783 """
784 create_using = check_create_using(create_using, directed=False, multigraph=False)
785 if m1 < 1 or m1 >= n:
786 raise nx.NetworkXError(
787 f"Dual Barabási–Albert must have m1 >= 1 and m1 < n, m1 = {m1}, n = {n}"
788 )
789 if m2 < 1 or m2 >= n:
790 raise nx.NetworkXError(
791 f"Dual Barabási–Albert must have m2 >= 1 and m2 < n, m2 = {m2}, n = {n}"
792 )
793 if p < 0 or p > 1:
794 raise nx.NetworkXError(
795 f"Dual Barabási–Albert network must have 0 <= p <= 1, p = {p}"
796 )
797
798 # For simplicity, if p == 0 or 1, just return BA
799 if p == 1:
800 return barabasi_albert_graph(n, m1, seed, create_using=create_using)
801 elif p == 0:
802 return barabasi_albert_graph(n, m2, seed, create_using=create_using)
803
804 if initial_graph is None:
805 # Default initial graph : star graph on max(m1, m2) nodes
806 G = star_graph(max(m1, m2), create_using)
807 else:
808 if len(initial_graph) < max(m1, m2) or len(initial_graph) > n:
809 raise nx.NetworkXError(
810 f"Barabási–Albert initial graph must have between "
811 f"max(m1, m2) = {max(m1, m2)} and n = {n} nodes"
812 )
813 G = initial_graph.copy()
814
815 # Target nodes for new edges
816 targets = list(G)
817 # List of existing nodes, with nodes repeated once for each adjacent edge
818 repeated_nodes = [n for n, d in G.degree() for _ in range(d)]
819 # Start adding the remaining nodes.
820 source = len(G)
821 while source < n:
822 # Pick which m to use (m1 or m2)
823 if seed.random() < p:
824 m = m1
825 else:
826 m = m2
827 # Now choose m unique nodes from the existing nodes
828 # Pick uniformly from repeated_nodes (preferential attachment)
829 targets = _random_subset(repeated_nodes, m, seed)
830 # Add edges to m nodes from the source.
831 G.add_edges_from(zip([source] * m, targets))
832 # Add one node to the list for each new edge just created.
833 repeated_nodes.extend(targets)
834 # And the new node "source" has m edges to add to the list.
835 repeated_nodes.extend([source] * m)
836
837 source += 1
838 return G
839
840
841@py_random_state(4)
842@nx._dispatchable(graphs=None, returns_graph=True)
843def extended_barabasi_albert_graph(n, m, p, q, seed=None, *, create_using=None):
844 """Returns an extended Barabási–Albert model graph.
845
846 An extended Barabási–Albert model graph is a random graph constructed
847 using preferential attachment. The extended model allows new edges,
848 rewired edges or new nodes. Based on the probabilities $p$ and $q$
849 with $p + q < 1$, the growing behavior of the graph is determined as:
850
851 1) With $p$ probability, $m$ new edges are added to the graph,
852 starting from randomly chosen existing nodes and attached preferentially at the
853 other end.
854
855 2) With $q$ probability, $m$ existing edges are rewired
856 by randomly choosing an edge and rewiring one end to a preferentially chosen node.
857
858 3) With $(1 - p - q)$ probability, $m$ new nodes are added to the graph
859 with edges attached preferentially.
860
861 When $p = q = 0$, the model behaves just like the Barabási–Alber model.
862
863 Parameters
864 ----------
865 n : int
866 Number of nodes
867 m : int
868 Number of edges with which a new node attaches to existing nodes
869 p : float
870 Probability value for adding an edge between existing nodes. p + q < 1
871 q : float
872 Probability value of rewiring of existing edges. p + q < 1
873 seed : integer, random_state, or None (default)
874 Indicator of random number generation state.
875 See :ref:`Randomness<randomness>`.
876 create_using : Graph constructor, optional (default=nx.Graph)
877 Graph type to create. If graph instance, then cleared before populated.
878 Multigraph and directed types are not supported and raise a ``NetworkXError``.
879
880 Returns
881 -------
882 G : Graph
883
884 Raises
885 ------
886 NetworkXError
887 If `m` does not satisfy ``1 <= m < n`` or ``1 >= p + q``
888
889 References
890 ----------
891 .. [1] Albert, R., & Barabási, A. L. (2000)
892 Topology of evolving networks: local events and universality
893 Physical review letters, 85(24), 5234.
894 """
895 create_using = check_create_using(create_using, directed=False, multigraph=False)
896 if m < 1 or m >= n:
897 msg = f"Extended Barabasi-Albert network needs m>=1 and m<n, m={m}, n={n}"
898 raise nx.NetworkXError(msg)
899 if p + q >= 1:
900 msg = f"Extended Barabasi-Albert network needs p + q <= 1, p={p}, q={q}"
901 raise nx.NetworkXError(msg)
902
903 # Add m initial nodes (m0 in barabasi-speak)
904 G = empty_graph(m, create_using)
905
906 # List of nodes to represent the preferential attachment random selection.
907 # At the creation of the graph, all nodes are added to the list
908 # so that even nodes that are not connected have a chance to get selected,
909 # for rewiring and adding of edges.
910 # With each new edge, nodes at the ends of the edge are added to the list.
911 attachment_preference = []
912 attachment_preference.extend(range(m))
913
914 # Start adding the other n-m nodes. The first node is m.
915 new_node = m
916 while new_node < n:
917 a_probability = seed.random()
918
919 # Total number of edges of a Clique of all the nodes
920 clique_degree = len(G) - 1
921 clique_size = (len(G) * clique_degree) / 2
922
923 # Adding m new edges, if there is room to add them
924 if a_probability < p and G.size() <= clique_size - m:
925 # Select the nodes where an edge can be added
926 eligible_nodes = [nd for nd, deg in G.degree() if deg < clique_degree]
927 for i in range(m):
928 # Choosing a random source node from eligible_nodes
929 src_node = seed.choice(eligible_nodes)
930
931 # Picking a possible node that is not 'src_node' or
932 # neighbor with 'src_node', with preferential attachment
933 prohibited_nodes = list(G[src_node])
934 prohibited_nodes.append(src_node)
935 # This will raise an exception if the sequence is empty
936 dest_node = seed.choice(
937 [nd for nd in attachment_preference if nd not in prohibited_nodes]
938 )
939 # Adding the new edge
940 G.add_edge(src_node, dest_node)
941
942 # Appending both nodes to add to their preferential attachment
943 attachment_preference.append(src_node)
944 attachment_preference.append(dest_node)
945
946 # Adjusting the eligible nodes. Degree may be saturated.
947 if G.degree(src_node) == clique_degree:
948 eligible_nodes.remove(src_node)
949 if G.degree(dest_node) == clique_degree and dest_node in eligible_nodes:
950 eligible_nodes.remove(dest_node)
951
952 # Rewiring m edges, if there are enough edges
953 elif p <= a_probability < (p + q) and m <= G.size() < clique_size:
954 # Selecting nodes that have at least 1 edge but that are not
955 # fully connected to ALL other nodes (center of star).
956 # These nodes are the pivot nodes of the edges to rewire
957 eligible_nodes = [nd for nd, deg in G.degree() if 0 < deg < clique_degree]
958 for i in range(m):
959 # Choosing a random source node
960 node = seed.choice(eligible_nodes)
961
962 # The available nodes do have a neighbor at least.
963 nbr_nodes = list(G[node])
964
965 # Choosing the other end that will get detached
966 src_node = seed.choice(nbr_nodes)
967
968 # Picking a target node that is not 'node' or
969 # neighbor with 'node', with preferential attachment
970 nbr_nodes.append(node)
971 dest_node = seed.choice(
972 [nd for nd in attachment_preference if nd not in nbr_nodes]
973 )
974 # Rewire
975 G.remove_edge(node, src_node)
976 G.add_edge(node, dest_node)
977
978 # Adjusting the preferential attachment list
979 attachment_preference.remove(src_node)
980 attachment_preference.append(dest_node)
981
982 # Adjusting the eligible nodes.
983 # nodes may be saturated or isolated.
984 if G.degree(src_node) == 0 and src_node in eligible_nodes:
985 eligible_nodes.remove(src_node)
986 if dest_node in eligible_nodes:
987 if G.degree(dest_node) == clique_degree:
988 eligible_nodes.remove(dest_node)
989 else:
990 if G.degree(dest_node) == 1:
991 eligible_nodes.append(dest_node)
992
993 # Adding new node with m edges
994 else:
995 # Select the edges' nodes by preferential attachment
996 targets = _random_subset(attachment_preference, m, seed)
997 G.add_edges_from(zip([new_node] * m, targets))
998
999 # Add one node to the list for each new edge just created.
1000 attachment_preference.extend(targets)
1001 # The new node has m edges to it, plus itself: m + 1
1002 attachment_preference.extend([new_node] * (m + 1))
1003 new_node += 1
1004 return G
1005
1006
1007@py_random_state(3)
1008@nx._dispatchable(graphs=None, returns_graph=True)
1009def powerlaw_cluster_graph(n, m, p, seed=None, *, create_using=None):
1010 """Holme and Kim algorithm for growing graphs with powerlaw
1011 degree distribution and approximate average clustering.
1012
1013 Parameters
1014 ----------
1015 n : int
1016 the number of nodes
1017 m : int
1018 the number of random edges to add for each new node
1019 p : float,
1020 Probability of adding a triangle after adding a random edge
1021 seed : integer, random_state, or None (default)
1022 Indicator of random number generation state.
1023 See :ref:`Randomness<randomness>`.
1024 create_using : Graph constructor, optional (default=nx.Graph)
1025 Graph type to create. If graph instance, then cleared before populated.
1026 Multigraph and directed types are not supported and raise a ``NetworkXError``.
1027
1028 Notes
1029 -----
1030 The average clustering has a hard time getting above a certain
1031 cutoff that depends on `m`. This cutoff is often quite low. The
1032 transitivity (fraction of triangles to possible triangles) seems to
1033 decrease with network size.
1034
1035 It is essentially the Barabási–Albert (BA) growth model with an
1036 extra step that each random edge is followed by a chance of
1037 making an edge to one of its neighbors too (and thus a triangle).
1038
1039 This algorithm improves on BA in the sense that it enables a
1040 higher average clustering to be attained if desired.
1041
1042 It seems possible to have a disconnected graph with this algorithm
1043 since the initial `m` nodes may not be all linked to a new node
1044 on the first iteration like the BA model.
1045
1046 Raises
1047 ------
1048 NetworkXError
1049 If `m` does not satisfy ``1 <= m <= n`` or `p` does not
1050 satisfy ``0 <= p <= 1``.
1051
1052 References
1053 ----------
1054 .. [1] P. Holme and B. J. Kim,
1055 "Growing scale-free networks with tunable clustering",
1056 Phys. Rev. E, 65, 026107, 2002.
1057 """
1058 create_using = check_create_using(create_using, directed=False, multigraph=False)
1059 if m < 1 or n < m:
1060 raise nx.NetworkXError(f"NetworkXError must have m>1 and m<n, m={m},n={n}")
1061
1062 if p > 1 or p < 0:
1063 raise nx.NetworkXError(f"NetworkXError p must be in [0,1], p={p}")
1064
1065 G = empty_graph(m, create_using) # add m initial nodes (m0 in barabasi-speak)
1066 repeated_nodes = list(G) # list of existing nodes to sample from
1067 # with nodes repeated once for each adjacent edge
1068 source = m # next node is m
1069 while source < n: # Now add the other n-1 nodes
1070 possible_targets = _random_subset(repeated_nodes, m, seed)
1071 # do one preferential attachment for new node
1072 target = possible_targets.pop()
1073 G.add_edge(source, target)
1074 repeated_nodes.append(target) # add one node to list for each new link
1075 count = 1
1076 while count < m: # add m-1 more new links
1077 if seed.random() < p: # clustering step: add triangle
1078 neighborhood = [
1079 nbr
1080 for nbr in G.neighbors(target)
1081 if not G.has_edge(source, nbr) and nbr != source
1082 ]
1083 if neighborhood: # if there is a neighbor without a link
1084 nbr = seed.choice(neighborhood)
1085 G.add_edge(source, nbr) # add triangle
1086 repeated_nodes.append(nbr)
1087 count = count + 1
1088 continue # go to top of while loop
1089 # else do preferential attachment step if above fails
1090 target = possible_targets.pop()
1091 G.add_edge(source, target)
1092 repeated_nodes.append(target)
1093 count = count + 1
1094
1095 repeated_nodes.extend([source] * m) # add source node to list m times
1096 source += 1
1097 return G
1098
1099
1100@py_random_state(3)
1101@nx._dispatchable(graphs=None, returns_graph=True)
1102def random_lobster_graph(n, p1, p2, seed=None, *, create_using=None):
1103 """Returns a random lobster graph.
1104
1105 A lobster is a tree that reduces to a caterpillar when pruning all
1106 leaf nodes. A caterpillar is a tree that reduces to a path graph
1107 when pruning all leaf nodes; setting `p2` to zero produces a caterpillar.
1108
1109 This implementation iterates on the probabilities `p1` and `p2` to add
1110 edges at levels 1 and 2, respectively. Graphs are therefore constructed
1111 iteratively with uniform randomness at each level rather than being selected
1112 uniformly at random from the set of all possible lobsters.
1113
1114 Parameters
1115 ----------
1116 n : int
1117 The expected number of nodes in the backbone
1118 p1 : float
1119 Probability of adding an edge to the backbone
1120 p2 : float
1121 Probability of adding an edge one level beyond backbone
1122 seed : integer, random_state, or None (default)
1123 Indicator of random number generation state.
1124 See :ref:`Randomness<randomness>`.
1125 create_using : Graph constructor, optional (default=nx.Graph)
1126 Graph type to create. If graph instance, then cleared before populated.
1127 Multigraph and directed types are not supported and raise a ``NetworkXError``.
1128
1129 Raises
1130 ------
1131 NetworkXError
1132 If `p1` or `p2` parameters are >= 1 because the while loops would never finish.
1133 """
1134 create_using = check_create_using(create_using, directed=False, multigraph=False)
1135 p1, p2 = abs(p1), abs(p2)
1136 if any(p >= 1 for p in [p1, p2]):
1137 raise nx.NetworkXError("Probability values for `p1` and `p2` must both be < 1.")
1138
1139 # a necessary ingredient in any self-respecting graph library
1140 llen = int(2 * seed.random() * n + 0.5)
1141 L = path_graph(llen, create_using)
1142 # build caterpillar: add edges to path graph with probability p1
1143 current_node = llen - 1
1144 for n in range(llen):
1145 while seed.random() < p1: # add fuzzy caterpillar parts
1146 current_node += 1
1147 L.add_edge(n, current_node)
1148 cat_node = current_node
1149 while seed.random() < p2: # add crunchy lobster bits
1150 current_node += 1
1151 L.add_edge(cat_node, current_node)
1152 return L # voila, un lobster!
1153
1154
1155@py_random_state(3)
1156@nx._dispatchable(graphs=None, returns_graph=True)
1157def random_lobster(n, p1, p2, seed=None, *, create_using=None):
1158 """
1159 .. deprecated:: 3.5
1160 `random_lobster` is a deprecated alias
1161 for `random_lobster_graph`.
1162 Use `random_lobster_graph` instead.
1163 """
1164 import warnings
1165
1166 warnings.warn(
1167 "`random_lobster` is deprecated, use `random_lobster_graph` instead.",
1168 category=DeprecationWarning,
1169 stacklevel=2,
1170 )
1171 return random_lobster_graph(n, p1, p2, seed=seed, create_using=create_using)
1172
1173
1174@py_random_state(1)
1175@nx._dispatchable(graphs=None, returns_graph=True)
1176def random_shell_graph(constructor, seed=None, *, create_using=None):
1177 """Returns a random shell graph for the constructor given.
1178
1179 Parameters
1180 ----------
1181 constructor : list of three-tuples
1182 Represents the parameters for a shell, starting at the center
1183 shell. Each element of the list must be of the form `(n, m,
1184 d)`, where `n` is the number of nodes in the shell, `m` is
1185 the number of edges in the shell, and `d` is the ratio of
1186 inter-shell (next) edges to intra-shell edges. If `d` is zero,
1187 there will be no intra-shell edges, and if `d` is one there
1188 will be all possible intra-shell edges.
1189 seed : integer, random_state, or None (default)
1190 Indicator of random number generation state.
1191 See :ref:`Randomness<randomness>`.
1192 create_using : Graph constructor, optional (default=nx.Graph)
1193 Graph type to create. Graph instances are not supported.
1194 Multigraph and directed types are not supported and raise a ``NetworkXError``.
1195
1196 Examples
1197 --------
1198 >>> constructor = [(10, 20, 0.8), (20, 40, 0.8)]
1199 >>> G = nx.random_shell_graph(constructor)
1200
1201 """
1202 create_using = check_create_using(create_using, directed=False, multigraph=False)
1203 G = empty_graph(0, create_using)
1204
1205 glist = []
1206 intra_edges = []
1207 nnodes = 0
1208 # create gnm graphs for each shell
1209 for n, m, d in constructor:
1210 inter_edges = int(m * d)
1211 intra_edges.append(m - inter_edges)
1212 g = nx.convert_node_labels_to_integers(
1213 gnm_random_graph(n, inter_edges, seed=seed, create_using=G.__class__),
1214 first_label=nnodes,
1215 )
1216 glist.append(g)
1217 nnodes += n
1218 G = nx.operators.union(G, g)
1219
1220 # connect the shells randomly
1221 for gi in range(len(glist) - 1):
1222 nlist1 = list(glist[gi])
1223 nlist2 = list(glist[gi + 1])
1224 total_edges = intra_edges[gi]
1225 edge_count = 0
1226 while edge_count < total_edges:
1227 u = seed.choice(nlist1)
1228 v = seed.choice(nlist2)
1229 if u == v or G.has_edge(u, v):
1230 continue
1231 else:
1232 G.add_edge(u, v)
1233 edge_count = edge_count + 1
1234 return G
1235
1236
1237@py_random_state(2)
1238@nx._dispatchable(graphs=None, returns_graph=True)
1239def random_powerlaw_tree(n, gamma=3, seed=None, tries=100, *, create_using=None):
1240 """Returns a tree with a power law degree distribution.
1241
1242 Parameters
1243 ----------
1244 n : int
1245 The number of nodes.
1246 gamma : float
1247 Exponent of the power law.
1248 seed : integer, random_state, or None (default)
1249 Indicator of random number generation state.
1250 See :ref:`Randomness<randomness>`.
1251 tries : int
1252 Number of attempts to adjust the sequence to make it a tree.
1253 create_using : Graph constructor, optional (default=nx.Graph)
1254 Graph type to create. If graph instance, then cleared before populated.
1255 Multigraph and directed types are not supported and raise a ``NetworkXError``.
1256
1257 Raises
1258 ------
1259 NetworkXError
1260 If no valid sequence is found within the maximum number of
1261 attempts.
1262
1263 Notes
1264 -----
1265 A trial power law degree sequence is chosen and then elements are
1266 swapped with new elements from a powerlaw distribution until the
1267 sequence makes a tree (by checking, for example, that the number of
1268 edges is one smaller than the number of nodes).
1269
1270 """
1271 create_using = check_create_using(create_using, directed=False, multigraph=False)
1272 # This call may raise a NetworkXError if the number of tries is succeeded.
1273 seq = random_powerlaw_tree_sequence(n, gamma=gamma, seed=seed, tries=tries)
1274 G = degree_sequence_tree(seq, create_using)
1275 return G
1276
1277
1278@py_random_state(2)
1279@nx._dispatchable(graphs=None)
1280def random_powerlaw_tree_sequence(n, gamma=3, seed=None, tries=100):
1281 """Returns a degree sequence for a tree with a power law distribution.
1282
1283 Parameters
1284 ----------
1285 n : int,
1286 The number of nodes.
1287 gamma : float
1288 Exponent of the power law.
1289 seed : integer, random_state, or None (default)
1290 Indicator of random number generation state.
1291 See :ref:`Randomness<randomness>`.
1292 tries : int
1293 Number of attempts to adjust the sequence to make it a tree.
1294
1295 Raises
1296 ------
1297 NetworkXError
1298 If no valid sequence is found within the maximum number of
1299 attempts.
1300
1301 Notes
1302 -----
1303 A trial power law degree sequence is chosen and then elements are
1304 swapped with new elements from a power law distribution until
1305 the sequence makes a tree (by checking, for example, that the number of
1306 edges is one smaller than the number of nodes).
1307
1308 """
1309 # get trial sequence
1310 z = nx.utils.powerlaw_sequence(n, exponent=gamma, seed=seed)
1311 # round to integer values in the range [0,n]
1312 zseq = [min(n, max(round(s), 0)) for s in z]
1313
1314 # another sequence to swap values from
1315 z = nx.utils.powerlaw_sequence(tries, exponent=gamma, seed=seed)
1316 # round to integer values in the range [0,n]
1317 swap = [min(n, max(round(s), 0)) for s in z]
1318
1319 for _ in swap:
1320 valid, _ = nx.utils.is_valid_tree_degree_sequence(zseq)
1321 if valid:
1322 return zseq
1323 index = seed.randint(0, n - 1)
1324 zseq[index] = swap.pop()
1325
1326 raise nx.NetworkXError(
1327 f"Exceeded max ({tries}) attempts for a valid tree sequence."
1328 )
1329
1330
1331@py_random_state(3)
1332@nx._dispatchable(graphs=None, returns_graph=True)
1333def random_kernel_graph(
1334 n, kernel_integral, kernel_root=None, seed=None, *, create_using=None
1335):
1336 r"""Returns an random graph based on the specified kernel.
1337
1338 The algorithm chooses each of the $[n(n-1)]/2$ possible edges with
1339 probability specified by a kernel $\kappa(x,y)$ [1]_. The kernel
1340 $\kappa(x,y)$ must be a symmetric (in $x,y$), non-negative,
1341 bounded function.
1342
1343 Parameters
1344 ----------
1345 n : int
1346 The number of nodes
1347 kernel_integral : function
1348 Function that returns the definite integral of the kernel $\kappa(x,y)$,
1349 $F(y,a,b) := \int_a^b \kappa(x,y)dx$
1350 kernel_root: function (optional)
1351 Function that returns the root $b$ of the equation $F(y,a,b) = r$.
1352 If None, the root is found using :func:`scipy.optimize.brentq`
1353 (this requires SciPy).
1354 seed : integer, random_state, or None (default)
1355 Indicator of random number generation state.
1356 See :ref:`Randomness<randomness>`.
1357 create_using : Graph constructor, optional (default=nx.Graph)
1358 Graph type to create. If graph instance, then cleared before populated.
1359 Multigraph and directed types are not supported and raise a ``NetworkXError``.
1360
1361 Notes
1362 -----
1363 The kernel is specified through its definite integral which must be
1364 provided as one of the arguments. If the integral and root of the
1365 kernel integral can be found in $O(1)$ time then this algorithm runs in
1366 time $O(n+m)$ where m is the expected number of edges [2]_.
1367
1368 The nodes are set to integers from $0$ to $n-1$.
1369
1370 Examples
1371 --------
1372 Generate an Erdős–Rényi random graph $G(n,c/n)$, with kernel
1373 $\kappa(x,y)=c$ where $c$ is the mean expected degree.
1374
1375 >>> def integral(u, w, z):
1376 ... return c * (z - w)
1377 >>> def root(u, w, r):
1378 ... return r / c + w
1379 >>> c = 1
1380 >>> graph = nx.random_kernel_graph(1000, integral, root)
1381
1382 See Also
1383 --------
1384 gnp_random_graph
1385 expected_degree_graph
1386
1387 References
1388 ----------
1389 .. [1] Bollobás, Béla, Janson, S. and Riordan, O.
1390 "The phase transition in inhomogeneous random graphs",
1391 *Random Structures Algorithms*, 31, 3--122, 2007.
1392
1393 .. [2] Hagberg A, Lemons N (2015),
1394 "Fast Generation of Sparse Random Kernel Graphs".
1395 PLoS ONE 10(9): e0135177, 2015. doi:10.1371/journal.pone.0135177
1396 """
1397 create_using = check_create_using(create_using, directed=False, multigraph=False)
1398 if kernel_root is None:
1399 import scipy as sp
1400
1401 def kernel_root(y, a, r):
1402 def my_function(b):
1403 return kernel_integral(y, a, b) - r
1404
1405 return sp.optimize.brentq(my_function, a, 1)
1406
1407 graph = nx.empty_graph(create_using=create_using)
1408 graph.add_nodes_from(range(n))
1409 (i, j) = (1, 1)
1410 while i < n:
1411 r = -math.log(1 - seed.random()) # (1-seed.random()) in (0, 1]
1412 if kernel_integral(i / n, j / n, 1) <= r:
1413 i, j = i + 1, i + 1
1414 else:
1415 j = math.ceil(n * kernel_root(i / n, j / n, r))
1416 graph.add_edge(i - 1, j - 1)
1417 return graph
1418
1419
1420@py_random_state("seed")
1421@nx._dispatchable(graphs=None, returns_graph=True)
1422def random_k_lift(G, k, seed=None):
1423 r"""Return a `k`-lift of a graph using random permutations.
1424
1425 The resulting graph ``H`` has `k` copies of each node from `G`.
1426 For each edge ``(u, v)`` in `G`, a random permutation is used to connect the ``i``-th copy of ``u``
1427 to the permuted ``i``-th copy of ``v`` in ``H``.
1428
1429 Parameters
1430 ----------
1431 G : Graph, DiGraph, MultiGraph, or MultiDiGraph
1432 The base graph to lift. Any standard NetworkX graph type is supported.
1433 k : int
1434 The lift parameter. Each node in `G` is expanded to `k` copies.
1435 seed : int, RandomState, or None (default: None)
1436 Seed for the random number generator (used for permutation generation).
1437 This ensures reproducibility.
1438 See :ref:`Randomness<randomness>`.
1439
1440 Returns
1441 -------
1442 H : Graph, DiGraph, MultiGraph, or MultiDiGraph
1443 The resulting `k`-lifted graph. The returned type matches the input graph type.
1444
1445 Notes
1446 -----
1447 Given a base graph `G` and a lift parameter `k`, this function performs a `k`-lift as follows:
1448 - For each node ``v`` in `G`, it creates `k` copies: ``(v, 0), ..., (v, k - 1)``.
1449 - For each edge ``(u, v)`` in `G`, a random permutation ``σ`` is applied to determine new edges:
1450 if ``σ(i) = j``, then ``((u, i), (v, j))`` is added to ``H``.
1451 The permutation is simulated by creating a shuffled list ``permutation`` of values 0 to ``k - 1``.
1452 Each ``i``-th copy of ``u`` is then connected to the ``permutation[i]``-th copy of ``v``.
1453
1454 This operation is often used in the construction of expander graphs [1].
1455 If the base graph is a decent expander (i.e., has a good spectral gap-the
1456 difference between the two largest eigenvalues (in absolute value) of its adjacency matrix),
1457 then its `k`-lifts are also expanders, with the spectral gap preserved
1458 (or slightly reduced and stabilizing for larger `k` [3]) with high probability,
1459 while producing a larger graph of the same degree.
1460
1461 For arbitrary input graphs, the lifted graph may be disconnected.
1462 Disconnected lifts occur more often when the base graph is a poor expander.
1463 Since enforcing connectivity in such cases is unlikely to
1464 produce a good expander, this implementation returns the lift directly,
1465 even if it is disconnected. Users who require connected results may wrap
1466 this function with retries until a connected lift is found.
1467
1468 This implementation supports all standard NetworkX graph types.
1469 While expander graphs are most commonly studied as degree-regular undirected simple graphs/multigraphs,
1470 the `k`-lift operation itself is well-defined and can also be beneficial for directed [4]
1471 and non-regular [5] graphs.
1472
1473 References
1474 ----------
1475 [1] Y. Bilu and N. Linial, "Lifts, Discrepancy and Nearly Optimal Spectral Gap."
1476 *Combinatorica*, 26(5), pp. 495–519, 2006,
1477 https://www.cs.huji.ac.il/~nati/PAPERS/raman_lift.pdf
1478 [2] A. Valadarsky, G. Shahaf, M. Dinitz and M. Schapira,
1479 "Xpander: Towards Optimal-Performance Datacenters.",
1480 In *Proceedings of the 12th International Conference on
1481 Emerging Networking Experiments and Technologies (CoNEXT)*, 2016,
1482 https://dl.acm.org/doi/pdf/10.1145/2999572.2999580
1483 [3] N. Agarwal, K. Chandrasekaran, A. Kolla and V. Madan,
1484 "On the Expansion of Group-Based Lifts.", arXiv preprint arXiv:1311.3268v2, 2016,
1485 http://arxiv.org/abs/1311.3268v2
1486 [4] P. Chebolu and A. Frieze,
1487 "Hamilton Cycles in Random Lifts of Directed Graphs.",
1488 Department of Mathematics, Carnegie Mellon University, 2007,
1489 https://www.math.cmu.edu/~af1p/Texfiles/LiftHamDir.pdf
1490 [5] S. Hoory,
1491 "On the Girth of Graph Lifts." arXiv:2401.01238v1, 2024,
1492 https://arxiv.org/pdf/2401.01238
1493
1494 Examples
1495 --------
1496 >>> G = nx.complete_graph(4) # 3-regular, connected graph
1497 >>> H = nx.random_k_lift(G, 4, seed=42) # 4-lift of G
1498 >>> H.number_of_nodes()
1499 16
1500 """
1501 H = G.__class__()
1502
1503 # Create k copies of each node
1504 H.add_nodes_from((v, i) for v in G.nodes for i in range(k))
1505
1506 # Apply random permutation to edges
1507 edges = []
1508 permutation = list(range(k))
1509 for u, v in G.edges():
1510 seed.shuffle(permutation)
1511 edges.extend(((u, i), (v, permutation[i])) for i in range(k))
1512 H.add_edges_from(edges)
1513
1514 return H