comparison planemo/lib/python3.7/site-packages/networkx/generators/classic.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 # Copyright (C) 2004-2019 by
2 # Aric Hagberg <hagberg@lanl.gov>
3 # Dan Schult <dschult@colgate.edu>
4 # Pieter Swart <swart@lanl.gov>
5 # All rights reserved.
6 # BSD license.
7 #
8 # Authors: Aric Hagberg (hagberg@lanl.gov)
9 # Pieter Swart (swart@lanl.gov)
10 """Generators for some classic graphs.
11
12 The typical graph generator is called as follows:
13
14 >>> G = nx.complete_graph(100)
15
16 returning the complete graph on n nodes labeled 0, .., 99
17 as a simple graph. Except for empty_graph, all the generators
18 in this module return a Graph class (i.e. a simple, undirected graph).
19
20 """
21
22 import itertools
23
24 import networkx as nx
25 from networkx.classes import Graph
26 from networkx.exception import NetworkXError
27 from networkx.utils import accumulate
28 from networkx.utils import nodes_or_number
29 from networkx.utils import pairwise
30
31 __all__ = ['balanced_tree',
32 'barbell_graph',
33 'binomial_tree',
34 'complete_graph',
35 'complete_multipartite_graph',
36 'circular_ladder_graph',
37 'circulant_graph',
38 'cycle_graph',
39 'dorogovtsev_goltsev_mendes_graph',
40 'empty_graph',
41 'full_rary_tree',
42 'ladder_graph',
43 'lollipop_graph',
44 'null_graph',
45 'path_graph',
46 'star_graph',
47 'trivial_graph',
48 'turan_graph',
49 'wheel_graph']
50
51
52 # -------------------------------------------------------------------
53 # Some Classic Graphs
54 # -------------------------------------------------------------------
55
56 def _tree_edges(n, r):
57 if n == 0:
58 return
59 # helper function for trees
60 # yields edges in rooted tree at 0 with n nodes and branching ratio r
61 nodes = iter(range(n))
62 parents = [next(nodes)] # stack of max length r
63 while parents:
64 source = parents.pop(0)
65 for i in range(r):
66 try:
67 target = next(nodes)
68 parents.append(target)
69 yield source, target
70 except StopIteration:
71 break
72
73
74 def full_rary_tree(r, n, create_using=None):
75 """Creates a full r-ary tree of n vertices.
76
77 Sometimes called a k-ary, n-ary, or m-ary tree.
78 "... all non-leaf vertices have exactly r children and all levels
79 are full except for some rightmost position of the bottom level
80 (if a leaf at the bottom level is missing, then so are all of the
81 leaves to its right." [1]_
82
83 Parameters
84 ----------
85 r : int
86 branching factor of the tree
87 n : int
88 Number of nodes in the tree
89 create_using : NetworkX graph constructor, optional (default=nx.Graph)
90 Graph type to create. If graph instance, then cleared before populated.
91
92 Returns
93 -------
94 G : networkx Graph
95 An r-ary tree with n nodes
96
97 References
98 ----------
99 .. [1] An introduction to data structures and algorithms,
100 James Andrew Storer, Birkhauser Boston 2001, (page 225).
101 """
102 G = empty_graph(n, create_using)
103 G.add_edges_from(_tree_edges(n, r))
104 return G
105
106
107 def balanced_tree(r, h, create_using=None):
108 """Returns the perfectly balanced `r`-ary tree of height `h`.
109
110 Parameters
111 ----------
112 r : int
113 Branching factor of the tree; each node will have `r`
114 children.
115
116 h : int
117 Height of the tree.
118
119 create_using : NetworkX graph constructor, optional (default=nx.Graph)
120 Graph type to create. If graph instance, then cleared before populated.
121
122 Returns
123 -------
124 G : NetworkX graph
125 A balanced `r`-ary tree of height `h`.
126
127 Notes
128 -----
129 This is the rooted tree where all leaves are at distance `h` from
130 the root. The root has degree `r` and all other internal nodes
131 have degree `r + 1`.
132
133 Node labels are integers, starting from zero.
134
135 A balanced tree is also known as a *complete r-ary tree*.
136
137 """
138 # The number of nodes in the balanced tree is `1 + r + ... + r^h`,
139 # which is computed by using the closed-form formula for a geometric
140 # sum with ratio `r`. In the special case that `r` is 1, the number
141 # of nodes is simply `h + 1` (since the tree is actually a path
142 # graph).
143 if r == 1:
144 n = h + 1
145 else:
146 # This must be an integer if both `r` and `h` are integers. If
147 # they are not, we force integer division anyway.
148 n = (1 - r ** (h + 1)) // (1 - r)
149 return full_rary_tree(r, n, create_using=create_using)
150
151
152 def barbell_graph(m1, m2, create_using=None):
153 """Returns the Barbell Graph: two complete graphs connected by a path.
154
155 For $m1 > 1$ and $m2 >= 0$.
156
157 Two identical complete graphs $K_{m1}$ form the left and right bells,
158 and are connected by a path $P_{m2}$.
159
160 The `2*m1+m2` nodes are numbered
161 `0, ..., m1-1` for the left barbell,
162 `m1, ..., m1+m2-1` for the path,
163 and `m1+m2, ..., 2*m1+m2-1` for the right barbell.
164
165 The 3 subgraphs are joined via the edges `(m1-1, m1)` and
166 `(m1+m2-1, m1+m2)`. If `m2=0`, this is merely two complete
167 graphs joined together.
168
169 This graph is an extremal example in David Aldous
170 and Jim Fill's e-text on Random Walks on Graphs.
171
172 """
173 if m1 < 2:
174 raise NetworkXError(
175 "Invalid graph description, m1 should be >=2")
176 if m2 < 0:
177 raise NetworkXError(
178 "Invalid graph description, m2 should be >=0")
179
180 # left barbell
181 G = complete_graph(m1, create_using)
182 if G.is_directed():
183 raise NetworkXError("Directed Graph not supported")
184
185 # connecting path
186 G.add_nodes_from(range(m1, m1 + m2 - 1))
187 if m2 > 1:
188 G.add_edges_from(pairwise(range(m1, m1 + m2)))
189 # right barbell
190 G.add_edges_from((u, v) for u in range(m1 + m2, 2 * m1 + m2)
191 for v in range(u + 1, 2 * m1 + m2))
192 # connect it up
193 G.add_edge(m1 - 1, m1)
194 if m2 > 0:
195 G.add_edge(m1 + m2 - 1, m1 + m2)
196 return G
197
198 def binomial_tree(n):
199 """Returns the Binomial Tree of order n.
200
201 The binomial tree of order 0 consists of a single vertex. A binomial tree of order k
202 is defined recursively by linking two binomial trees of order k-1: the root of one is
203 the leftmost child of the root of the other.
204
205 Parameters
206 ----------
207 n : int
208 Order of the binomial tree.
209
210 Returns
211 -------
212 G : NetworkX graph
213 A binomial tree of $2^n$ vertices and $2^n - 1$ edges.
214
215 """
216 G = nx.empty_graph(1)
217 N = 1
218 for i in range(n):
219 edges = [(u + N, v + N) for (u, v) in G.edges]
220 G.add_edges_from(edges)
221 G.add_edge(0,N)
222 N *= 2
223 return G
224
225 @nodes_or_number(0)
226 def complete_graph(n, create_using=None):
227 """ Return the complete graph `K_n` with n nodes.
228
229 Parameters
230 ----------
231 n : int or iterable container of nodes
232 If n is an integer, nodes are from range(n).
233 If n is a container of nodes, those nodes appear in the graph.
234 create_using : NetworkX graph constructor, optional (default=nx.Graph)
235 Graph type to create. If graph instance, then cleared before populated.
236
237 Examples
238 --------
239 >>> G = nx.complete_graph(9)
240 >>> len(G)
241 9
242 >>> G.size()
243 36
244 >>> G = nx.complete_graph(range(11, 14))
245 >>> list(G.nodes())
246 [11, 12, 13]
247 >>> G = nx.complete_graph(4, nx.DiGraph())
248 >>> G.is_directed()
249 True
250
251 """
252 n_name, nodes = n
253 G = empty_graph(n_name, create_using)
254 if len(nodes) > 1:
255 if G.is_directed():
256 edges = itertools.permutations(nodes, 2)
257 else:
258 edges = itertools.combinations(nodes, 2)
259 G.add_edges_from(edges)
260 return G
261
262
263 def circular_ladder_graph(n, create_using=None):
264 """Returns the circular ladder graph $CL_n$ of length n.
265
266 $CL_n$ consists of two concentric n-cycles in which
267 each of the n pairs of concentric nodes are joined by an edge.
268
269 Node labels are the integers 0 to n-1
270
271 """
272 G = ladder_graph(n, create_using)
273 G.add_edge(0, n - 1)
274 G.add_edge(n, 2 * n - 1)
275 return G
276
277
278 def circulant_graph(n, offsets, create_using=None):
279 """Generates the circulant graph $Ci_n(x_1, x_2, ..., x_m)$ with $n$ vertices.
280
281 Returns
282 -------
283 The graph $Ci_n(x_1, ..., x_m)$ consisting of $n$ vertices $0, ..., n-1$ such
284 that the vertex with label $i$ is connected to the vertices labelled $(i + x)$
285 and $(i - x)$, for all $x$ in $x_1$ up to $x_m$, with the indices taken modulo $n$.
286
287 Parameters
288 ----------
289 n : integer
290 The number of vertices the generated graph is to contain.
291 offsets : list of integers
292 A list of vertex offsets, $x_1$ up to $x_m$, as described above.
293 create_using : NetworkX graph constructor, optional (default=nx.Graph)
294 Graph type to create. If graph instance, then cleared before populated.
295
296 Examples
297 --------
298 Many well-known graph families are subfamilies of the circulant graphs;
299 for example, to generate the cycle graph on n points, we connect every
300 vertex to every other at offset plus or minus one. For n = 10,
301
302 >>> import networkx
303 >>> G = networkx.generators.classic.circulant_graph(10, [1])
304 >>> edges = [
305 ... (0, 9), (0, 1), (1, 2), (2, 3), (3, 4),
306 ... (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)]
307 ...
308 >>> sorted(edges) == sorted(G.edges())
309 True
310
311 Similarly, we can generate the complete graph on 5 points with the set of
312 offsets [1, 2]:
313
314 >>> G = networkx.generators.classic.circulant_graph(5, [1, 2])
315 >>> edges = [
316 ... (0, 1), (0, 2), (0, 3), (0, 4), (1, 2),
317 ... (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
318 ...
319 >>> sorted(edges) == sorted(G.edges())
320 True
321
322 """
323 G = empty_graph(n, create_using)
324 for i in range(n):
325 for j in offsets:
326 G.add_edge(i, (i - j) % n)
327 G.add_edge(i, (i + j) % n)
328 return G
329
330
331 @nodes_or_number(0)
332 def cycle_graph(n, create_using=None):
333 """Returns the cycle graph $C_n$ of cyclically connected nodes.
334
335 $C_n$ is a path with its two end-nodes connected.
336
337 Parameters
338 ----------
339 n : int or iterable container of nodes
340 If n is an integer, nodes are from `range(n)`.
341 If n is a container of nodes, those nodes appear in the graph.
342 create_using : NetworkX graph constructor, optional (default=nx.Graph)
343 Graph type to create. If graph instance, then cleared before populated.
344
345 Notes
346 -----
347 If create_using is directed, the direction is in increasing order.
348
349 """
350 n_orig, nodes = n
351 G = empty_graph(nodes, create_using)
352 G.add_edges_from(pairwise(nodes))
353 G.add_edge(nodes[-1], nodes[0])
354 return G
355
356
357 def dorogovtsev_goltsev_mendes_graph(n, create_using=None):
358 """Returns the hierarchically constructed Dorogovtsev-Goltsev-Mendes graph.
359
360 n is the generation.
361 See: arXiv:/cond-mat/0112143 by Dorogovtsev, Goltsev and Mendes.
362
363 """
364 G = empty_graph(0, create_using)
365 if G.is_directed():
366 raise NetworkXError("Directed Graph not supported")
367 if G.is_multigraph():
368 raise NetworkXError("Multigraph not supported")
369
370 G.add_edge(0, 1)
371 if n == 0:
372 return G
373 new_node = 2 # next node to be added
374 for i in range(1, n + 1): # iterate over number of generations.
375 last_generation_edges = list(G.edges())
376 number_of_edges_in_last_generation = len(last_generation_edges)
377 for j in range(0, number_of_edges_in_last_generation):
378 G.add_edge(new_node, last_generation_edges[j][0])
379 G.add_edge(new_node, last_generation_edges[j][1])
380 new_node += 1
381 return G
382
383
384 @nodes_or_number(0)
385 def empty_graph(n=0, create_using=None, default=nx.Graph):
386 """Returns the empty graph with n nodes and zero edges.
387
388 Parameters
389 ----------
390 n : int or iterable container of nodes (default = 0)
391 If n is an integer, nodes are from `range(n)`.
392 If n is a container of nodes, those nodes appear in the graph.
393 create_using : Graph Instance, Constructor or None
394 Indicator of type of graph to return.
395 If a Graph-type instance, then clear and use it.
396 If None, use the `default` constructor.
397 If a constructor, call it to create an empty graph.
398 default : Graph constructor (optional, default = nx.Graph)
399 The constructor to use if create_using is None.
400 If None, then nx.Graph is used.
401 This is used when passing an unknown `create_using` value
402 through your home-grown function to `empty_graph` and
403 you want a default constructor other than nx.Graph.
404
405 Examples
406 --------
407 >>> G = nx.empty_graph(10)
408 >>> G.number_of_nodes()
409 10
410 >>> G.number_of_edges()
411 0
412 >>> G = nx.empty_graph("ABC")
413 >>> G.number_of_nodes()
414 3
415 >>> sorted(G)
416 ['A', 'B', 'C']
417
418 Notes
419 -----
420 The variable create_using should be a Graph Constructor or a
421 "graph"-like object. Constructors, e.g. `nx.Graph` or `nx.MultiGraph`
422 will be used to create the returned graph. "graph"-like objects
423 will be cleared (nodes and edges will be removed) and refitted as
424 an empty "graph" with nodes specified in n. This capability
425 is useful for specifying the class-nature of the resulting empty
426 "graph" (i.e. Graph, DiGraph, MyWeirdGraphClass, etc.).
427
428 The variable create_using has three main uses:
429 Firstly, the variable create_using can be used to create an
430 empty digraph, multigraph, etc. For example,
431
432 >>> n = 10
433 >>> G = nx.empty_graph(n, create_using=nx.DiGraph)
434
435 will create an empty digraph on n nodes.
436
437 Secondly, one can pass an existing graph (digraph, multigraph,
438 etc.) via create_using. For example, if G is an existing graph
439 (resp. digraph, multigraph, etc.), then empty_graph(n, create_using=G)
440 will empty G (i.e. delete all nodes and edges using G.clear())
441 and then add n nodes and zero edges, and return the modified graph.
442
443 Thirdly, when constructing your home-grown graph creation function
444 you can use empty_graph to construct the graph by passing a user
445 defined create_using to empty_graph. In this case, if you want the
446 default constructor to be other than nx.Graph, specify `default`.
447
448 >>> def mygraph(n, create_using=None):
449 ... G = nx.empty_graph(n, create_using, nx.MultiGraph)
450 ... G.add_edges_from([(0, 1), (0, 1)])
451 ... return G
452 >>> G = mygraph(3)
453 >>> G.is_multigraph()
454 True
455 >>> G = mygraph(3, nx.Graph)
456 >>> G.is_multigraph()
457 False
458
459 See also create_empty_copy(G).
460
461 """
462 if create_using is None:
463 G = default()
464 elif hasattr(create_using, '_adj'):
465 # create_using is a NetworkX style Graph
466 create_using.clear()
467 G = create_using
468 else:
469 # try create_using as constructor
470 G = create_using()
471
472 n_name, nodes = n
473 G.add_nodes_from(nodes)
474 return G
475
476
477 def ladder_graph(n, create_using=None):
478 """Returns the Ladder graph of length n.
479
480 This is two paths of n nodes, with
481 each pair connected by a single edge.
482
483 Node labels are the integers 0 to 2*n - 1.
484
485 """
486 G = empty_graph(2 * n, create_using)
487 if G.is_directed():
488 raise NetworkXError("Directed Graph not supported")
489 G.add_edges_from(pairwise(range(n)))
490 G.add_edges_from(pairwise(range(n, 2 * n)))
491 G.add_edges_from((v, v + n) for v in range(n))
492 return G
493
494
495 @nodes_or_number([0, 1])
496 def lollipop_graph(m, n, create_using=None):
497 """Returns the Lollipop Graph; `K_m` connected to `P_n`.
498
499 This is the Barbell Graph without the right barbell.
500
501 Parameters
502 ----------
503 m, n : int or iterable container of nodes (default = 0)
504 If an integer, nodes are from `range(m)` and `range(m,m+n)`.
505 If a container, the entries are the coordinate of the node.
506
507 The nodes for m appear in the complete graph $K_m$ and the nodes
508 for n appear in the path $P_n$
509 create_using : NetworkX graph constructor, optional (default=nx.Graph)
510 Graph type to create. If graph instance, then cleared before populated.
511
512 Notes
513 -----
514 The 2 subgraphs are joined via an edge (m-1, m).
515 If n=0, this is merely a complete graph.
516
517 (This graph is an extremal example in David Aldous and Jim
518 Fill's etext on Random Walks on Graphs.)
519
520 """
521 m, m_nodes = m
522 n, n_nodes = n
523 M = len(m_nodes)
524 N = len(n_nodes)
525 if isinstance(m, int):
526 n_nodes = [len(m_nodes) + i for i in n_nodes]
527 if M < 2:
528 raise NetworkXError(
529 "Invalid graph description, m should be >=2")
530 if N < 0:
531 raise NetworkXError(
532 "Invalid graph description, n should be >=0")
533
534 # the ball
535 G = complete_graph(m_nodes, create_using)
536 if G.is_directed():
537 raise NetworkXError("Directed Graph not supported")
538 # the stick
539 G.add_nodes_from(n_nodes)
540 if N > 1:
541 G.add_edges_from(pairwise(n_nodes))
542 # connect ball to stick
543 if M > 0 and N > 0:
544 G.add_edge(m_nodes[-1], n_nodes[0])
545 return G
546
547
548 def null_graph(create_using=None):
549 """Returns the Null graph with no nodes or edges.
550
551 See empty_graph for the use of create_using.
552
553 """
554 G = empty_graph(0, create_using)
555 return G
556
557
558 @nodes_or_number(0)
559 def path_graph(n, create_using=None):
560 """Returns the Path graph `P_n` of linearly connected nodes.
561
562 Parameters
563 ----------
564 n : int or iterable
565 If an integer, node labels are 0 to n with center 0.
566 If an iterable of nodes, the center is the first.
567 create_using : NetworkX graph constructor, optional (default=nx.Graph)
568 Graph type to create. If graph instance, then cleared before populated.
569
570 """
571 n_name, nodes = n
572 G = empty_graph(nodes, create_using)
573 G.add_edges_from(pairwise(nodes))
574 return G
575
576
577 @nodes_or_number(0)
578 def star_graph(n, create_using=None):
579 """ Return the star graph
580
581 The star graph consists of one center node connected to n outer nodes.
582
583 Parameters
584 ----------
585 n : int or iterable
586 If an integer, node labels are 0 to n with center 0.
587 If an iterable of nodes, the center is the first.
588 create_using : NetworkX graph constructor, optional (default=nx.Graph)
589 Graph type to create. If graph instance, then cleared before populated.
590
591 Notes
592 -----
593 The graph has n+1 nodes for integer n.
594 So star_graph(3) is the same as star_graph(range(4)).
595 """
596 n_name, nodes = n
597 if isinstance(n_name, int):
598 nodes = nodes + [n_name] # there should be n+1 nodes
599 first = nodes[0]
600 G = empty_graph(nodes, create_using)
601 if G.is_directed():
602 raise NetworkXError("Directed Graph not supported")
603 G.add_edges_from((first, v) for v in nodes[1:])
604 return G
605
606
607 def trivial_graph(create_using=None):
608 """ Return the Trivial graph with one node (with label 0) and no edges.
609
610 """
611 G = empty_graph(1, create_using)
612 return G
613
614
615 def turan_graph(n, r):
616 r""" Return the Turan Graph
617
618 The Turan Graph is a complete multipartite graph on $n$ vertices
619 with $r$ disjoint subsets. It is the graph with the edges for any graph with
620 $n$ vertices and $r$ disjoint subsets.
621
622 Given $n$ and $r$, we generate a complete multipartite graph with
623 $r-(n \mod r)$ partitions of size $n/r$, rounded down, and
624 $n \mod r$ partitions of size $n/r+1$, rounded down.
625
626 Parameters
627 ----------
628 n : int
629 The number of vertices.
630 r : int
631 The number of partitions.
632 Must be less than or equal to n.
633
634 Notes
635 -----
636 Must satisfy $1 <= r <= n$.
637 The graph has $(r-1)(n^2)/(2r)$ edges, rounded down.
638 """
639
640 if not 1 <= r <= n:
641 raise NetworkXError("Must satisfy 1 <= r <= n")
642
643 partitions = [n // r] * (r - (n % r)) + [n // r + 1] * (n % r)
644 G = complete_multipartite_graph(*partitions)
645 return G
646
647
648 @nodes_or_number(0)
649 def wheel_graph(n, create_using=None):
650 """ Return the wheel graph
651
652 The wheel graph consists of a hub node connected to a cycle of (n-1) nodes.
653
654 Parameters
655 ----------
656 n : int or iterable
657 If an integer, node labels are 0 to n with center 0.
658 If an iterable of nodes, the center is the first.
659 create_using : NetworkX graph constructor, optional (default=nx.Graph)
660 Graph type to create. If graph instance, then cleared before populated.
661
662 Node labels are the integers 0 to n - 1.
663 """
664 n_name, nodes = n
665 if n_name == 0:
666 G = empty_graph(0, create_using)
667 return G
668 G = star_graph(nodes, create_using)
669 if len(G) > 2:
670 G.add_edges_from(pairwise(nodes[1:]))
671 G.add_edge(nodes[-1], nodes[1])
672 return G
673
674
675 def complete_multipartite_graph(*subset_sizes):
676 """Returns the complete multipartite graph with the specified subset sizes.
677
678 Parameters
679 ----------
680 subset_sizes : tuple of integers or tuple of node iterables
681 The arguments can either all be integer number of nodes or they
682 can all be iterables of nodes. If integers, they represent the
683 number of vertices in each subset of the multipartite graph.
684 If iterables, each is used to create the nodes for that subset.
685 The length of subset_sizes is the number of subsets.
686
687 Returns
688 -------
689 G : NetworkX Graph
690 Returns the complete multipartite graph with the specified subsets.
691
692 For each node, the node attribute 'subset' is an integer
693 indicating which subset contains the node.
694
695 Examples
696 --------
697 Creating a complete tripartite graph, with subsets of one, two, and three
698 vertices, respectively.
699
700 >>> import networkx as nx
701 >>> G = nx.complete_multipartite_graph(1, 2, 3)
702 >>> [G.nodes[u]['subset'] for u in G]
703 [0, 1, 1, 2, 2, 2]
704 >>> list(G.edges(0))
705 [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
706 >>> list(G.edges(2))
707 [(2, 0), (2, 3), (2, 4), (2, 5)]
708 >>> list(G.edges(4))
709 [(4, 0), (4, 1), (4, 2)]
710
711 >>> G = nx.complete_multipartite_graph('a', 'bc', 'def')
712 >>> [G.nodes[u]['subset'] for u in sorted(G)]
713 [0, 1, 1, 2, 2, 2]
714
715 Notes
716 -----
717 This function generalizes several other graph generator functions.
718
719 - If no subset sizes are given, this returns the null graph.
720 - If a single subset size `n` is given, this returns the empty graph on
721 `n` nodes.
722 - If two subset sizes `m` and `n` are given, this returns the complete
723 bipartite graph on `m + n` nodes.
724 - If subset sizes `1` and `n` are given, this returns the star graph on
725 `n + 1` nodes.
726
727 See also
728 --------
729 complete_bipartite_graph
730 """
731 # The complete multipartite graph is an undirected simple graph.
732 G = Graph()
733
734 if len(subset_sizes) == 0:
735 return G
736
737 # set up subsets of nodes
738 try:
739 extents = pairwise(accumulate((0,) + subset_sizes))
740 subsets = [range(start, end) for start, end in extents]
741 except TypeError:
742 subsets = subset_sizes
743
744 # add nodes with subset attribute
745 # while checking that ints are not mixed with iterables
746 try:
747 for (i, subset) in enumerate(subsets):
748 G.add_nodes_from(subset, subset=i)
749 except TypeError:
750 raise NetworkXError("Arguments must be all ints or all iterables")
751
752 # Across subsets, all vertices should be adjacent.
753 # We can use itertools.combinations() because undirected.
754 for subset1, subset2 in itertools.combinations(subsets, 2):
755 G.add_edges_from(itertools.product(subset1, subset2))
756 return G