about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/coding.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/algorithms/tree/coding.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py413
1 files changed, 413 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py
new file mode 100644
index 00000000..f33089f7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py
@@ -0,0 +1,413 @@
+"""Functions for encoding and decoding trees.
+
+Since a tree is a highly restricted form of graph, it can be represented
+concisely in several ways. This module includes functions for encoding
+and decoding trees in the form of nested tuples and Prüfer
+sequences. The former requires a rooted tree, whereas the latter can be
+applied to unrooted trees. Furthermore, there is a bijection from Prüfer
+sequences to labeled trees.
+
+"""
+
+from collections import Counter
+from itertools import chain
+
+import networkx as nx
+from networkx.utils import not_implemented_for
+
+__all__ = [
+    "from_nested_tuple",
+    "from_prufer_sequence",
+    "NotATree",
+    "to_nested_tuple",
+    "to_prufer_sequence",
+]
+
+
+class NotATree(nx.NetworkXException):
+    """Raised when a function expects a tree (that is, a connected
+    undirected graph with no cycles) but gets a non-tree graph as input
+    instead.
+
+    """
+
+
+@not_implemented_for("directed")
+@nx._dispatchable(graphs="T")
+def to_nested_tuple(T, root, canonical_form=False):
+    """Returns a nested tuple representation of the given tree.
+
+    The nested tuple representation of a tree is defined
+    recursively. The tree with one node and no edges is represented by
+    the empty tuple, ``()``. A tree with ``k`` subtrees is represented
+    by a tuple of length ``k`` in which each element is the nested tuple
+    representation of a subtree.
+
+    Parameters
+    ----------
+    T : NetworkX graph
+        An undirected graph object representing a tree.
+
+    root : node
+        The node in ``T`` to interpret as the root of the tree.
+
+    canonical_form : bool
+        If ``True``, each tuple is sorted so that the function returns
+        a canonical form for rooted trees. This means "lighter" subtrees
+        will appear as nested tuples before "heavier" subtrees. In this
+        way, each isomorphic rooted tree has the same nested tuple
+        representation.
+
+    Returns
+    -------
+    tuple
+        A nested tuple representation of the tree.
+
+    Notes
+    -----
+    This function is *not* the inverse of :func:`from_nested_tuple`; the
+    only guarantee is that the rooted trees are isomorphic.
+
+    See also
+    --------
+    from_nested_tuple
+    to_prufer_sequence
+
+    Examples
+    --------
+    The tree need not be a balanced binary tree::
+
+        >>> T = nx.Graph()
+        >>> T.add_edges_from([(0, 1), (0, 2), (0, 3)])
+        >>> T.add_edges_from([(1, 4), (1, 5)])
+        >>> T.add_edges_from([(3, 6), (3, 7)])
+        >>> root = 0
+        >>> nx.to_nested_tuple(T, root)
+        (((), ()), (), ((), ()))
+
+    Continuing the above example, if ``canonical_form`` is ``True``, the
+    nested tuples will be sorted::
+
+        >>> nx.to_nested_tuple(T, root, canonical_form=True)
+        ((), ((), ()), ((), ()))
+
+    Even the path graph can be interpreted as a tree::
+
+        >>> T = nx.path_graph(4)
+        >>> root = 0
+        >>> nx.to_nested_tuple(T, root)
+        ((((),),),)
+
+    """
+
+    def _make_tuple(T, root, _parent):
+        """Recursively compute the nested tuple representation of the
+        given rooted tree.
+
+        ``_parent`` is the parent node of ``root`` in the supertree in
+        which ``T`` is a subtree, or ``None`` if ``root`` is the root of
+        the supertree. This argument is used to determine which
+        neighbors of ``root`` are children and which is the parent.
+
+        """
+        # Get the neighbors of `root` that are not the parent node. We
+        # are guaranteed that `root` is always in `T` by construction.
+        children = set(T[root]) - {_parent}
+        if len(children) == 0:
+            return ()
+        nested = (_make_tuple(T, v, root) for v in children)
+        if canonical_form:
+            nested = sorted(nested)
+        return tuple(nested)
+
+    # Do some sanity checks on the input.
+    if not nx.is_tree(T):
+        raise nx.NotATree("provided graph is not a tree")
+    if root not in T:
+        raise nx.NodeNotFound(f"Graph {T} contains no node {root}")
+
+    return _make_tuple(T, root, None)
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def from_nested_tuple(sequence, sensible_relabeling=False):
+    """Returns the rooted tree corresponding to the given nested tuple.
+
+    The nested tuple representation of a tree is defined
+    recursively. The tree with one node and no edges is represented by
+    the empty tuple, ``()``. A tree with ``k`` subtrees is represented
+    by a tuple of length ``k`` in which each element is the nested tuple
+    representation of a subtree.
+
+    Parameters
+    ----------
+    sequence : tuple
+        A nested tuple representing a rooted tree.
+
+    sensible_relabeling : bool
+        Whether to relabel the nodes of the tree so that nodes are
+        labeled in increasing order according to their breadth-first
+        search order from the root node.
+
+    Returns
+    -------
+    NetworkX graph
+        The tree corresponding to the given nested tuple, whose root
+        node is node 0. If ``sensible_labeling`` is ``True``, nodes will
+        be labeled in breadth-first search order starting from the root
+        node.
+
+    Notes
+    -----
+    This function is *not* the inverse of :func:`to_nested_tuple`; the
+    only guarantee is that the rooted trees are isomorphic.
+
+    See also
+    --------
+    to_nested_tuple
+    from_prufer_sequence
+
+    Examples
+    --------
+    Sensible relabeling ensures that the nodes are labeled from the root
+    starting at 0::
+
+        >>> balanced = (((), ()), ((), ()))
+        >>> T = nx.from_nested_tuple(balanced, sensible_relabeling=True)
+        >>> edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
+        >>> all((u, v) in T.edges() or (v, u) in T.edges() for (u, v) in edges)
+        True
+
+    """
+
+    def _make_tree(sequence):
+        """Recursively creates a tree from the given sequence of nested
+        tuples.
+
+        This function employs the :func:`~networkx.tree.join` function
+        to recursively join subtrees into a larger tree.
+
+        """
+        # The empty sequence represents the empty tree, which is the
+        # (unique) graph with a single node. We mark the single node
+        # with an attribute that indicates that it is the root of the
+        # graph.
+        if len(sequence) == 0:
+            return nx.empty_graph(1)
+        # For a nonempty sequence, get the subtrees for each child
+        # sequence and join all the subtrees at their roots. After
+        # joining the subtrees, the root is node 0.
+        return nx.tree.join_trees([(_make_tree(child), 0) for child in sequence])
+
+    # Make the tree and remove the `is_root` node attribute added by the
+    # helper function.
+    T = _make_tree(sequence)
+    if sensible_relabeling:
+        # Relabel the nodes according to their breadth-first search
+        # order, starting from the root node (that is, the node 0).
+        bfs_nodes = chain([0], (v for u, v in nx.bfs_edges(T, 0)))
+        labels = {v: i for i, v in enumerate(bfs_nodes)}
+        # We would like to use `copy=False`, but `relabel_nodes` doesn't
+        # allow a relabel mapping that can't be topologically sorted.
+        T = nx.relabel_nodes(T, labels)
+    return T
+
+
+@not_implemented_for("directed")
+@nx._dispatchable(graphs="T")
+def to_prufer_sequence(T):
+    r"""Returns the Prüfer sequence of the given tree.
+
+    A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and
+    *n* - 1, inclusive. The tree corresponding to a given Prüfer
+    sequence can be recovered by repeatedly joining a node in the
+    sequence with a node with the smallest potential degree according to
+    the sequence.
+
+    Parameters
+    ----------
+    T : NetworkX graph
+        An undirected graph object representing a tree.
+
+    Returns
+    -------
+    list
+        The Prüfer sequence of the given tree.
+
+    Raises
+    ------
+    NetworkXPointlessConcept
+        If the number of nodes in `T` is less than two.
+
+    NotATree
+        If `T` is not a tree.
+
+    KeyError
+        If the set of nodes in `T` is not {0, …, *n* - 1}.
+
+    Notes
+    -----
+    There is a bijection from labeled trees to Prüfer sequences. This
+    function is the inverse of the :func:`from_prufer_sequence`
+    function.
+
+    Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead
+    of from 0 to *n* - 1. This function requires nodes to be labeled in
+    the latter form. You can use :func:`~networkx.relabel_nodes` to
+    relabel the nodes of your tree to the appropriate format.
+
+    This implementation is from [1]_ and has a running time of
+    $O(n)$.
+
+    See also
+    --------
+    to_nested_tuple
+    from_prufer_sequence
+
+    References
+    ----------
+    .. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu.
+           "An optimal algorithm for Prufer codes."
+           *Journal of Software Engineering and Applications* 2.02 (2009): 111.
+           <https://doi.org/10.4236/jsea.2009.22016>
+
+    Examples
+    --------
+    There is a bijection between Prüfer sequences and labeled trees, so
+    this function is the inverse of the :func:`from_prufer_sequence`
+    function:
+
+    >>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
+    >>> tree = nx.Graph(edges)
+    >>> sequence = nx.to_prufer_sequence(tree)
+    >>> sequence
+    [3, 3, 3, 4]
+    >>> tree2 = nx.from_prufer_sequence(sequence)
+    >>> list(tree2.edges()) == edges
+    True
+
+    """
+    # Perform some sanity checks on the input.
+    n = len(T)
+    if n < 2:
+        msg = "Prüfer sequence undefined for trees with fewer than two nodes"
+        raise nx.NetworkXPointlessConcept(msg)
+    if not nx.is_tree(T):
+        raise nx.NotATree("provided graph is not a tree")
+    if set(T) != set(range(n)):
+        raise KeyError("tree must have node labels {0, ..., n - 1}")
+
+    degree = dict(T.degree())
+
+    def parents(u):
+        return next(v for v in T[u] if degree[v] > 1)
+
+    index = u = next(k for k in range(n) if degree[k] == 1)
+    result = []
+    for i in range(n - 2):
+        v = parents(u)
+        result.append(v)
+        degree[v] -= 1
+        if v < index and degree[v] == 1:
+            u = v
+        else:
+            index = u = next(k for k in range(index + 1, n) if degree[k] == 1)
+    return result
+
+
+@nx._dispatchable(graphs=None, returns_graph=True)
+def from_prufer_sequence(sequence):
+    r"""Returns the tree corresponding to the given Prüfer sequence.
+
+    A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and
+    *n* - 1, inclusive. The tree corresponding to a given Prüfer
+    sequence can be recovered by repeatedly joining a node in the
+    sequence with a node with the smallest potential degree according to
+    the sequence.
+
+    Parameters
+    ----------
+    sequence : list
+        A Prüfer sequence, which is a list of *n* - 2 integers between
+        zero and *n* - 1, inclusive.
+
+    Returns
+    -------
+    NetworkX graph
+        The tree corresponding to the given Prüfer sequence.
+
+    Raises
+    ------
+    NetworkXError
+        If the Prüfer sequence is not valid.
+
+    Notes
+    -----
+    There is a bijection from labeled trees to Prüfer sequences. This
+    function is the inverse of the :func:`from_prufer_sequence` function.
+
+    Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead
+    of from 0 to *n* - 1. This function requires nodes to be labeled in
+    the latter form. You can use :func:`networkx.relabel_nodes` to
+    relabel the nodes of your tree to the appropriate format.
+
+    This implementation is from [1]_ and has a running time of
+    $O(n)$.
+
+    References
+    ----------
+    .. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu.
+           "An optimal algorithm for Prufer codes."
+           *Journal of Software Engineering and Applications* 2.02 (2009): 111.
+           <https://doi.org/10.4236/jsea.2009.22016>
+
+    See also
+    --------
+    from_nested_tuple
+    to_prufer_sequence
+
+    Examples
+    --------
+    There is a bijection between Prüfer sequences and labeled trees, so
+    this function is the inverse of the :func:`to_prufer_sequence`
+    function:
+
+    >>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
+    >>> tree = nx.Graph(edges)
+    >>> sequence = nx.to_prufer_sequence(tree)
+    >>> sequence
+    [3, 3, 3, 4]
+    >>> tree2 = nx.from_prufer_sequence(sequence)
+    >>> list(tree2.edges()) == edges
+    True
+
+    """
+    n = len(sequence) + 2
+    # `degree` stores the remaining degree (plus one) for each node. The
+    # degree of a node in the decoded tree is one more than the number
+    # of times it appears in the code.
+    degree = Counter(chain(sequence, range(n)))
+    T = nx.empty_graph(n)
+    # `not_orphaned` is the set of nodes that have a parent in the
+    # tree. After the loop, there should be exactly two nodes that are
+    # not in this set.
+    not_orphaned = set()
+    index = u = next(k for k in range(n) if degree[k] == 1)
+    for v in sequence:
+        # check the validity of the prufer sequence
+        if v < 0 or v > n - 1:
+            raise nx.NetworkXError(
+                f"Invalid Prufer sequence: Values must be between 0 and {n-1}, got {v}"
+            )
+        T.add_edge(u, v)
+        not_orphaned.add(u)
+        degree[v] -= 1
+        if v < index and degree[v] == 1:
+            u = v
+        else:
+            index = u = next(k for k in range(index + 1, n) if degree[k] == 1)
+    # At this point, there must be exactly two orphaned nodes; join them.
+    orphans = set(T) - not_orphaned
+    u, v = orphans
+    T.add_edge(u, v)
+    return T