aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/generators/classic.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/generators/classic.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/generators/classic.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/generators/classic.py1068
1 files changed, 1068 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/generators/classic.py b/.venv/lib/python3.12/site-packages/networkx/generators/classic.py
new file mode 100644
index 00000000..a461e7bd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/generators/classic.py
@@ -0,0 +1,1068 @@
+"""Generators for some classic graphs.
+
+The typical graph builder function is called as follows:
+
+>>> G = nx.complete_graph(100)
+
+returning the complete graph on n nodes labeled 0, .., 99
+as a simple graph. Except for `empty_graph`, all the functions
+in this module return a Graph class (i.e. a simple, undirected graph).
+
+"""
+
+import itertools
+import numbers
+
+import networkx as nx
+from networkx.classes import Graph
+from networkx.exception import NetworkXError
+from networkx.utils import nodes_or_number, pairwise
+
+__all__ = [
+ "balanced_tree",
+ "barbell_graph",
+ "binomial_tree",
+ "complete_graph",
+ "complete_multipartite_graph",
+ "circular_ladder_graph",
+ "circulant_graph",
+ "cycle_graph",
+ "dorogovtsev_goltsev_mendes_graph",
+ "empty_graph",
+ "full_rary_tree",
+ "kneser_graph",
+ "ladder_graph",
+ "lollipop_graph",
+ "null_graph",
+ "path_graph",
+ "star_graph",
+ "tadpole_graph",
+ "trivial_graph",
+ "turan_graph",
+ "wheel_graph",
+]
+
+
+# -------------------------------------------------------------------
+# Some Classic Graphs
+# -------------------------------------------------------------------
+
+
+def _tree_edges(n, r):
+ if n == 0:
+ return
+ # helper function for trees
+ # yields edges in rooted tree at 0 with n nodes and branching ratio r
+ nodes = iter(range(n))
+ parents = [next(nodes)] # stack of max length r
+ while parents:
+ source = parents.pop(0)
+ for i in range(r):
+ try:
+ target = next(nodes)
+ parents.append(target)
+ yield source, target
+ except StopIteration:
+ break
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def full_rary_tree(r, n, create_using=None):
+ """Creates a full r-ary tree of `n` nodes.
+
+ Sometimes called a k-ary, n-ary, or m-ary tree.
+ "... all non-leaf nodes have exactly r children and all levels
+ are full except for some rightmost position of the bottom level
+ (if a leaf at the bottom level is missing, then so are all of the
+ leaves to its right." [1]_
+
+ .. plot::
+
+ >>> nx.draw(nx.full_rary_tree(2, 10))
+
+ Parameters
+ ----------
+ r : int
+ branching factor of the tree
+ n : int
+ Number of nodes in the tree
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Returns
+ -------
+ G : networkx Graph
+ An r-ary tree with n nodes
+
+ References
+ ----------
+ .. [1] An introduction to data structures and algorithms,
+ James Andrew Storer, Birkhauser Boston 2001, (page 225).
+ """
+ G = empty_graph(n, create_using)
+ G.add_edges_from(_tree_edges(n, r))
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def kneser_graph(n, k):
+ """Returns the Kneser Graph with parameters `n` and `k`.
+
+ The Kneser Graph has nodes that are k-tuples (subsets) of the integers
+ between 0 and ``n-1``. Nodes are adjacent if their corresponding sets are disjoint.
+
+ Parameters
+ ----------
+ n: int
+ Number of integers from which to make node subsets.
+ Subsets are drawn from ``set(range(n))``.
+ k: int
+ Size of the subsets.
+
+ Returns
+ -------
+ G : NetworkX Graph
+
+ Examples
+ --------
+ >>> G = nx.kneser_graph(5, 2)
+ >>> G.number_of_nodes()
+ 10
+ >>> G.number_of_edges()
+ 15
+ >>> nx.is_isomorphic(G, nx.petersen_graph())
+ True
+ """
+ if n <= 0:
+ raise NetworkXError("n should be greater than zero")
+ if k <= 0 or k > n:
+ raise NetworkXError("k should be greater than zero and smaller than n")
+
+ G = nx.Graph()
+ # Create all k-subsets of [0, 1, ..., n-1]
+ subsets = list(itertools.combinations(range(n), k))
+
+ if 2 * k > n:
+ G.add_nodes_from(subsets)
+
+ universe = set(range(n))
+ comb = itertools.combinations # only to make it all fit on one line
+ G.add_edges_from((s, t) for s in subsets for t in comb(universe - set(s), k))
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def balanced_tree(r, h, create_using=None):
+ """Returns the perfectly balanced `r`-ary tree of height `h`.
+
+ .. plot::
+
+ >>> nx.draw(nx.balanced_tree(2, 3))
+
+ Parameters
+ ----------
+ r : int
+ Branching factor of the tree; each node will have `r`
+ children.
+
+ h : int
+ Height of the tree.
+
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Returns
+ -------
+ G : NetworkX graph
+ A balanced `r`-ary tree of height `h`.
+
+ Notes
+ -----
+ This is the rooted tree where all leaves are at distance `h` from
+ the root. The root has degree `r` and all other internal nodes
+ have degree `r + 1`.
+
+ Node labels are integers, starting from zero.
+
+ A balanced tree is also known as a *complete r-ary tree*.
+
+ """
+ # The number of nodes in the balanced tree is `1 + r + ... + r^h`,
+ # which is computed by using the closed-form formula for a geometric
+ # sum with ratio `r`. In the special case that `r` is 1, the number
+ # of nodes is simply `h + 1` (since the tree is actually a path
+ # graph).
+ if r == 1:
+ n = h + 1
+ else:
+ # This must be an integer if both `r` and `h` are integers. If
+ # they are not, we force integer division anyway.
+ n = (1 - r ** (h + 1)) // (1 - r)
+ return full_rary_tree(r, n, create_using=create_using)
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def barbell_graph(m1, m2, create_using=None):
+ """Returns the Barbell Graph: two complete graphs connected by a path.
+
+ .. plot::
+
+ >>> nx.draw(nx.barbell_graph(4, 2))
+
+ Parameters
+ ----------
+ m1 : int
+ Size of the left and right barbells, must be greater than 2.
+
+ m2 : int
+ Length of the path connecting the barbells.
+
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+ Only undirected Graphs are supported.
+
+ Returns
+ -------
+ G : NetworkX graph
+ A barbell graph.
+
+ Notes
+ -----
+
+
+ Two identical complete graphs $K_{m1}$ form the left and right bells,
+ and are connected by a path $P_{m2}$.
+
+ The `2*m1+m2` nodes are numbered
+ `0, ..., m1-1` for the left barbell,
+ `m1, ..., m1+m2-1` for the path,
+ and `m1+m2, ..., 2*m1+m2-1` for the right barbell.
+
+ The 3 subgraphs are joined via the edges `(m1-1, m1)` and
+ `(m1+m2-1, m1+m2)`. If `m2=0`, this is merely two complete
+ graphs joined together.
+
+ This graph is an extremal example in David Aldous
+ and Jim Fill's e-text on Random Walks on Graphs.
+
+ """
+ if m1 < 2:
+ raise NetworkXError("Invalid graph description, m1 should be >=2")
+ if m2 < 0:
+ raise NetworkXError("Invalid graph description, m2 should be >=0")
+
+ # left barbell
+ G = complete_graph(m1, create_using)
+ if G.is_directed():
+ raise NetworkXError("Directed Graph not supported")
+
+ # connecting path
+ G.add_nodes_from(range(m1, m1 + m2 - 1))
+ if m2 > 1:
+ G.add_edges_from(pairwise(range(m1, m1 + m2)))
+
+ # right barbell
+ G.add_edges_from(
+ (u, v) for u in range(m1 + m2, 2 * m1 + m2) for v in range(u + 1, 2 * m1 + m2)
+ )
+
+ # connect it up
+ G.add_edge(m1 - 1, m1)
+ if m2 > 0:
+ G.add_edge(m1 + m2 - 1, m1 + m2)
+
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def binomial_tree(n, create_using=None):
+ """Returns the Binomial Tree of order n.
+
+ The binomial tree of order 0 consists of a single node. A binomial tree of order k
+ is defined recursively by linking two binomial trees of order k-1: the root of one is
+ the leftmost child of the root of the other.
+
+ .. plot::
+
+ >>> nx.draw(nx.binomial_tree(3))
+
+ Parameters
+ ----------
+ n : int
+ Order of the binomial tree.
+
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Returns
+ -------
+ G : NetworkX graph
+ A binomial tree of $2^n$ nodes and $2^n - 1$ edges.
+
+ """
+ G = nx.empty_graph(1, create_using)
+
+ N = 1
+ for i in range(n):
+ # Use G.edges() to ensure 2-tuples. G.edges is 3-tuple for MultiGraph
+ edges = [(u + N, v + N) for (u, v) in G.edges()]
+ G.add_edges_from(edges)
+ G.add_edge(0, N)
+ N *= 2
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number(0)
+def complete_graph(n, create_using=None):
+ """Return the complete graph `K_n` with n nodes.
+
+ A complete graph on `n` nodes means that all pairs
+ of distinct nodes have an edge connecting them.
+
+ .. plot::
+
+ >>> nx.draw(nx.complete_graph(5))
+
+ Parameters
+ ----------
+ n : int or iterable container of nodes
+ If n is an integer, nodes are from range(n).
+ If n is a container of nodes, those nodes appear in the graph.
+ Warning: n is not checked for duplicates and if present the
+ resulting graph may not be as desired. Make sure you have no duplicates.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Examples
+ --------
+ >>> G = nx.complete_graph(9)
+ >>> len(G)
+ 9
+ >>> G.size()
+ 36
+ >>> G = nx.complete_graph(range(11, 14))
+ >>> list(G.nodes())
+ [11, 12, 13]
+ >>> G = nx.complete_graph(4, nx.DiGraph())
+ >>> G.is_directed()
+ True
+
+ """
+ _, nodes = n
+ G = empty_graph(nodes, create_using)
+ if len(nodes) > 1:
+ if G.is_directed():
+ edges = itertools.permutations(nodes, 2)
+ else:
+ edges = itertools.combinations(nodes, 2)
+ G.add_edges_from(edges)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def circular_ladder_graph(n, create_using=None):
+ """Returns the circular ladder graph $CL_n$ of length n.
+
+ $CL_n$ consists of two concentric n-cycles in which
+ each of the n pairs of concentric nodes are joined by an edge.
+
+ Node labels are the integers 0 to n-1
+
+ .. plot::
+
+ >>> nx.draw(nx.circular_ladder_graph(5))
+
+ """
+ G = ladder_graph(n, create_using)
+ G.add_edge(0, n - 1)
+ G.add_edge(n, 2 * n - 1)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def circulant_graph(n, offsets, create_using=None):
+ r"""Returns the circulant graph $Ci_n(x_1, x_2, ..., x_m)$ with $n$ nodes.
+
+ The circulant graph $Ci_n(x_1, ..., x_m)$ consists of $n$ nodes $0, ..., n-1$
+ such that node $i$ is connected to nodes $(i + x) \mod n$ and $(i - x) \mod n$
+ for all $x$ in $x_1, ..., x_m$. Thus $Ci_n(1)$ is a cycle graph.
+
+ .. plot::
+
+ >>> nx.draw(nx.circulant_graph(10, [1]))
+
+ Parameters
+ ----------
+ n : integer
+ The number of nodes in the graph.
+ offsets : list of integers
+ A list of node offsets, $x_1$ up to $x_m$, as described above.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Returns
+ -------
+ NetworkX Graph of type create_using
+
+ Examples
+ --------
+ Many well-known graph families are subfamilies of the circulant graphs;
+ for example, to create the cycle graph on n points, we connect every
+ node to nodes on either side (with offset plus or minus one). For n = 10,
+
+ >>> G = nx.circulant_graph(10, [1])
+ >>> edges = [
+ ... (0, 9),
+ ... (0, 1),
+ ... (1, 2),
+ ... (2, 3),
+ ... (3, 4),
+ ... (4, 5),
+ ... (5, 6),
+ ... (6, 7),
+ ... (7, 8),
+ ... (8, 9),
+ ... ]
+ >>> sorted(edges) == sorted(G.edges())
+ True
+
+ Similarly, we can create the complete graph
+ on 5 points with the set of offsets [1, 2]:
+
+ >>> G = nx.circulant_graph(5, [1, 2])
+ >>> edges = [
+ ... (0, 1),
+ ... (0, 2),
+ ... (0, 3),
+ ... (0, 4),
+ ... (1, 2),
+ ... (1, 3),
+ ... (1, 4),
+ ... (2, 3),
+ ... (2, 4),
+ ... (3, 4),
+ ... ]
+ >>> sorted(edges) == sorted(G.edges())
+ True
+
+ """
+ G = empty_graph(n, create_using)
+ for i in range(n):
+ for j in offsets:
+ G.add_edge(i, (i - j) % n)
+ G.add_edge(i, (i + j) % n)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number(0)
+def cycle_graph(n, create_using=None):
+ """Returns the cycle graph $C_n$ of cyclically connected nodes.
+
+ $C_n$ is a path with its two end-nodes connected.
+
+ .. plot::
+
+ >>> nx.draw(nx.cycle_graph(5))
+
+ Parameters
+ ----------
+ n : int or iterable container of nodes
+ If n is an integer, nodes are from `range(n)`.
+ If n is a container of nodes, those nodes appear in the graph.
+ Warning: n is not checked for duplicates and if present the
+ resulting graph may not be as desired. Make sure you have no duplicates.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Notes
+ -----
+ If create_using is directed, the direction is in increasing order.
+
+ """
+ _, nodes = n
+ G = empty_graph(nodes, create_using)
+ G.add_edges_from(pairwise(nodes, cyclic=True))
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def dorogovtsev_goltsev_mendes_graph(n, create_using=None):
+ """Returns the hierarchically constructed Dorogovtsev--Goltsev--Mendes graph.
+
+ The Dorogovtsev--Goltsev--Mendes [1]_ procedure deterministically produces a
+ scale-free graph with ``3/2 * (3**(n-1) + 1)`` nodes
+ and ``3**n`` edges for a given `n`.
+
+ Note that `n` denotes the number of times the state transition is applied,
+ starting from the base graph with ``n = 0`` (no transitions), as in [2]_.
+ This is different from the parameter ``t = n - 1`` in [1]_.
+
+ .. plot::
+
+ >>> nx.draw(nx.dorogovtsev_goltsev_mendes_graph(3))
+
+ Parameters
+ ----------
+ n : integer
+ The generation number.
+
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. Directed graphs and multigraphs are not supported.
+
+ Returns
+ -------
+ G : NetworkX `Graph`
+
+ Raises
+ ------
+ NetworkXError
+ If `n` is less than zero.
+
+ If `create_using` is a directed graph or multigraph.
+
+ Examples
+ --------
+ >>> G = nx.dorogovtsev_goltsev_mendes_graph(3)
+ >>> G.number_of_nodes()
+ 15
+ >>> G.number_of_edges()
+ 27
+ >>> nx.is_planar(G)
+ True
+
+ References
+ ----------
+ .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes,
+ "Pseudofractal scale-free web", Physical Review E 65, 066122, 2002.
+ https://arxiv.org/pdf/cond-mat/0112143.pdf
+ .. [2] Weisstein, Eric W. "Dorogovtsev--Goltsev--Mendes Graph".
+ From MathWorld--A Wolfram Web Resource.
+ https://mathworld.wolfram.com/Dorogovtsev-Goltsev-MendesGraph.html
+ """
+ if n < 0:
+ raise NetworkXError("n must be greater than or equal to 0")
+
+ G = empty_graph(0, create_using)
+ if G.is_directed():
+ raise NetworkXError("directed graph not supported")
+ if G.is_multigraph():
+ raise NetworkXError("multigraph not supported")
+
+ G.add_edge(0, 1)
+ new_node = 2 # next node to be added
+ for _ in range(n): # iterate over number of generations.
+ new_edges = []
+ for u, v in G.edges():
+ new_edges.append((u, new_node))
+ new_edges.append((v, new_node))
+ new_node += 1
+
+ G.add_edges_from(new_edges)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number(0)
+def empty_graph(n=0, create_using=None, default=Graph):
+ """Returns the empty graph with n nodes and zero edges.
+
+ .. plot::
+
+ >>> nx.draw(nx.empty_graph(5))
+
+ Parameters
+ ----------
+ n : int or iterable container of nodes (default = 0)
+ If n is an integer, nodes are from `range(n)`.
+ If n is a container of nodes, those nodes appear in the graph.
+ create_using : Graph Instance, Constructor or None
+ Indicator of type of graph to return.
+ If a Graph-type instance, then clear and use it.
+ If None, use the `default` constructor.
+ If a constructor, call it to create an empty graph.
+ default : Graph constructor (optional, default = nx.Graph)
+ The constructor to use if create_using is None.
+ If None, then nx.Graph is used.
+ This is used when passing an unknown `create_using` value
+ through your home-grown function to `empty_graph` and
+ you want a default constructor other than nx.Graph.
+
+ Examples
+ --------
+ >>> G = nx.empty_graph(10)
+ >>> G.number_of_nodes()
+ 10
+ >>> G.number_of_edges()
+ 0
+ >>> G = nx.empty_graph("ABC")
+ >>> G.number_of_nodes()
+ 3
+ >>> sorted(G)
+ ['A', 'B', 'C']
+
+ Notes
+ -----
+ The variable create_using should be a Graph Constructor or a
+ "graph"-like object. Constructors, e.g. `nx.Graph` or `nx.MultiGraph`
+ will be used to create the returned graph. "graph"-like objects
+ will be cleared (nodes and edges will be removed) and refitted as
+ an empty "graph" with nodes specified in n. This capability
+ is useful for specifying the class-nature of the resulting empty
+ "graph" (i.e. Graph, DiGraph, MyWeirdGraphClass, etc.).
+
+ The variable create_using has three main uses:
+ Firstly, the variable create_using can be used to create an
+ empty digraph, multigraph, etc. For example,
+
+ >>> n = 10
+ >>> G = nx.empty_graph(n, create_using=nx.DiGraph)
+
+ will create an empty digraph on n nodes.
+
+ Secondly, one can pass an existing graph (digraph, multigraph,
+ etc.) via create_using. For example, if G is an existing graph
+ (resp. digraph, multigraph, etc.), then empty_graph(n, create_using=G)
+ will empty G (i.e. delete all nodes and edges using G.clear())
+ and then add n nodes and zero edges, and return the modified graph.
+
+ Thirdly, when constructing your home-grown graph creation function
+ you can use empty_graph to construct the graph by passing a user
+ defined create_using to empty_graph. In this case, if you want the
+ default constructor to be other than nx.Graph, specify `default`.
+
+ >>> def mygraph(n, create_using=None):
+ ... G = nx.empty_graph(n, create_using, nx.MultiGraph)
+ ... G.add_edges_from([(0, 1), (0, 1)])
+ ... return G
+ >>> G = mygraph(3)
+ >>> G.is_multigraph()
+ True
+ >>> G = mygraph(3, nx.Graph)
+ >>> G.is_multigraph()
+ False
+
+ See also create_empty_copy(G).
+
+ """
+ if create_using is None:
+ G = default()
+ elif isinstance(create_using, type):
+ G = create_using()
+ elif not hasattr(create_using, "adj"):
+ raise TypeError("create_using is not a valid NetworkX graph type or instance")
+ else:
+ # create_using is a NetworkX style Graph
+ create_using.clear()
+ G = create_using
+
+ _, nodes = n
+ G.add_nodes_from(nodes)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def ladder_graph(n, create_using=None):
+ """Returns the Ladder graph of length n.
+
+ This is two paths of n nodes, with
+ each pair connected by a single edge.
+
+ Node labels are the integers 0 to 2*n - 1.
+
+ .. plot::
+
+ >>> nx.draw(nx.ladder_graph(5))
+
+ """
+ G = empty_graph(2 * n, create_using)
+ if G.is_directed():
+ raise NetworkXError("Directed Graph not supported")
+ G.add_edges_from(pairwise(range(n)))
+ G.add_edges_from(pairwise(range(n, 2 * n)))
+ G.add_edges_from((v, v + n) for v in range(n))
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number([0, 1])
+def lollipop_graph(m, n, create_using=None):
+ """Returns the Lollipop Graph; ``K_m`` connected to ``P_n``.
+
+ This is the Barbell Graph without the right barbell.
+
+ .. plot::
+
+ >>> nx.draw(nx.lollipop_graph(3, 4))
+
+ Parameters
+ ----------
+ m, n : int or iterable container of nodes
+ If an integer, nodes are from ``range(m)`` and ``range(m, m+n)``.
+ If a container of nodes, those nodes appear in the graph.
+ Warning: `m` and `n` are not checked for duplicates and if present the
+ resulting graph may not be as desired. Make sure you have no duplicates.
+
+ The nodes for `m` appear in the complete graph $K_m$ and the nodes
+ for `n` appear in the path $P_n$
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Returns
+ -------
+ Networkx graph
+ A complete graph with `m` nodes connected to a path of length `n`.
+
+ Notes
+ -----
+ The 2 subgraphs are joined via an edge ``(m-1, m)``.
+ If ``n=0``, this is merely a complete graph.
+
+ (This graph is an extremal example in David Aldous and Jim
+ Fill's etext on Random Walks on Graphs.)
+
+ """
+ m, m_nodes = m
+ M = len(m_nodes)
+ if M < 2:
+ raise NetworkXError("Invalid description: m should indicate at least 2 nodes")
+
+ n, n_nodes = n
+ if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral):
+ n_nodes = list(range(M, M + n))
+ N = len(n_nodes)
+
+ # the ball
+ G = complete_graph(m_nodes, create_using)
+ if G.is_directed():
+ raise NetworkXError("Directed Graph not supported")
+
+ # the stick
+ G.add_nodes_from(n_nodes)
+ if N > 1:
+ G.add_edges_from(pairwise(n_nodes))
+
+ if len(G) != M + N:
+ raise NetworkXError("Nodes must be distinct in containers m and n")
+
+ # connect ball to stick
+ if M > 0 and N > 0:
+ G.add_edge(m_nodes[-1], n_nodes[0])
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def null_graph(create_using=None):
+ """Returns the Null graph with no nodes or edges.
+
+ See empty_graph for the use of create_using.
+
+ """
+ G = empty_graph(0, create_using)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number(0)
+def path_graph(n, create_using=None):
+ """Returns the Path graph `P_n` of linearly connected nodes.
+
+ .. plot::
+
+ >>> nx.draw(nx.path_graph(5))
+
+ Parameters
+ ----------
+ n : int or iterable
+ If an integer, nodes are 0 to n - 1.
+ If an iterable of nodes, in the order they appear in the path.
+ Warning: n is not checked for duplicates and if present the
+ resulting graph may not be as desired. Make sure you have no duplicates.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ """
+ _, nodes = n
+ G = empty_graph(nodes, create_using)
+ G.add_edges_from(pairwise(nodes))
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number(0)
+def star_graph(n, create_using=None):
+ """Return the star graph
+
+ The star graph consists of one center node connected to n outer nodes.
+
+ .. plot::
+
+ >>> nx.draw(nx.star_graph(6))
+
+ Parameters
+ ----------
+ n : int or iterable
+ If an integer, node labels are 0 to n with center 0.
+ If an iterable of nodes, the center is the first.
+ Warning: n is not checked for duplicates and if present the
+ resulting graph may not be as desired. Make sure you have no duplicates.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Notes
+ -----
+ The graph has n+1 nodes for integer n.
+ So star_graph(3) is the same as star_graph(range(4)).
+ """
+ n, nodes = n
+ if isinstance(n, numbers.Integral):
+ nodes.append(int(n)) # there should be n+1 nodes
+ G = empty_graph(nodes, create_using)
+ if G.is_directed():
+ raise NetworkXError("Directed Graph not supported")
+
+ if len(nodes) > 1:
+ hub, *spokes = nodes
+ G.add_edges_from((hub, node) for node in spokes)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number([0, 1])
+def tadpole_graph(m, n, create_using=None):
+ """Returns the (m,n)-tadpole graph; ``C_m`` connected to ``P_n``.
+
+ This graph on m+n nodes connects a cycle of size `m` to a path of length `n`.
+ It looks like a tadpole. It is also called a kite graph or a dragon graph.
+
+ .. plot::
+
+ >>> nx.draw(nx.tadpole_graph(3, 5))
+
+ Parameters
+ ----------
+ m, n : int or iterable container of nodes
+ If an integer, nodes are from ``range(m)`` and ``range(m,m+n)``.
+ If a container of nodes, those nodes appear in the graph.
+ Warning: `m` and `n` are not checked for duplicates and if present the
+ resulting graph may not be as desired.
+
+ The nodes for `m` appear in the cycle graph $C_m$ and the nodes
+ for `n` appear in the path $P_n$.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Returns
+ -------
+ Networkx graph
+ A cycle of size `m` connected to a path of length `n`.
+
+ Raises
+ ------
+ NetworkXError
+ If ``m < 2``. The tadpole graph is undefined for ``m<2``.
+
+ Notes
+ -----
+ The 2 subgraphs are joined via an edge ``(m-1, m)``.
+ If ``n=0``, this is a cycle graph.
+ `m` and/or `n` can be a container of nodes instead of an integer.
+
+ """
+ m, m_nodes = m
+ M = len(m_nodes)
+ if M < 2:
+ raise NetworkXError("Invalid description: m should indicate at least 2 nodes")
+
+ n, n_nodes = n
+ if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral):
+ n_nodes = list(range(M, M + n))
+
+ # the circle
+ G = cycle_graph(m_nodes, create_using)
+ if G.is_directed():
+ raise NetworkXError("Directed Graph not supported")
+
+ # the stick
+ nx.add_path(G, [m_nodes[-1]] + list(n_nodes))
+
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def trivial_graph(create_using=None):
+ """Return the Trivial graph with one node (with label 0) and no edges.
+
+ .. plot::
+
+ >>> nx.draw(nx.trivial_graph(), with_labels=True)
+
+ """
+ G = empty_graph(1, create_using)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def turan_graph(n, r):
+ r"""Return the Turan Graph
+
+ The Turan Graph is a complete multipartite graph on $n$ nodes
+ with $r$ disjoint subsets. That is, edges connect each node to
+ every node not in its subset.
+
+ Given $n$ and $r$, we create a complete multipartite graph with
+ $r-(n \mod r)$ partitions of size $n/r$, rounded down, and
+ $n \mod r$ partitions of size $n/r+1$, rounded down.
+
+ .. plot::
+
+ >>> nx.draw(nx.turan_graph(6, 2))
+
+ Parameters
+ ----------
+ n : int
+ The number of nodes.
+ r : int
+ The number of partitions.
+ Must be less than or equal to n.
+
+ Notes
+ -----
+ Must satisfy $1 <= r <= n$.
+ The graph has $(r-1)(n^2)/(2r)$ edges, rounded down.
+ """
+
+ if not 1 <= r <= n:
+ raise NetworkXError("Must satisfy 1 <= r <= n")
+
+ partitions = [n // r] * (r - (n % r)) + [n // r + 1] * (n % r)
+ G = complete_multipartite_graph(*partitions)
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+@nodes_or_number(0)
+def wheel_graph(n, create_using=None):
+ """Return the wheel graph
+
+ The wheel graph consists of a hub node connected to a cycle of (n-1) nodes.
+
+ .. plot::
+
+ >>> nx.draw(nx.wheel_graph(5))
+
+ Parameters
+ ----------
+ n : int or iterable
+ If an integer, node labels are 0 to n with center 0.
+ If an iterable of nodes, the center is the first.
+ Warning: n is not checked for duplicates and if present the
+ resulting graph may not be as desired. Make sure you have no duplicates.
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
+ Graph type to create. If graph instance, then cleared before populated.
+
+ Node labels are the integers 0 to n - 1.
+ """
+ _, nodes = n
+ G = empty_graph(nodes, create_using)
+ if G.is_directed():
+ raise NetworkXError("Directed Graph not supported")
+
+ if len(nodes) > 1:
+ hub, *rim = nodes
+ G.add_edges_from((hub, node) for node in rim)
+ if len(rim) > 1:
+ G.add_edges_from(pairwise(rim, cyclic=True))
+ return G
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def complete_multipartite_graph(*subset_sizes):
+ """Returns the complete multipartite graph with the specified subset sizes.
+
+ .. plot::
+
+ >>> nx.draw(nx.complete_multipartite_graph(1, 2, 3))
+
+ Parameters
+ ----------
+ subset_sizes : tuple of integers or tuple of node iterables
+ The arguments can either all be integer number of nodes or they
+ can all be iterables of nodes. If integers, they represent the
+ number of nodes in each subset of the multipartite graph.
+ If iterables, each is used to create the nodes for that subset.
+ The length of subset_sizes is the number of subsets.
+
+ Returns
+ -------
+ G : NetworkX Graph
+ Returns the complete multipartite graph with the specified subsets.
+
+ For each node, the node attribute 'subset' is an integer
+ indicating which subset contains the node.
+
+ Examples
+ --------
+ Creating a complete tripartite graph, with subsets of one, two, and three
+ nodes, respectively.
+
+ >>> G = nx.complete_multipartite_graph(1, 2, 3)
+ >>> [G.nodes[u]["subset"] for u in G]
+ [0, 1, 1, 2, 2, 2]
+ >>> list(G.edges(0))
+ [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
+ >>> list(G.edges(2))
+ [(2, 0), (2, 3), (2, 4), (2, 5)]
+ >>> list(G.edges(4))
+ [(4, 0), (4, 1), (4, 2)]
+
+ >>> G = nx.complete_multipartite_graph("a", "bc", "def")
+ >>> [G.nodes[u]["subset"] for u in sorted(G)]
+ [0, 1, 1, 2, 2, 2]
+
+ Notes
+ -----
+ This function generalizes several other graph builder functions.
+
+ - If no subset sizes are given, this returns the null graph.
+ - If a single subset size `n` is given, this returns the empty graph on
+ `n` nodes.
+ - If two subset sizes `m` and `n` are given, this returns the complete
+ bipartite graph on `m + n` nodes.
+ - If subset sizes `1` and `n` are given, this returns the star graph on
+ `n + 1` nodes.
+
+ See also
+ --------
+ complete_bipartite_graph
+ """
+ # The complete multipartite graph is an undirected simple graph.
+ G = Graph()
+
+ if len(subset_sizes) == 0:
+ return G
+
+ # set up subsets of nodes
+ try:
+ extents = pairwise(itertools.accumulate((0,) + subset_sizes))
+ subsets = [range(start, end) for start, end in extents]
+ except TypeError:
+ subsets = subset_sizes
+ else:
+ if any(size < 0 for size in subset_sizes):
+ raise NetworkXError(f"Negative number of nodes not valid: {subset_sizes}")
+
+ # add nodes with subset attribute
+ # while checking that ints are not mixed with iterables
+ try:
+ for i, subset in enumerate(subsets):
+ G.add_nodes_from(subset, subset=i)
+ except TypeError as err:
+ raise NetworkXError("Arguments must be all ints or all iterables") from err
+
+ # Across subsets, all nodes should be adjacent.
+ # We can use itertools.combinations() because undirected.
+ for subset1, subset2 in itertools.combinations(subsets, 2):
+ G.add_edges_from(itertools.product(subset1, subset2))
+ return G