Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/tree/recognition.py: 46%

26 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1""" 

2Recognition Tests 

3================= 

4 

5A *forest* is an acyclic, undirected graph, and a *tree* is a connected forest. 

6Depending on the subfield, there are various conventions for generalizing these 

7definitions to directed graphs. 

8 

9In one convention, directed variants of forest and tree are defined in an 

10identical manner, except that the direction of the edges is ignored. In effect, 

11each directed edge is treated as a single undirected edge. Then, additional 

12restrictions are imposed to define *branchings* and *arborescences*. 

13 

14In another convention, directed variants of forest and tree correspond to 

15the previous convention's branchings and arborescences, respectively. Then two 

16new terms, *polyforest* and *polytree*, are defined to correspond to the other 

17convention's forest and tree. 

18 

19Summarizing:: 

20 

21 +-----------------------------+ 

22 | Convention A | Convention B | 

23 +=============================+ 

24 | forest | polyforest | 

25 | tree | polytree | 

26 | branching | forest | 

27 | arborescence | tree | 

28 +-----------------------------+ 

29 

30Each convention has its reasons. The first convention emphasizes definitional 

31similarity in that directed forests and trees are only concerned with 

32acyclicity and do not have an in-degree constraint, just as their undirected 

33counterparts do not. The second convention emphasizes functional similarity 

34in the sense that the directed analog of a spanning tree is a spanning 

35arborescence. That is, take any spanning tree and choose one node as the root. 

36Then every edge is assigned a direction such there is a directed path from the 

37root to every other node. The result is a spanning arborescence. 

38 

39NetworkX follows convention "A". Explicitly, these are: 

40 

41undirected forest 

42 An undirected graph with no undirected cycles. 

43 

44undirected tree 

45 A connected, undirected forest. 

46 

47directed forest 

48 A directed graph with no undirected cycles. Equivalently, the underlying 

49 graph structure (which ignores edge orientations) is an undirected forest. 

50 In convention B, this is known as a polyforest. 

51 

52directed tree 

53 A weakly connected, directed forest. Equivalently, the underlying graph 

54 structure (which ignores edge orientations) is an undirected tree. In 

55 convention B, this is known as a polytree. 

56 

57branching 

58 A directed forest with each node having, at most, one parent. So the maximum 

59 in-degree is equal to 1. In convention B, this is known as a forest. 

60 

61arborescence 

62 A directed tree with each node having, at most, one parent. So the maximum 

63 in-degree is equal to 1. In convention B, this is known as a tree. 

64 

65For trees and arborescences, the adjective "spanning" may be added to designate 

66that the graph, when considered as a forest/branching, consists of a single 

67tree/arborescence that includes all nodes in the graph. It is true, by 

68definition, that every tree/arborescence is spanning with respect to the nodes 

69that define the tree/arborescence and so, it might seem redundant to introduce 

70the notion of "spanning". However, the nodes may represent a subset of 

71nodes from a larger graph, and it is in this context that the term "spanning" 

72becomes a useful notion. 

73 

74""" 

75 

76import networkx as nx 

77 

78__all__ = ["is_arborescence", "is_branching", "is_forest", "is_tree"] 

79 

80 

81@nx.utils.not_implemented_for("undirected") 

82@nx._dispatch 

83def is_arborescence(G): 

84 """ 

85 Returns True if `G` is an arborescence. 

86 

87 An arborescence is a directed tree with maximum in-degree equal to 1. 

88 

89 Parameters 

90 ---------- 

91 G : graph 

92 The graph to test. 

93 

94 Returns 

95 ------- 

96 b : bool 

97 A boolean that is True if `G` is an arborescence. 

98 

99 Examples 

100 -------- 

101 >>> G = nx.DiGraph([(0, 1), (0, 2), (2, 3), (3, 4)]) 

102 >>> nx.is_arborescence(G) 

103 True 

104 >>> G.remove_edge(0, 1) 

105 >>> G.add_edge(1, 2) # maximum in-degree is 2 

106 >>> nx.is_arborescence(G) 

107 False 

108 

109 Notes 

110 ----- 

111 In another convention, an arborescence is known as a *tree*. 

112 

113 See Also 

114 -------- 

115 is_tree 

116 

117 """ 

118 return is_tree(G) and max(d for n, d in G.in_degree()) <= 1 

119 

120 

121@nx.utils.not_implemented_for("undirected") 

122@nx._dispatch 

123def is_branching(G): 

124 """ 

125 Returns True if `G` is a branching. 

126 

127 A branching is a directed forest with maximum in-degree equal to 1. 

128 

129 Parameters 

130 ---------- 

131 G : directed graph 

132 The directed graph to test. 

133 

134 Returns 

135 ------- 

136 b : bool 

137 A boolean that is True if `G` is a branching. 

138 

139 Examples 

140 -------- 

141 >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)]) 

142 >>> nx.is_branching(G) 

143 True 

144 >>> G.remove_edge(2, 3) 

145 >>> G.add_edge(3, 1) # maximum in-degree is 2 

146 >>> nx.is_branching(G) 

147 False 

148 

149 Notes 

150 ----- 

151 In another convention, a branching is also known as a *forest*. 

152 

153 See Also 

154 -------- 

155 is_forest 

156 

157 """ 

158 return is_forest(G) and max(d for n, d in G.in_degree()) <= 1 

159 

160 

161@nx._dispatch 

162def is_forest(G): 

163 """ 

164 Returns True if `G` is a forest. 

165 

166 A forest is a graph with no undirected cycles. 

167 

168 For directed graphs, `G` is a forest if the underlying graph is a forest. 

169 The underlying graph is obtained by treating each directed edge as a single 

170 undirected edge in a multigraph. 

171 

172 Parameters 

173 ---------- 

174 G : graph 

175 The graph to test. 

176 

177 Returns 

178 ------- 

179 b : bool 

180 A boolean that is True if `G` is a forest. 

181 

182 Raises 

183 ------ 

184 NetworkXPointlessConcept 

185 If `G` is empty. 

186 

187 Examples 

188 -------- 

189 >>> G = nx.Graph() 

190 >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)]) 

191 >>> nx.is_forest(G) 

192 True 

193 >>> G.add_edge(4, 1) 

194 >>> nx.is_forest(G) 

195 False 

196 

197 Notes 

198 ----- 

199 In another convention, a directed forest is known as a *polyforest* and 

200 then *forest* corresponds to a *branching*. 

201 

202 See Also 

203 -------- 

204 is_branching 

205 

206 """ 

207 if len(G) == 0: 

208 raise nx.exception.NetworkXPointlessConcept("G has no nodes.") 

209 

210 if G.is_directed(): 

211 components = (G.subgraph(c) for c in nx.weakly_connected_components(G)) 

212 else: 

213 components = (G.subgraph(c) for c in nx.connected_components(G)) 

214 

215 return all(len(c) - 1 == c.number_of_edges() for c in components) 

216 

217 

218@nx._dispatch 

219def is_tree(G): 

220 """ 

221 Returns True if `G` is a tree. 

222 

223 A tree is a connected graph with no undirected cycles. 

224 

225 For directed graphs, `G` is a tree if the underlying graph is a tree. The 

226 underlying graph is obtained by treating each directed edge as a single 

227 undirected edge in a multigraph. 

228 

229 Parameters 

230 ---------- 

231 G : graph 

232 The graph to test. 

233 

234 Returns 

235 ------- 

236 b : bool 

237 A boolean that is True if `G` is a tree. 

238 

239 Raises 

240 ------ 

241 NetworkXPointlessConcept 

242 If `G` is empty. 

243 

244 Examples 

245 -------- 

246 >>> G = nx.Graph() 

247 >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)]) 

248 >>> nx.is_tree(G) # n-1 edges 

249 True 

250 >>> G.add_edge(3, 4) 

251 >>> nx.is_tree(G) # n edges 

252 False 

253 

254 Notes 

255 ----- 

256 In another convention, a directed tree is known as a *polytree* and then 

257 *tree* corresponds to an *arborescence*. 

258 

259 See Also 

260 -------- 

261 is_arborescence 

262 

263 """ 

264 if len(G) == 0: 

265 raise nx.exception.NetworkXPointlessConcept("G has no nodes.") 

266 

267 if G.is_directed(): 

268 is_connected = nx.is_weakly_connected 

269 else: 

270 is_connected = nx.is_connected 

271 

272 # A connected graph with no cycles has n-1 edges. 

273 return len(G) - 1 == G.number_of_edges() and is_connected(G)