about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py1284
1 files changed, 1284 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py
new file mode 100644
index 00000000..554613b8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py
@@ -0,0 +1,1284 @@
+"""
+Algorithms for calculating min/max spanning trees/forests.
+
+"""
+
+from dataclasses import dataclass, field
+from enum import Enum
+from heapq import heappop, heappush
+from itertools import count
+from math import isnan
+from operator import itemgetter
+from queue import PriorityQueue
+
+import networkx as nx
+from networkx.utils import UnionFind, not_implemented_for, py_random_state
+
+__all__ = [
+    "minimum_spanning_edges",
+    "maximum_spanning_edges",
+    "minimum_spanning_tree",
+    "maximum_spanning_tree",
+    "number_of_spanning_trees",
+    "random_spanning_tree",
+    "partition_spanning_tree",
+    "EdgePartition",
+    "SpanningTreeIterator",
+]
+
+
+class EdgePartition(Enum):
+    """
+    An enum to store the state of an edge partition. The enum is written to the
+    edges of a graph before being pasted to `kruskal_mst_edges`. Options are:
+
+    - EdgePartition.OPEN
+    - EdgePartition.INCLUDED
+    - EdgePartition.EXCLUDED
+    """
+
+    OPEN = 0
+    INCLUDED = 1
+    EXCLUDED = 2
+
+
+@not_implemented_for("multigraph")
+@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data")
+def boruvka_mst_edges(
+    G, minimum=True, weight="weight", keys=False, data=True, ignore_nan=False
+):
+    """Iterate over edges of a Borůvka's algorithm min/max spanning tree.
+
+    Parameters
+    ----------
+    G : NetworkX Graph
+        The edges of `G` must have distinct weights,
+        otherwise the edges may not form a tree.
+
+    minimum : bool (default: True)
+        Find the minimum (True) or maximum (False) spanning tree.
+
+    weight : string (default: 'weight')
+        The name of the edge attribute holding the edge weights.
+
+    keys : bool (default: True)
+        This argument is ignored since this function is not
+        implemented for multigraphs; it exists only for consistency
+        with the other minimum spanning tree functions.
+
+    data : bool (default: True)
+        Flag for whether to yield edge attribute dicts.
+        If True, yield edges `(u, v, d)`, where `d` is the attribute dict.
+        If False, yield edges `(u, v)`.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+    """
+    # Initialize a forest, assuming initially that it is the discrete
+    # partition of the nodes of the graph.
+    forest = UnionFind(G)
+
+    def best_edge(component):
+        """Returns the optimum (minimum or maximum) edge on the edge
+        boundary of the given set of nodes.
+
+        A return value of ``None`` indicates an empty boundary.
+
+        """
+        sign = 1 if minimum else -1
+        minwt = float("inf")
+        boundary = None
+        for e in nx.edge_boundary(G, component, data=True):
+            wt = e[-1].get(weight, 1) * sign
+            if isnan(wt):
+                if ignore_nan:
+                    continue
+                msg = f"NaN found as an edge weight. Edge {e}"
+                raise ValueError(msg)
+            if wt < minwt:
+                minwt = wt
+                boundary = e
+        return boundary
+
+    # Determine the optimum edge in the edge boundary of each component
+    # in the forest.
+    best_edges = (best_edge(component) for component in forest.to_sets())
+    best_edges = [edge for edge in best_edges if edge is not None]
+    # If each entry was ``None``, that means the graph was disconnected,
+    # so we are done generating the forest.
+    while best_edges:
+        # Determine the optimum edge in the edge boundary of each
+        # component in the forest.
+        #
+        # This must be a sequence, not an iterator. In this list, the
+        # same edge may appear twice, in different orientations (but
+        # that's okay, since a union operation will be called on the
+        # endpoints the first time it is seen, but not the second time).
+        #
+        # Any ``None`` indicates that the edge boundary for that
+        # component was empty, so that part of the forest has been
+        # completed.
+        #
+        # TODO This can be parallelized, both in the outer loop over
+        # each component in the forest and in the computation of the
+        # minimum. (Same goes for the identical lines outside the loop.)
+        best_edges = (best_edge(component) for component in forest.to_sets())
+        best_edges = [edge for edge in best_edges if edge is not None]
+        # Join trees in the forest using the best edges, and yield that
+        # edge, since it is part of the spanning tree.
+        #
+        # TODO This loop can be parallelized, to an extent (the union
+        # operation must be atomic).
+        for u, v, d in best_edges:
+            if forest[u] != forest[v]:
+                if data:
+                    yield u, v, d
+                else:
+                    yield u, v
+                forest.union(u, v)
+
+
+@nx._dispatchable(
+    edge_attrs={"weight": None, "partition": None}, preserve_edge_attrs="data"
+)
+def kruskal_mst_edges(
+    G, minimum, weight="weight", keys=True, data=True, ignore_nan=False, partition=None
+):
+    """
+    Iterate over edge of a Kruskal's algorithm min/max spanning tree.
+
+    Parameters
+    ----------
+    G : NetworkX Graph
+        The graph holding the tree of interest.
+
+    minimum : bool (default: True)
+        Find the minimum (True) or maximum (False) spanning tree.
+
+    weight : string (default: 'weight')
+        The name of the edge attribute holding the edge weights.
+
+    keys : bool (default: True)
+        If `G` is a multigraph, `keys` controls whether edge keys ar yielded.
+        Otherwise `keys` is ignored.
+
+    data : bool (default: True)
+        Flag for whether to yield edge attribute dicts.
+        If True, yield edges `(u, v, d)`, where `d` is the attribute dict.
+        If False, yield edges `(u, v)`.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+    partition : string (default: None)
+        The name of the edge attribute holding the partition data, if it exists.
+        Partition data is written to the edges using the `EdgePartition` enum.
+        If a partition exists, all included edges and none of the excluded edges
+        will appear in the final tree. Open edges may or may not be used.
+
+    Yields
+    ------
+    edge tuple
+        The edges as discovered by Kruskal's method. Each edge can
+        take the following forms: `(u, v)`, `(u, v, d)` or `(u, v, k, d)`
+        depending on the `key` and `data` parameters
+    """
+    subtrees = UnionFind()
+    if G.is_multigraph():
+        edges = G.edges(keys=True, data=True)
+    else:
+        edges = G.edges(data=True)
+
+    """
+    Sort the edges of the graph with respect to the partition data. 
+    Edges are returned in the following order:
+
+    * Included edges
+    * Open edges from smallest to largest weight
+    * Excluded edges
+    """
+    included_edges = []
+    open_edges = []
+    for e in edges:
+        d = e[-1]
+        wt = d.get(weight, 1)
+        if isnan(wt):
+            if ignore_nan:
+                continue
+            raise ValueError(f"NaN found as an edge weight. Edge {e}")
+
+        edge = (wt,) + e
+        if d.get(partition) == EdgePartition.INCLUDED:
+            included_edges.append(edge)
+        elif d.get(partition) == EdgePartition.EXCLUDED:
+            continue
+        else:
+            open_edges.append(edge)
+
+    if minimum:
+        sorted_open_edges = sorted(open_edges, key=itemgetter(0))
+    else:
+        sorted_open_edges = sorted(open_edges, key=itemgetter(0), reverse=True)
+
+    # Condense the lists into one
+    included_edges.extend(sorted_open_edges)
+    sorted_edges = included_edges
+    del open_edges, sorted_open_edges, included_edges
+
+    # Multigraphs need to handle edge keys in addition to edge data.
+    if G.is_multigraph():
+        for wt, u, v, k, d in sorted_edges:
+            if subtrees[u] != subtrees[v]:
+                if keys:
+                    if data:
+                        yield u, v, k, d
+                    else:
+                        yield u, v, k
+                else:
+                    if data:
+                        yield u, v, d
+                    else:
+                        yield u, v
+                subtrees.union(u, v)
+    else:
+        for wt, u, v, d in sorted_edges:
+            if subtrees[u] != subtrees[v]:
+                if data:
+                    yield u, v, d
+                else:
+                    yield u, v
+                subtrees.union(u, v)
+
+
+@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data")
+def prim_mst_edges(G, minimum, weight="weight", keys=True, data=True, ignore_nan=False):
+    """Iterate over edges of Prim's algorithm min/max spanning tree.
+
+    Parameters
+    ----------
+    G : NetworkX Graph
+        The graph holding the tree of interest.
+
+    minimum : bool (default: True)
+        Find the minimum (True) or maximum (False) spanning tree.
+
+    weight : string (default: 'weight')
+        The name of the edge attribute holding the edge weights.
+
+    keys : bool (default: True)
+        If `G` is a multigraph, `keys` controls whether edge keys ar yielded.
+        Otherwise `keys` is ignored.
+
+    data : bool (default: True)
+        Flag for whether to yield edge attribute dicts.
+        If True, yield edges `(u, v, d)`, where `d` is the attribute dict.
+        If False, yield edges `(u, v)`.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+    """
+    is_multigraph = G.is_multigraph()
+    push = heappush
+    pop = heappop
+
+    nodes = set(G)
+    c = count()
+
+    sign = 1 if minimum else -1
+
+    while nodes:
+        u = nodes.pop()
+        frontier = []
+        visited = {u}
+        if is_multigraph:
+            for v, keydict in G.adj[u].items():
+                for k, d in keydict.items():
+                    wt = d.get(weight, 1) * sign
+                    if isnan(wt):
+                        if ignore_nan:
+                            continue
+                        msg = f"NaN found as an edge weight. Edge {(u, v, k, d)}"
+                        raise ValueError(msg)
+                    push(frontier, (wt, next(c), u, v, k, d))
+        else:
+            for v, d in G.adj[u].items():
+                wt = d.get(weight, 1) * sign
+                if isnan(wt):
+                    if ignore_nan:
+                        continue
+                    msg = f"NaN found as an edge weight. Edge {(u, v, d)}"
+                    raise ValueError(msg)
+                push(frontier, (wt, next(c), u, v, d))
+        while nodes and frontier:
+            if is_multigraph:
+                W, _, u, v, k, d = pop(frontier)
+            else:
+                W, _, u, v, d = pop(frontier)
+            if v in visited or v not in nodes:
+                continue
+            # Multigraphs need to handle edge keys in addition to edge data.
+            if is_multigraph and keys:
+                if data:
+                    yield u, v, k, d
+                else:
+                    yield u, v, k
+            else:
+                if data:
+                    yield u, v, d
+                else:
+                    yield u, v
+            # update frontier
+            visited.add(v)
+            nodes.discard(v)
+            if is_multigraph:
+                for w, keydict in G.adj[v].items():
+                    if w in visited:
+                        continue
+                    for k2, d2 in keydict.items():
+                        new_weight = d2.get(weight, 1) * sign
+                        if isnan(new_weight):
+                            if ignore_nan:
+                                continue
+                            msg = f"NaN found as an edge weight. Edge {(v, w, k2, d2)}"
+                            raise ValueError(msg)
+                        push(frontier, (new_weight, next(c), v, w, k2, d2))
+            else:
+                for w, d2 in G.adj[v].items():
+                    if w in visited:
+                        continue
+                    new_weight = d2.get(weight, 1) * sign
+                    if isnan(new_weight):
+                        if ignore_nan:
+                            continue
+                        msg = f"NaN found as an edge weight. Edge {(v, w, d2)}"
+                        raise ValueError(msg)
+                    push(frontier, (new_weight, next(c), v, w, d2))
+
+
+ALGORITHMS = {
+    "boruvka": boruvka_mst_edges,
+    "borůvka": boruvka_mst_edges,
+    "kruskal": kruskal_mst_edges,
+    "prim": prim_mst_edges,
+}
+
+
+@not_implemented_for("directed")
+@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data")
+def minimum_spanning_edges(
+    G, algorithm="kruskal", weight="weight", keys=True, data=True, ignore_nan=False
+):
+    """Generate edges in a minimum spanning forest of an undirected
+    weighted graph.
+
+    A minimum spanning tree is a subgraph of the graph (a tree)
+    with the minimum sum of edge weights.  A spanning forest is a
+    union of the spanning trees for each connected component of the graph.
+
+    Parameters
+    ----------
+    G : undirected Graph
+       An undirected graph. If `G` is connected, then the algorithm finds a
+       spanning tree. Otherwise, a spanning forest is found.
+
+    algorithm : string
+       The algorithm to use when finding a minimum spanning tree. Valid
+       choices are 'kruskal', 'prim', or 'boruvka'. The default is 'kruskal'.
+
+    weight : string
+       Edge data key to use for weight (default 'weight').
+
+    keys : bool
+       Whether to yield edge key in multigraphs in addition to the edge.
+       If `G` is not a multigraph, this is ignored.
+
+    data : bool, optional
+       If True yield the edge data along with the edge.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+    Returns
+    -------
+    edges : iterator
+       An iterator over edges in a maximum spanning tree of `G`.
+       Edges connecting nodes `u` and `v` are represented as tuples:
+       `(u, v, k, d)` or `(u, v, k)` or `(u, v, d)` or `(u, v)`
+
+       If `G` is a multigraph, `keys` indicates whether the edge key `k` will
+       be reported in the third position in the edge tuple. `data` indicates
+       whether the edge datadict `d` will appear at the end of the edge tuple.
+
+       If `G` is not a multigraph, the tuples are `(u, v, d)` if `data` is True
+       or `(u, v)` if `data` is False.
+
+    Examples
+    --------
+    >>> from networkx.algorithms import tree
+
+    Find minimum spanning edges by Kruskal's algorithm
+
+    >>> G = nx.cycle_graph(4)
+    >>> G.add_edge(0, 3, weight=2)
+    >>> mst = tree.minimum_spanning_edges(G, algorithm="kruskal", data=False)
+    >>> edgelist = list(mst)
+    >>> sorted(sorted(e) for e in edgelist)
+    [[0, 1], [1, 2], [2, 3]]
+
+    Find minimum spanning edges by Prim's algorithm
+
+    >>> G = nx.cycle_graph(4)
+    >>> G.add_edge(0, 3, weight=2)
+    >>> mst = tree.minimum_spanning_edges(G, algorithm="prim", data=False)
+    >>> edgelist = list(mst)
+    >>> sorted(sorted(e) for e in edgelist)
+    [[0, 1], [1, 2], [2, 3]]
+
+    Notes
+    -----
+    For Borůvka's algorithm, each edge must have a weight attribute, and
+    each edge weight must be distinct.
+
+    For the other algorithms, if the graph edges do not have a weight
+    attribute a default weight of 1 will be used.
+
+    Modified code from David Eppstein, April 2006
+    http://www.ics.uci.edu/~eppstein/PADS/
+
+    """
+    try:
+        algo = ALGORITHMS[algorithm]
+    except KeyError as err:
+        msg = f"{algorithm} is not a valid choice for an algorithm."
+        raise ValueError(msg) from err
+
+    return algo(
+        G, minimum=True, weight=weight, keys=keys, data=data, ignore_nan=ignore_nan
+    )
+
+
+@not_implemented_for("directed")
+@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data")
+def maximum_spanning_edges(
+    G, algorithm="kruskal", weight="weight", keys=True, data=True, ignore_nan=False
+):
+    """Generate edges in a maximum spanning forest of an undirected
+    weighted graph.
+
+    A maximum spanning tree is a subgraph of the graph (a tree)
+    with the maximum possible sum of edge weights.  A spanning forest is a
+    union of the spanning trees for each connected component of the graph.
+
+    Parameters
+    ----------
+    G : undirected Graph
+       An undirected graph. If `G` is connected, then the algorithm finds a
+       spanning tree. Otherwise, a spanning forest is found.
+
+    algorithm : string
+       The algorithm to use when finding a maximum spanning tree. Valid
+       choices are 'kruskal', 'prim', or 'boruvka'. The default is 'kruskal'.
+
+    weight : string
+       Edge data key to use for weight (default 'weight').
+
+    keys : bool
+       Whether to yield edge key in multigraphs in addition to the edge.
+       If `G` is not a multigraph, this is ignored.
+
+    data : bool, optional
+       If True yield the edge data along with the edge.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+    Returns
+    -------
+    edges : iterator
+       An iterator over edges in a maximum spanning tree of `G`.
+       Edges connecting nodes `u` and `v` are represented as tuples:
+       `(u, v, k, d)` or `(u, v, k)` or `(u, v, d)` or `(u, v)`
+
+       If `G` is a multigraph, `keys` indicates whether the edge key `k` will
+       be reported in the third position in the edge tuple. `data` indicates
+       whether the edge datadict `d` will appear at the end of the edge tuple.
+
+       If `G` is not a multigraph, the tuples are `(u, v, d)` if `data` is True
+       or `(u, v)` if `data` is False.
+
+    Examples
+    --------
+    >>> from networkx.algorithms import tree
+
+    Find maximum spanning edges by Kruskal's algorithm
+
+    >>> G = nx.cycle_graph(4)
+    >>> G.add_edge(0, 3, weight=2)
+    >>> mst = tree.maximum_spanning_edges(G, algorithm="kruskal", data=False)
+    >>> edgelist = list(mst)
+    >>> sorted(sorted(e) for e in edgelist)
+    [[0, 1], [0, 3], [1, 2]]
+
+    Find maximum spanning edges by Prim's algorithm
+
+    >>> G = nx.cycle_graph(4)
+    >>> G.add_edge(0, 3, weight=2)  # assign weight 2 to edge 0-3
+    >>> mst = tree.maximum_spanning_edges(G, algorithm="prim", data=False)
+    >>> edgelist = list(mst)
+    >>> sorted(sorted(e) for e in edgelist)
+    [[0, 1], [0, 3], [2, 3]]
+
+    Notes
+    -----
+    For Borůvka's algorithm, each edge must have a weight attribute, and
+    each edge weight must be distinct.
+
+    For the other algorithms, if the graph edges do not have a weight
+    attribute a default weight of 1 will be used.
+
+    Modified code from David Eppstein, April 2006
+    http://www.ics.uci.edu/~eppstein/PADS/
+    """
+    try:
+        algo = ALGORITHMS[algorithm]
+    except KeyError as err:
+        msg = f"{algorithm} is not a valid choice for an algorithm."
+        raise ValueError(msg) from err
+
+    return algo(
+        G, minimum=False, weight=weight, keys=keys, data=data, ignore_nan=ignore_nan
+    )
+
+
+@nx._dispatchable(preserve_all_attrs=True, returns_graph=True)
+def minimum_spanning_tree(G, weight="weight", algorithm="kruskal", ignore_nan=False):
+    """Returns a minimum spanning tree or forest on an undirected graph `G`.
+
+    Parameters
+    ----------
+    G : undirected graph
+        An undirected graph. If `G` is connected, then the algorithm finds a
+        spanning tree. Otherwise, a spanning forest is found.
+
+    weight : str
+       Data key to use for edge weights.
+
+    algorithm : string
+       The algorithm to use when finding a minimum spanning tree. Valid
+       choices are 'kruskal', 'prim', or 'boruvka'. The default is
+       'kruskal'.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+    Returns
+    -------
+    G : NetworkX Graph
+       A minimum spanning tree or forest.
+
+    Examples
+    --------
+    >>> G = nx.cycle_graph(4)
+    >>> G.add_edge(0, 3, weight=2)
+    >>> T = nx.minimum_spanning_tree(G)
+    >>> sorted(T.edges(data=True))
+    [(0, 1, {}), (1, 2, {}), (2, 3, {})]
+
+
+    Notes
+    -----
+    For Borůvka's algorithm, each edge must have a weight attribute, and
+    each edge weight must be distinct.
+
+    For the other algorithms, if the graph edges do not have a weight
+    attribute a default weight of 1 will be used.
+
+    There may be more than one tree with the same minimum or maximum weight.
+    See :mod:`networkx.tree.recognition` for more detailed definitions.
+
+    Isolated nodes with self-loops are in the tree as edgeless isolated nodes.
+
+    """
+    edges = minimum_spanning_edges(
+        G, algorithm, weight, keys=True, data=True, ignore_nan=ignore_nan
+    )
+    T = G.__class__()  # Same graph class as G
+    T.graph.update(G.graph)
+    T.add_nodes_from(G.nodes.items())
+    T.add_edges_from(edges)
+    return T
+
+
+@nx._dispatchable(preserve_all_attrs=True, returns_graph=True)
+def partition_spanning_tree(
+    G, minimum=True, weight="weight", partition="partition", ignore_nan=False
+):
+    """
+    Find a spanning tree while respecting a partition of edges.
+
+    Edges can be flagged as either `INCLUDED` which are required to be in the
+    returned tree, `EXCLUDED`, which cannot be in the returned tree and `OPEN`.
+
+    This is used in the SpanningTreeIterator to create new partitions following
+    the algorithm of Sörensen and Janssens [1]_.
+
+    Parameters
+    ----------
+    G : undirected graph
+        An undirected graph.
+
+    minimum : bool (default: True)
+        Determines whether the returned tree is the minimum spanning tree of
+        the partition of the maximum one.
+
+    weight : str
+        Data key to use for edge weights.
+
+    partition : str
+        The key for the edge attribute containing the partition
+        data on the graph. Edges can be included, excluded or open using the
+        `EdgePartition` enum.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+
+    Returns
+    -------
+    G : NetworkX Graph
+        A minimum spanning tree using all of the included edges in the graph and
+        none of the excluded edges.
+
+    References
+    ----------
+    .. [1] G.K. Janssens, K. Sörensen, An algorithm to generate all spanning
+           trees in order of increasing cost, Pesquisa Operacional, 2005-08,
+           Vol. 25 (2), p. 219-229,
+           https://www.scielo.br/j/pope/a/XHswBwRwJyrfL88dmMwYNWp/?lang=en
+    """
+    edges = kruskal_mst_edges(
+        G,
+        minimum,
+        weight,
+        keys=True,
+        data=True,
+        ignore_nan=ignore_nan,
+        partition=partition,
+    )
+    T = G.__class__()  # Same graph class as G
+    T.graph.update(G.graph)
+    T.add_nodes_from(G.nodes.items())
+    T.add_edges_from(edges)
+    return T
+
+
+@nx._dispatchable(preserve_all_attrs=True, returns_graph=True)
+def maximum_spanning_tree(G, weight="weight", algorithm="kruskal", ignore_nan=False):
+    """Returns a maximum spanning tree or forest on an undirected graph `G`.
+
+    Parameters
+    ----------
+    G : undirected graph
+        An undirected graph. If `G` is connected, then the algorithm finds a
+        spanning tree. Otherwise, a spanning forest is found.
+
+    weight : str
+       Data key to use for edge weights.
+
+    algorithm : string
+       The algorithm to use when finding a maximum spanning tree. Valid
+       choices are 'kruskal', 'prim', or 'boruvka'. The default is
+       'kruskal'.
+
+    ignore_nan : bool (default: False)
+        If a NaN is found as an edge weight normally an exception is raised.
+        If `ignore_nan is True` then that edge is ignored instead.
+
+
+    Returns
+    -------
+    G : NetworkX Graph
+       A maximum spanning tree or forest.
+
+
+    Examples
+    --------
+    >>> G = nx.cycle_graph(4)
+    >>> G.add_edge(0, 3, weight=2)
+    >>> T = nx.maximum_spanning_tree(G)
+    >>> sorted(T.edges(data=True))
+    [(0, 1, {}), (0, 3, {'weight': 2}), (1, 2, {})]
+
+
+    Notes
+    -----
+    For Borůvka's algorithm, each edge must have a weight attribute, and
+    each edge weight must be distinct.
+
+    For the other algorithms, if the graph edges do not have a weight
+    attribute a default weight of 1 will be used.
+
+    There may be more than one tree with the same minimum or maximum weight.
+    See :mod:`networkx.tree.recognition` for more detailed definitions.
+
+    Isolated nodes with self-loops are in the tree as edgeless isolated nodes.
+
+    """
+    edges = maximum_spanning_edges(
+        G, algorithm, weight, keys=True, data=True, ignore_nan=ignore_nan
+    )
+    edges = list(edges)
+    T = G.__class__()  # Same graph class as G
+    T.graph.update(G.graph)
+    T.add_nodes_from(G.nodes.items())
+    T.add_edges_from(edges)
+    return T
+
+
+@py_random_state(3)
+@nx._dispatchable(preserve_edge_attrs=True, returns_graph=True)
+def random_spanning_tree(G, weight=None, *, multiplicative=True, seed=None):
+    """
+    Sample a random spanning tree using the edges weights of `G`.
+
+    This function supports two different methods for determining the
+    probability of the graph. If ``multiplicative=True``, the probability
+    is based on the product of edge weights, and if ``multiplicative=False``
+    it is based on the sum of the edge weight. However, since it is
+    easier to determine the total weight of all spanning trees for the
+    multiplicative version, that is significantly faster and should be used if
+    possible. Additionally, setting `weight` to `None` will cause a spanning tree
+    to be selected with uniform probability.
+
+    The function uses algorithm A8 in [1]_ .
+
+    Parameters
+    ----------
+    G : nx.Graph
+        An undirected version of the original graph.
+
+    weight : string
+        The edge key for the edge attribute holding edge weight.
+
+    multiplicative : bool, default=True
+        If `True`, the probability of each tree is the product of its edge weight
+        over the sum of the product of all the spanning trees in the graph. If
+        `False`, the probability is the sum of its edge weight over the sum of
+        the sum of weights for all spanning trees in the graph.
+
+    seed : integer, random_state, or None (default)
+        Indicator of random number generation state.
+        See :ref:`Randomness<randomness>`.
+
+    Returns
+    -------
+    nx.Graph
+        A spanning tree using the distribution defined by the weight of the tree.
+
+    References
+    ----------
+    .. [1] V. Kulkarni, Generating random combinatorial objects, Journal of
+       Algorithms, 11 (1990), pp. 185–207
+    """
+
+    def find_node(merged_nodes, node):
+        """
+        We can think of clusters of contracted nodes as having one
+        representative in the graph. Each node which is not in merged_nodes
+        is still its own representative. Since a representative can be later
+        contracted, we need to recursively search though the dict to find
+        the final representative, but once we know it we can use path
+        compression to speed up the access of the representative for next time.
+
+        This cannot be replaced by the standard NetworkX union_find since that
+        data structure will merge nodes with less representing nodes into the
+        one with more representing nodes but this function requires we merge
+        them using the order that contract_edges contracts using.
+
+        Parameters
+        ----------
+        merged_nodes : dict
+            The dict storing the mapping from node to representative
+        node
+            The node whose representative we seek
+
+        Returns
+        -------
+        The representative of the `node`
+        """
+        if node not in merged_nodes:
+            return node
+        else:
+            rep = find_node(merged_nodes, merged_nodes[node])
+            merged_nodes[node] = rep
+            return rep
+
+    def prepare_graph():
+        """
+        For the graph `G`, remove all edges not in the set `V` and then
+        contract all edges in the set `U`.
+
+        Returns
+        -------
+        A copy of `G` which has had all edges not in `V` removed and all edges
+        in `U` contracted.
+        """
+
+        # The result is a MultiGraph version of G so that parallel edges are
+        # allowed during edge contraction
+        result = nx.MultiGraph(incoming_graph_data=G)
+
+        # Remove all edges not in V
+        edges_to_remove = set(result.edges()).difference(V)
+        result.remove_edges_from(edges_to_remove)
+
+        # Contract all edges in U
+        #
+        # Imagine that you have two edges to contract and they share an
+        # endpoint like this:
+        #                        [0] ----- [1] ----- [2]
+        # If we contract (0, 1) first, the contraction function will always
+        # delete the second node it is passed so the resulting graph would be
+        #                             [0] ----- [2]
+        # and edge (1, 2) no longer exists but (0, 2) would need to be contracted
+        # in its place now. That is why I use the below dict as a merge-find
+        # data structure with path compression to track how the nodes are merged.
+        merged_nodes = {}
+
+        for u, v in U:
+            u_rep = find_node(merged_nodes, u)
+            v_rep = find_node(merged_nodes, v)
+            # We cannot contract a node with itself
+            if u_rep == v_rep:
+                continue
+            nx.contracted_nodes(result, u_rep, v_rep, self_loops=False, copy=False)
+            merged_nodes[v_rep] = u_rep
+
+        return merged_nodes, result
+
+    def spanning_tree_total_weight(G, weight):
+        """
+        Find the sum of weights of the spanning trees of `G` using the
+        appropriate `method`.
+
+        This is easy if the chosen method is 'multiplicative', since we can
+        use Kirchhoff's Tree Matrix Theorem directly. However, with the
+        'additive' method, this process is slightly more complex and less
+        computationally efficient as we have to find the number of spanning
+        trees which contain each possible edge in the graph.
+
+        Parameters
+        ----------
+        G : NetworkX Graph
+            The graph to find the total weight of all spanning trees on.
+
+        weight : string
+            The key for the weight edge attribute of the graph.
+
+        Returns
+        -------
+        float
+            The sum of either the multiplicative or additive weight for all
+            spanning trees in the graph.
+        """
+        if multiplicative:
+            return nx.total_spanning_tree_weight(G, weight)
+        else:
+            # There are two cases for the total spanning tree additive weight.
+            # 1. There is one edge in the graph. Then the only spanning tree is
+            #    that edge itself, which will have a total weight of that edge
+            #    itself.
+            if G.number_of_edges() == 1:
+                return G.edges(data=weight).__iter__().__next__()[2]
+            # 2. There are no edges or two or more edges in the graph. Then, we find the
+            #    total weight of the spanning trees using the formula in the
+            #    reference paper: take the weight of each edge and multiply it by
+            #    the number of spanning trees which include that edge. This
+            #    can be accomplished by contracting the edge and finding the
+            #    multiplicative total spanning tree weight if the weight of each edge
+            #    is assumed to be 1, which is conveniently built into networkx already,
+            #    by calling total_spanning_tree_weight with weight=None.
+            #    Note that with no edges the returned value is just zero.
+            else:
+                total = 0
+                for u, v, w in G.edges(data=weight):
+                    total += w * nx.total_spanning_tree_weight(
+                        nx.contracted_edge(G, edge=(u, v), self_loops=False), None
+                    )
+                return total
+
+    if G.number_of_nodes() < 2:
+        # no edges in the spanning tree
+        return nx.empty_graph(G.nodes)
+
+    U = set()
+    st_cached_value = 0
+    V = set(G.edges())
+    shuffled_edges = list(G.edges())
+    seed.shuffle(shuffled_edges)
+
+    for u, v in shuffled_edges:
+        e_weight = G[u][v][weight] if weight is not None else 1
+        node_map, prepared_G = prepare_graph()
+        G_total_tree_weight = spanning_tree_total_weight(prepared_G, weight)
+        # Add the edge to U so that we can compute the total tree weight
+        # assuming we include that edge
+        # Now, if (u, v) cannot exist in G because it is fully contracted out
+        # of existence, then it by definition cannot influence G_e's Kirchhoff
+        # value. But, we also cannot pick it.
+        rep_edge = (find_node(node_map, u), find_node(node_map, v))
+        # Check to see if the 'representative edge' for the current edge is
+        # in prepared_G. If so, then we can pick it.
+        if rep_edge in prepared_G.edges:
+            prepared_G_e = nx.contracted_edge(
+                prepared_G, edge=rep_edge, self_loops=False
+            )
+            G_e_total_tree_weight = spanning_tree_total_weight(prepared_G_e, weight)
+            if multiplicative:
+                threshold = e_weight * G_e_total_tree_weight / G_total_tree_weight
+            else:
+                numerator = (
+                    st_cached_value + e_weight
+                ) * nx.total_spanning_tree_weight(prepared_G_e) + G_e_total_tree_weight
+                denominator = (
+                    st_cached_value * nx.total_spanning_tree_weight(prepared_G)
+                    + G_total_tree_weight
+                )
+                threshold = numerator / denominator
+        else:
+            threshold = 0.0
+        z = seed.uniform(0.0, 1.0)
+        if z > threshold:
+            # Remove the edge from V since we did not pick it.
+            V.remove((u, v))
+        else:
+            # Add the edge to U since we picked it.
+            st_cached_value += e_weight
+            U.add((u, v))
+        # If we decide to keep an edge, it may complete the spanning tree.
+        if len(U) == G.number_of_nodes() - 1:
+            spanning_tree = nx.Graph()
+            spanning_tree.add_edges_from(U)
+            return spanning_tree
+    raise Exception(f"Something went wrong! Only {len(U)} edges in the spanning tree!")
+
+
+class SpanningTreeIterator:
+    """
+    Iterate over all spanning trees of a graph in either increasing or
+    decreasing cost.
+
+    Notes
+    -----
+    This iterator uses the partition scheme from [1]_ (included edges,
+    excluded edges and open edges) as well as a modified Kruskal's Algorithm
+    to generate minimum spanning trees which respect the partition of edges.
+    For spanning trees with the same weight, ties are broken arbitrarily.
+
+    References
+    ----------
+    .. [1] G.K. Janssens, K. Sörensen, An algorithm to generate all spanning
+           trees in order of increasing cost, Pesquisa Operacional, 2005-08,
+           Vol. 25 (2), p. 219-229,
+           https://www.scielo.br/j/pope/a/XHswBwRwJyrfL88dmMwYNWp/?lang=en
+    """
+
+    @dataclass(order=True)
+    class Partition:
+        """
+        This dataclass represents a partition and stores a dict with the edge
+        data and the weight of the minimum spanning tree of the partition dict.
+        """
+
+        mst_weight: float
+        partition_dict: dict = field(compare=False)
+
+        def __copy__(self):
+            return SpanningTreeIterator.Partition(
+                self.mst_weight, self.partition_dict.copy()
+            )
+
+    def __init__(self, G, weight="weight", minimum=True, ignore_nan=False):
+        """
+        Initialize the iterator
+
+        Parameters
+        ----------
+        G : nx.Graph
+            The directed graph which we need to iterate trees over
+
+        weight : String, default = "weight"
+            The edge attribute used to store the weight of the edge
+
+        minimum : bool, default = True
+            Return the trees in increasing order while true and decreasing order
+            while false.
+
+        ignore_nan : bool, default = False
+            If a NaN is found as an edge weight normally an exception is raised.
+            If `ignore_nan is True` then that edge is ignored instead.
+        """
+        self.G = G.copy()
+        self.G.__networkx_cache__ = None  # Disable caching
+        self.weight = weight
+        self.minimum = minimum
+        self.ignore_nan = ignore_nan
+        # Randomly create a key for an edge attribute to hold the partition data
+        self.partition_key = (
+            "SpanningTreeIterators super secret partition attribute name"
+        )
+
+    def __iter__(self):
+        """
+        Returns
+        -------
+        SpanningTreeIterator
+            The iterator object for this graph
+        """
+        self.partition_queue = PriorityQueue()
+        self._clear_partition(self.G)
+        mst_weight = partition_spanning_tree(
+            self.G, self.minimum, self.weight, self.partition_key, self.ignore_nan
+        ).size(weight=self.weight)
+
+        self.partition_queue.put(
+            self.Partition(mst_weight if self.minimum else -mst_weight, {})
+        )
+
+        return self
+
+    def __next__(self):
+        """
+        Returns
+        -------
+        (multi)Graph
+            The spanning tree of next greatest weight, which ties broken
+            arbitrarily.
+        """
+        if self.partition_queue.empty():
+            del self.G, self.partition_queue
+            raise StopIteration
+
+        partition = self.partition_queue.get()
+        self._write_partition(partition)
+        next_tree = partition_spanning_tree(
+            self.G, self.minimum, self.weight, self.partition_key, self.ignore_nan
+        )
+        self._partition(partition, next_tree)
+
+        self._clear_partition(next_tree)
+        return next_tree
+
+    def _partition(self, partition, partition_tree):
+        """
+        Create new partitions based of the minimum spanning tree of the
+        current minimum partition.
+
+        Parameters
+        ----------
+        partition : Partition
+            The Partition instance used to generate the current minimum spanning
+            tree.
+        partition_tree : nx.Graph
+            The minimum spanning tree of the input partition.
+        """
+        # create two new partitions with the data from the input partition dict
+        p1 = self.Partition(0, partition.partition_dict.copy())
+        p2 = self.Partition(0, partition.partition_dict.copy())
+        for e in partition_tree.edges:
+            # determine if the edge was open or included
+            if e not in partition.partition_dict:
+                # This is an open edge
+                p1.partition_dict[e] = EdgePartition.EXCLUDED
+                p2.partition_dict[e] = EdgePartition.INCLUDED
+
+                self._write_partition(p1)
+                p1_mst = partition_spanning_tree(
+                    self.G,
+                    self.minimum,
+                    self.weight,
+                    self.partition_key,
+                    self.ignore_nan,
+                )
+                p1_mst_weight = p1_mst.size(weight=self.weight)
+                if nx.is_connected(p1_mst):
+                    p1.mst_weight = p1_mst_weight if self.minimum else -p1_mst_weight
+                    self.partition_queue.put(p1.__copy__())
+                p1.partition_dict = p2.partition_dict.copy()
+
+    def _write_partition(self, partition):
+        """
+        Writes the desired partition into the graph to calculate the minimum
+        spanning tree.
+
+        Parameters
+        ----------
+        partition : Partition
+            A Partition dataclass describing a partition on the edges of the
+            graph.
+        """
+
+        partition_dict = partition.partition_dict
+        partition_key = self.partition_key
+        G = self.G
+
+        edges = (
+            G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
+        )
+        for *e, d in edges:
+            d[partition_key] = partition_dict.get(tuple(e), EdgePartition.OPEN)
+
+    def _clear_partition(self, G):
+        """
+        Removes partition data from the graph
+        """
+        partition_key = self.partition_key
+        edges = (
+            G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
+        )
+        for *e, d in edges:
+            if partition_key in d:
+                del d[partition_key]
+
+
+@nx._dispatchable(edge_attrs="weight")
+def number_of_spanning_trees(G, *, root=None, weight=None):
+    """Returns the number of spanning trees in `G`.
+
+    A spanning tree for an undirected graph is a tree that connects
+    all nodes in the graph. For a directed graph, the analog of a
+    spanning tree is called a (spanning) arborescence. The arborescence
+    includes a unique directed path from the `root` node to each other node.
+    The graph must be weakly connected, and the root must be a node
+    that includes all nodes as successors [3]_. Note that to avoid
+    discussing sink-roots and reverse-arborescences, we have reversed
+    the edge orientation from [3]_ and use the in-degree laplacian.
+
+    This function (when `weight` is `None`) returns the number of
+    spanning trees for an undirected graph and the number of
+    arborescences from a single root node for a directed graph.
+    When `weight` is the name of an edge attribute which holds the
+    weight value of each edge, the function returns the sum over
+    all trees of the multiplicative weight of each tree. That is,
+    the weight of the tree is the product of its edge weights.
+
+    Kirchoff's Tree Matrix Theorem states that any cofactor of the
+    Laplacian matrix of a graph is the number of spanning trees in the
+    graph. (Here we use cofactors for a diagonal entry so that the
+    cofactor becomes the determinant of the matrix with one row
+    and its matching column removed.) For a weighted Laplacian matrix,
+    the cofactor is the sum across all spanning trees of the
+    multiplicative weight of each tree. That is, the weight of each
+    tree is the product of its edge weights. The theorem is also
+    known as Kirchhoff's theorem [1]_ and the Matrix-Tree theorem [2]_.
+
+    For directed graphs, a similar theorem (Tutte's Theorem) holds with
+    the cofactor chosen to be the one with row and column removed that
+    correspond to the root. The cofactor is the number of arborescences
+    with the specified node as root. And the weighted version gives the
+    sum of the arborescence weights with root `root`. The arborescence
+    weight is the product of its edge weights.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    root : node
+       A node in the directed graph `G` that has all nodes as descendants.
+       (This is ignored for undirected graphs.)
+
+    weight : string or None, optional (default=None)
+        The name of the edge attribute holding the edge weight.
+        If `None`, then each edge is assumed to have a weight of 1.
+
+    Returns
+    -------
+    Number
+        Undirected graphs:
+            The number of spanning trees of the graph `G`.
+            Or the sum of all spanning tree weights of the graph `G`
+            where the weight of a tree is the product of its edge weights.
+        Directed graphs:
+            The number of arborescences of `G` rooted at node `root`.
+            Or the sum of all arborescence weights of the graph `G` with
+            specified root where the weight of an arborescence is the product
+            of its edge weights.
+
+    Raises
+    ------
+    NetworkXPointlessConcept
+        If `G` does not contain any nodes.
+
+    NetworkXError
+        If the graph `G` is directed and the root node
+        is not specified or is not in G.
+
+    Examples
+    --------
+    >>> G = nx.complete_graph(5)
+    >>> round(nx.number_of_spanning_trees(G))
+    125
+
+    >>> G = nx.Graph()
+    >>> G.add_edge(1, 2, weight=2)
+    >>> G.add_edge(1, 3, weight=1)
+    >>> G.add_edge(2, 3, weight=1)
+    >>> round(nx.number_of_spanning_trees(G, weight="weight"))
+    5
+
+    Notes
+    -----
+    Self-loops are excluded. Multi-edges are contracted in one edge
+    equal to the sum of the weights.
+
+    References
+    ----------
+    .. [1] Wikipedia
+       "Kirchhoff's theorem."
+       https://en.wikipedia.org/wiki/Kirchhoff%27s_theorem
+    .. [2] Kirchhoff, G. R.
+        Über die Auflösung der Gleichungen, auf welche man
+        bei der Untersuchung der linearen Vertheilung
+        Galvanischer Ströme geführt wird
+        Annalen der Physik und Chemie, vol. 72, pp. 497-508, 1847.
+    .. [3] Margoliash, J.
+        "Matrix-Tree Theorem for Directed Graphs"
+        https://www.math.uchicago.edu/~may/VIGRE/VIGRE2010/REUPapers/Margoliash.pdf
+    """
+    import numpy as np
+
+    if len(G) == 0:
+        raise nx.NetworkXPointlessConcept("Graph G must contain at least one node.")
+
+    # undirected G
+    if not nx.is_directed(G):
+        if not nx.is_connected(G):
+            return 0
+        G_laplacian = nx.laplacian_matrix(G, weight=weight).toarray()
+        return float(np.linalg.det(G_laplacian[1:, 1:]))
+
+    # directed G
+    if root is None:
+        raise nx.NetworkXError("Input `root` must be provided when G is directed")
+    if root not in G:
+        raise nx.NetworkXError("The node root is not in the graph G.")
+    if not nx.is_weakly_connected(G):
+        return 0
+
+    # Compute directed Laplacian matrix
+    nodelist = [root] + [n for n in G if n != root]
+    A = nx.adjacency_matrix(G, nodelist=nodelist, weight=weight)
+    D = np.diag(A.sum(axis=0))
+    G_laplacian = D - A
+
+    # Compute number of spanning trees
+    return float(np.linalg.det(G_laplacian[1:, 1:]))