about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py273
1 files changed, 273 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py
new file mode 100644
index 00000000..a9eae987
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py
@@ -0,0 +1,273 @@
+"""
+Recognition Tests
+=================
+
+A *forest* is an acyclic, undirected graph, and a *tree* is a connected forest.
+Depending on the subfield, there are various conventions for generalizing these
+definitions to directed graphs.
+
+In one convention, directed variants of forest and tree are defined in an
+identical manner, except that the direction of the edges is ignored. In effect,
+each directed edge is treated as a single undirected edge. Then, additional
+restrictions are imposed to define *branchings* and *arborescences*.
+
+In another convention, directed variants of forest and tree correspond to
+the previous convention's branchings and arborescences, respectively. Then two
+new terms, *polyforest* and *polytree*, are defined to correspond to the other
+convention's forest and tree.
+
+Summarizing::
+
+   +-----------------------------+
+   | Convention A | Convention B |
+   +=============================+
+   | forest       | polyforest   |
+   | tree         | polytree     |
+   | branching    | forest       |
+   | arborescence | tree         |
+   +-----------------------------+
+
+Each convention has its reasons. The first convention emphasizes definitional
+similarity in that directed forests and trees are only concerned with
+acyclicity and do not have an in-degree constraint, just as their undirected
+counterparts do not. The second convention emphasizes functional similarity
+in the sense that the directed analog of a spanning tree is a spanning
+arborescence. That is, take any spanning tree and choose one node as the root.
+Then every edge is assigned a direction such there is a directed path from the
+root to every other node. The result is a spanning arborescence.
+
+NetworkX follows convention "A". Explicitly, these are:
+
+undirected forest
+   An undirected graph with no undirected cycles.
+
+undirected tree
+   A connected, undirected forest.
+
+directed forest
+   A directed graph with no undirected cycles. Equivalently, the underlying
+   graph structure (which ignores edge orientations) is an undirected forest.
+   In convention B, this is known as a polyforest.
+
+directed tree
+   A weakly connected, directed forest. Equivalently, the underlying graph
+   structure (which ignores edge orientations) is an undirected tree. In
+   convention B, this is known as a polytree.
+
+branching
+   A directed forest with each node having, at most, one parent. So the maximum
+   in-degree is equal to 1. In convention B, this is known as a forest.
+
+arborescence
+   A directed tree with each node having, at most, one parent. So the maximum
+   in-degree is equal to 1. In convention B, this is known as a tree.
+
+For trees and arborescences, the adjective "spanning" may be added to designate
+that the graph, when considered as a forest/branching, consists of a single
+tree/arborescence that includes all nodes in the graph. It is true, by
+definition, that every tree/arborescence is spanning with respect to the nodes
+that define the tree/arborescence and so, it might seem redundant to introduce
+the notion of "spanning". However, the nodes may represent a subset of
+nodes from a larger graph, and it is in this context that the term "spanning"
+becomes a useful notion.
+
+"""
+
+import networkx as nx
+
+__all__ = ["is_arborescence", "is_branching", "is_forest", "is_tree"]
+
+
+@nx.utils.not_implemented_for("undirected")
+@nx._dispatchable
+def is_arborescence(G):
+    """
+    Returns True if `G` is an arborescence.
+
+    An arborescence is a directed tree with maximum in-degree equal to 1.
+
+    Parameters
+    ----------
+    G : graph
+        The graph to test.
+
+    Returns
+    -------
+    b : bool
+        A boolean that is True if `G` is an arborescence.
+
+    Examples
+    --------
+    >>> G = nx.DiGraph([(0, 1), (0, 2), (2, 3), (3, 4)])
+    >>> nx.is_arborescence(G)
+    True
+    >>> G.remove_edge(0, 1)
+    >>> G.add_edge(1, 2)  # maximum in-degree is 2
+    >>> nx.is_arborescence(G)
+    False
+
+    Notes
+    -----
+    In another convention, an arborescence is known as a *tree*.
+
+    See Also
+    --------
+    is_tree
+
+    """
+    return is_tree(G) and max(d for n, d in G.in_degree()) <= 1
+
+
+@nx.utils.not_implemented_for("undirected")
+@nx._dispatchable
+def is_branching(G):
+    """
+    Returns True if `G` is a branching.
+
+    A branching is a directed forest with maximum in-degree equal to 1.
+
+    Parameters
+    ----------
+    G : directed graph
+        The directed graph to test.
+
+    Returns
+    -------
+    b : bool
+        A boolean that is True if `G` is a branching.
+
+    Examples
+    --------
+    >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)])
+    >>> nx.is_branching(G)
+    True
+    >>> G.remove_edge(2, 3)
+    >>> G.add_edge(3, 1)  # maximum in-degree is 2
+    >>> nx.is_branching(G)
+    False
+
+    Notes
+    -----
+    In another convention, a branching is also known as a *forest*.
+
+    See Also
+    --------
+    is_forest
+
+    """
+    return is_forest(G) and max(d for n, d in G.in_degree()) <= 1
+
+
+@nx._dispatchable
+def is_forest(G):
+    """
+    Returns True if `G` is a forest.
+
+    A forest is a graph with no undirected cycles.
+
+    For directed graphs, `G` is a forest if the underlying graph is a forest.
+    The underlying graph is obtained by treating each directed edge as a single
+    undirected edge in a multigraph.
+
+    Parameters
+    ----------
+    G : graph
+        The graph to test.
+
+    Returns
+    -------
+    b : bool
+        A boolean that is True if `G` is a forest.
+
+    Raises
+    ------
+    NetworkXPointlessConcept
+        If `G` is empty.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)])
+    >>> nx.is_forest(G)
+    True
+    >>> G.add_edge(4, 1)
+    >>> nx.is_forest(G)
+    False
+
+    Notes
+    -----
+    In another convention, a directed forest is known as a *polyforest* and
+    then *forest* corresponds to a *branching*.
+
+    See Also
+    --------
+    is_branching
+
+    """
+    if len(G) == 0:
+        raise nx.exception.NetworkXPointlessConcept("G has no nodes.")
+
+    if G.is_directed():
+        components = (G.subgraph(c) for c in nx.weakly_connected_components(G))
+    else:
+        components = (G.subgraph(c) for c in nx.connected_components(G))
+
+    return all(len(c) - 1 == c.number_of_edges() for c in components)
+
+
+@nx._dispatchable
+def is_tree(G):
+    """
+    Returns True if `G` is a tree.
+
+    A tree is a connected graph with no undirected cycles.
+
+    For directed graphs, `G` is a tree if the underlying graph is a tree. The
+    underlying graph is obtained by treating each directed edge as a single
+    undirected edge in a multigraph.
+
+    Parameters
+    ----------
+    G : graph
+        The graph to test.
+
+    Returns
+    -------
+    b : bool
+        A boolean that is True if `G` is a tree.
+
+    Raises
+    ------
+    NetworkXPointlessConcept
+        If `G` is empty.
+
+    Examples
+    --------
+    >>> G = nx.Graph()
+    >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)])
+    >>> nx.is_tree(G)  # n-1 edges
+    True
+    >>> G.add_edge(3, 4)
+    >>> nx.is_tree(G)  # n edges
+    False
+
+    Notes
+    -----
+    In another convention, a directed tree is known as a *polytree* and then
+    *tree* corresponds to an *arborescence*.
+
+    See Also
+    --------
+    is_arborescence
+
+    """
+    if len(G) == 0:
+        raise nx.exception.NetworkXPointlessConcept("G has no nodes.")
+
+    if G.is_directed():
+        is_connected = nx.is_weakly_connected
+    else:
+        is_connected = nx.is_connected
+
+    # A connected graph with no cycles has n-1 edges.
+    return len(G) - 1 == G.number_of_edges() and is_connected(G)