about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/generators/classic.py
diff options
context:
space:
mode:
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