Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/operators/all.py: 13%

82 statements  

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

1"""Operations on many graphs. 

2""" 

3from itertools import chain, repeat 

4 

5import networkx as nx 

6 

7__all__ = ["union_all", "compose_all", "disjoint_union_all", "intersection_all"] 

8 

9 

10@nx._dispatch(graphs="[graphs]", preserve_all_attrs=True) 

11def union_all(graphs, rename=()): 

12 """Returns the union of all graphs. 

13 

14 The graphs must be disjoint, otherwise an exception is raised. 

15 

16 Parameters 

17 ---------- 

18 graphs : iterable 

19 Iterable of NetworkX graphs 

20 

21 rename : iterable , optional 

22 Node names of graphs can be changed by specifying the tuple 

23 rename=('G-','H-') (for example). Node "u" in G is then renamed 

24 "G-u" and "v" in H is renamed "H-v". Infinite generators (like itertools.count) 

25 are also supported. 

26 

27 Returns 

28 ------- 

29 U : a graph with the same type as the first graph in list 

30 

31 Raises 

32 ------ 

33 ValueError 

34 If `graphs` is an empty list. 

35 

36 NetworkXError 

37 In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. 

38 

39 Notes 

40 ----- 

41 For operating on mixed type graphs, they should be converted to the same type. 

42 >>> G = nx.Graph() 

43 >>> H = nx.DiGraph() 

44 >>> GH = union_all([nx.DiGraph(G), H]) 

45 

46 To force a disjoint union with node relabeling, use 

47 disjoint_union_all(G,H) or convert_node_labels_to integers(). 

48 

49 Graph, edge, and node attributes are propagated to the union graph. 

50 If a graph attribute is present in multiple graphs, then the value 

51 from the last graph in the list with that attribute is used. 

52 

53 Examples 

54 -------- 

55 >>> G1 = nx.Graph([(1, 2), (2, 3)]) 

56 >>> G2 = nx.Graph([(4, 5), (5, 6)]) 

57 >>> result_graph = nx.union_all([G1, G2]) 

58 >>> result_graph.nodes() 

59 NodeView((1, 2, 3, 4, 5, 6)) 

60 >>> result_graph.edges() 

61 EdgeView([(1, 2), (2, 3), (4, 5), (5, 6)]) 

62 

63 See Also 

64 -------- 

65 union 

66 disjoint_union_all 

67 """ 

68 R = None 

69 seen_nodes = set() 

70 

71 # rename graph to obtain disjoint node labels 

72 def add_prefix(graph, prefix): 

73 if prefix is None: 

74 return graph 

75 

76 def label(x): 

77 return f"{prefix}{x}" 

78 

79 return nx.relabel_nodes(graph, label) 

80 

81 rename = chain(rename, repeat(None)) 

82 graphs = (add_prefix(G, name) for G, name in zip(graphs, rename)) 

83 

84 for i, G in enumerate(graphs): 

85 G_nodes_set = set(G.nodes) 

86 if i == 0: 

87 # Union is the same type as first graph 

88 R = G.__class__() 

89 elif G.is_directed() != R.is_directed(): 

90 raise nx.NetworkXError("All graphs must be directed or undirected.") 

91 elif G.is_multigraph() != R.is_multigraph(): 

92 raise nx.NetworkXError("All graphs must be graphs or multigraphs.") 

93 elif not seen_nodes.isdisjoint(G_nodes_set): 

94 raise nx.NetworkXError( 

95 "The node sets of the graphs are not disjoint.", 

96 "Use appropriate rename" 

97 "=(G1prefix,G2prefix,...,GNprefix)" 

98 "or use disjoint_union(G1,G2,...,GN).", 

99 ) 

100 

101 seen_nodes |= G_nodes_set 

102 R.graph.update(G.graph) 

103 R.add_nodes_from(G.nodes(data=True)) 

104 R.add_edges_from( 

105 G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) 

106 ) 

107 

108 if R is None: 

109 raise ValueError("cannot apply union_all to an empty list") 

110 

111 return R 

112 

113 

114@nx._dispatch(graphs="[graphs]", preserve_all_attrs=True) 

115def disjoint_union_all(graphs): 

116 """Returns the disjoint union of all graphs. 

117 

118 This operation forces distinct integer node labels starting with 0 

119 for the first graph in the list and numbering consecutively. 

120 

121 Parameters 

122 ---------- 

123 graphs : iterable 

124 Iterable of NetworkX graphs 

125 

126 Returns 

127 ------- 

128 U : A graph with the same type as the first graph in list 

129 

130 Raises 

131 ------ 

132 ValueError 

133 If `graphs` is an empty list. 

134 

135 NetworkXError 

136 In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. 

137 

138 Examples 

139 -------- 

140 >>> G1 = nx.Graph([(1, 2), (2, 3)]) 

141 >>> G2 = nx.Graph([(4, 5), (5, 6)]) 

142 >>> U = nx.disjoint_union_all([G1, G2]) 

143 >>> list(U.nodes()) 

144 [0, 1, 2, 3, 4, 5] 

145 >>> list(U.edges()) 

146 [(0, 1), (1, 2), (3, 4), (4, 5)] 

147 

148 Notes 

149 ----- 

150 For operating on mixed type graphs, they should be converted to the same type. 

151 

152 Graph, edge, and node attributes are propagated to the union graph. 

153 If a graph attribute is present in multiple graphs, then the value 

154 from the last graph in the list with that attribute is used. 

155 """ 

156 

157 def yield_relabeled(graphs): 

158 first_label = 0 

159 for G in graphs: 

160 yield nx.convert_node_labels_to_integers(G, first_label=first_label) 

161 first_label += len(G) 

162 

163 R = union_all(yield_relabeled(graphs)) 

164 

165 return R 

166 

167 

168@nx._dispatch(graphs="[graphs]", preserve_all_attrs=True) 

169def compose_all(graphs): 

170 """Returns the composition of all graphs. 

171 

172 Composition is the simple union of the node sets and edge sets. 

173 The node sets of the supplied graphs need not be disjoint. 

174 

175 Parameters 

176 ---------- 

177 graphs : iterable 

178 Iterable of NetworkX graphs 

179 

180 Returns 

181 ------- 

182 C : A graph with the same type as the first graph in list 

183 

184 Raises 

185 ------ 

186 ValueError 

187 If `graphs` is an empty list. 

188 

189 NetworkXError 

190 In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. 

191 

192 Examples 

193 -------- 

194 >>> G1 = nx.Graph([(1, 2), (2, 3)]) 

195 >>> G2 = nx.Graph([(3, 4), (5, 6)]) 

196 >>> C = nx.compose_all([G1, G2]) 

197 >>> list(C.nodes()) 

198 [1, 2, 3, 4, 5, 6] 

199 >>> list(C.edges()) 

200 [(1, 2), (2, 3), (3, 4), (5, 6)] 

201 

202 Notes 

203 ----- 

204 For operating on mixed type graphs, they should be converted to the same type. 

205 

206 Graph, edge, and node attributes are propagated to the union graph. 

207 If a graph attribute is present in multiple graphs, then the value 

208 from the last graph in the list with that attribute is used. 

209 """ 

210 R = None 

211 

212 # add graph attributes, H attributes take precedent over G attributes 

213 for i, G in enumerate(graphs): 

214 if i == 0: 

215 # create new graph 

216 R = G.__class__() 

217 elif G.is_directed() != R.is_directed(): 

218 raise nx.NetworkXError("All graphs must be directed or undirected.") 

219 elif G.is_multigraph() != R.is_multigraph(): 

220 raise nx.NetworkXError("All graphs must be graphs or multigraphs.") 

221 

222 R.graph.update(G.graph) 

223 R.add_nodes_from(G.nodes(data=True)) 

224 R.add_edges_from( 

225 G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) 

226 ) 

227 

228 if R is None: 

229 raise ValueError("cannot apply compose_all to an empty list") 

230 

231 return R 

232 

233 

234@nx._dispatch(graphs="[graphs]") 

235def intersection_all(graphs): 

236 """Returns a new graph that contains only the nodes and the edges that exist in 

237 all graphs. 

238 

239 Parameters 

240 ---------- 

241 graphs : iterable 

242 Iterable of NetworkX graphs 

243 

244 Returns 

245 ------- 

246 R : A new graph with the same type as the first graph in list 

247 

248 Raises 

249 ------ 

250 ValueError 

251 If `graphs` is an empty list. 

252 

253 NetworkXError 

254 In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. 

255 

256 Notes 

257 ----- 

258 For operating on mixed type graphs, they should be converted to the same type. 

259 

260 Attributes from the graph, nodes, and edges are not copied to the new 

261 graph. 

262 

263 The resulting graph can be updated with attributes if desired. For example, code which adds the minimum attribute for each node across all graphs could work. 

264 >>> g = nx.Graph() 

265 >>> g.add_node(0, capacity=4) 

266 >>> g.add_node(1, capacity=3) 

267 >>> g.add_edge(0, 1) 

268 

269 >>> h = g.copy() 

270 >>> h.nodes[0]["capacity"] = 2 

271 

272 >>> gh = nx.intersection_all([g, h]) 

273 

274 >>> new_node_attr = {n: min(*(anyG.nodes[n].get('capacity', float('inf')) for anyG in [g, h])) for n in gh} 

275 >>> nx.set_node_attributes(gh, new_node_attr, 'new_capacity') 

276 >>> gh.nodes(data=True) 

277 NodeDataView({0: {'new_capacity': 2}, 1: {'new_capacity': 3}}) 

278 

279 Examples 

280 -------- 

281 >>> G1 = nx.Graph([(1, 2), (2, 3)]) 

282 >>> G2 = nx.Graph([(2, 3), (3, 4)]) 

283 >>> R = nx.intersection_all([G1, G2]) 

284 >>> list(R.nodes()) 

285 [2, 3] 

286 >>> list(R.edges()) 

287 [(2, 3)] 

288 

289 """ 

290 R = None 

291 

292 for i, G in enumerate(graphs): 

293 G_nodes_set = set(G.nodes) 

294 G_edges_set = set(G.edges) 

295 if not G.is_directed(): 

296 if G.is_multigraph(): 

297 G_edges_set.update((v, u, k) for u, v, k in list(G_edges_set)) 

298 else: 

299 G_edges_set.update((v, u) for u, v in list(G_edges_set)) 

300 if i == 0: 

301 # create new graph 

302 R = G.__class__() 

303 node_intersection = G_nodes_set 

304 edge_intersection = G_edges_set 

305 elif G.is_directed() != R.is_directed(): 

306 raise nx.NetworkXError("All graphs must be directed or undirected.") 

307 elif G.is_multigraph() != R.is_multigraph(): 

308 raise nx.NetworkXError("All graphs must be graphs or multigraphs.") 

309 else: 

310 node_intersection &= G_nodes_set 

311 edge_intersection &= G_edges_set 

312 

313 if R is None: 

314 raise ValueError("cannot apply intersection_all to an empty list") 

315 

316 R.add_nodes_from(node_intersection) 

317 R.add_edges_from(edge_intersection) 

318 

319 return R