recognition.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. """
  2. Recognition Tests
  3. =================
  4. A *forest* is an acyclic, undirected graph, and a *tree* is a connected forest.
  5. Depending on the subfield, there are various conventions for generalizing these
  6. definitions to directed graphs.
  7. In one convention, directed variants of forest and tree are defined in an
  8. identical manner, except that the direction of the edges is ignored. In effect,
  9. each directed edge is treated as a single undirected edge. Then, additional
  10. restrictions are imposed to define *branchings* and *arborescences*.
  11. In another convention, directed variants of forest and tree correspond to
  12. the previous convention's branchings and arborescences, respectively. Then two
  13. new terms, *polyforest* and *polytree*, are defined to correspond to the other
  14. convention's forest and tree.
  15. Summarizing::
  16. +-----------------------------+
  17. | Convention A | Convention B |
  18. +=============================+
  19. | forest | polyforest |
  20. | tree | polytree |
  21. | branching | forest |
  22. | arborescence | tree |
  23. +-----------------------------+
  24. Each convention has its reasons. The first convention emphasizes definitional
  25. similarity in that directed forests and trees are only concerned with
  26. acyclicity and do not have an in-degree constraint, just as their undirected
  27. counterparts do not. The second convention emphasizes functional similarity
  28. in the sense that the directed analog of a spanning tree is a spanning
  29. arborescence. That is, take any spanning tree and choose one node as the root.
  30. Then every edge is assigned a direction such there is a directed path from the
  31. root to every other node. The result is a spanning arborescence.
  32. NetworkX follows convention "A". Explicitly, these are:
  33. undirected forest
  34. An undirected graph with no undirected cycles.
  35. undirected tree
  36. A connected, undirected forest.
  37. directed forest
  38. A directed graph with no undirected cycles. Equivalently, the underlying
  39. graph structure (which ignores edge orientations) is an undirected forest.
  40. In convention B, this is known as a polyforest.
  41. directed tree
  42. A weakly connected, directed forest. Equivalently, the underlying graph
  43. structure (which ignores edge orientations) is an undirected tree. In
  44. convention B, this is known as a polytree.
  45. branching
  46. A directed forest with each node having, at most, one parent. So the maximum
  47. in-degree is equal to 1. In convention B, this is known as a forest.
  48. arborescence
  49. A directed tree with each node having, at most, one parent. So the maximum
  50. in-degree is equal to 1. In convention B, this is known as a tree.
  51. For trees and arborescences, the adjective "spanning" may be added to designate
  52. that the graph, when considered as a forest/branching, consists of a single
  53. tree/arborescence that includes all nodes in the graph. It is true, by
  54. definition, that every tree/arborescence is spanning with respect to the nodes
  55. that define the tree/arborescence and so, it might seem redundant to introduce
  56. the notion of "spanning". However, the nodes may represent a subset of
  57. nodes from a larger graph, and it is in this context that the term "spanning"
  58. becomes a useful notion.
  59. """
  60. import networkx as nx
  61. __all__ = ["is_arborescence", "is_branching", "is_forest", "is_tree"]
  62. @nx.utils.not_implemented_for("undirected")
  63. @nx._dispatchable
  64. def is_arborescence(G):
  65. """
  66. Returns True if `G` is an arborescence.
  67. An arborescence is a directed tree with maximum in-degree equal to 1.
  68. Parameters
  69. ----------
  70. G : graph
  71. The graph to test.
  72. Returns
  73. -------
  74. b : bool
  75. A boolean that is True if `G` is an arborescence.
  76. Examples
  77. --------
  78. >>> G = nx.DiGraph([(0, 1), (0, 2), (2, 3), (3, 4)])
  79. >>> nx.is_arborescence(G)
  80. True
  81. >>> G.remove_edge(0, 1)
  82. >>> G.add_edge(1, 2) # maximum in-degree is 2
  83. >>> nx.is_arborescence(G)
  84. False
  85. Notes
  86. -----
  87. In another convention, an arborescence is known as a *tree*.
  88. See Also
  89. --------
  90. is_tree
  91. """
  92. return is_tree(G) and max(d for n, d in G.in_degree()) <= 1
  93. @nx.utils.not_implemented_for("undirected")
  94. @nx._dispatchable
  95. def is_branching(G):
  96. """
  97. Returns True if `G` is a branching.
  98. A branching is a directed forest with maximum in-degree equal to 1.
  99. Parameters
  100. ----------
  101. G : directed graph
  102. The directed graph to test.
  103. Returns
  104. -------
  105. b : bool
  106. A boolean that is True if `G` is a branching.
  107. Examples
  108. --------
  109. >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)])
  110. >>> nx.is_branching(G)
  111. True
  112. >>> G.remove_edge(2, 3)
  113. >>> G.add_edge(3, 1) # maximum in-degree is 2
  114. >>> nx.is_branching(G)
  115. False
  116. Notes
  117. -----
  118. In another convention, a branching is also known as a *forest*.
  119. See Also
  120. --------
  121. is_forest
  122. """
  123. return is_forest(G) and max(d for n, d in G.in_degree()) <= 1
  124. @nx._dispatchable
  125. def is_forest(G):
  126. """
  127. Returns True if `G` is a forest.
  128. A forest is a graph with no undirected cycles.
  129. For directed graphs, `G` is a forest if the underlying graph is a forest.
  130. The underlying graph is obtained by treating each directed edge as a single
  131. undirected edge in a multigraph.
  132. Parameters
  133. ----------
  134. G : graph
  135. The graph to test.
  136. Returns
  137. -------
  138. b : bool
  139. A boolean that is True if `G` is a forest.
  140. Raises
  141. ------
  142. NetworkXPointlessConcept
  143. If `G` is empty.
  144. Examples
  145. --------
  146. >>> G = nx.Graph()
  147. >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)])
  148. >>> nx.is_forest(G)
  149. True
  150. >>> G.add_edge(4, 1)
  151. >>> nx.is_forest(G)
  152. False
  153. Notes
  154. -----
  155. In another convention, a directed forest is known as a *polyforest* and
  156. then *forest* corresponds to a *branching*.
  157. See Also
  158. --------
  159. is_branching
  160. """
  161. if len(G) == 0:
  162. raise nx.exception.NetworkXPointlessConcept("G has no nodes.")
  163. if G.is_directed():
  164. components = (G.subgraph(c) for c in nx.weakly_connected_components(G))
  165. else:
  166. components = (G.subgraph(c) for c in nx.connected_components(G))
  167. return all(len(c) - 1 == c.number_of_edges() for c in components)
  168. @nx._dispatchable
  169. def is_tree(G):
  170. """
  171. Returns True if `G` is a tree.
  172. A tree is a connected graph with no undirected cycles.
  173. For directed graphs, `G` is a tree if the underlying graph is a tree. The
  174. underlying graph is obtained by treating each directed edge as a single
  175. undirected edge in a multigraph.
  176. Parameters
  177. ----------
  178. G : graph
  179. The graph to test.
  180. Returns
  181. -------
  182. b : bool
  183. A boolean that is True if `G` is a tree.
  184. Raises
  185. ------
  186. NetworkXPointlessConcept
  187. If `G` is empty.
  188. Examples
  189. --------
  190. >>> G = nx.Graph()
  191. >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)])
  192. >>> nx.is_tree(G) # n-1 edges
  193. True
  194. >>> G.add_edge(3, 4)
  195. >>> nx.is_tree(G) # n edges
  196. False
  197. Notes
  198. -----
  199. In another convention, a directed tree is known as a *polytree* and then
  200. *tree* corresponds to an *arborescence*.
  201. See Also
  202. --------
  203. is_arborescence
  204. """
  205. if len(G) == 0:
  206. raise nx.exception.NetworkXPointlessConcept("G has no nodes.")
  207. if G.is_directed():
  208. is_connected = nx.is_weakly_connected
  209. else:
  210. is_connected = nx.is_connected
  211. # A connected graph with no cycles has n-1 edges.
  212. return len(G) - 1 == G.number_of_edges() and is_connected(G)