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

72 statements  

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

1""" 

2Operations on graphs including union, intersection, difference. 

3""" 

4import networkx as nx 

5 

6__all__ = [ 

7 "union", 

8 "compose", 

9 "disjoint_union", 

10 "intersection", 

11 "difference", 

12 "symmetric_difference", 

13 "full_join", 

14] 

15_G_H = {"G": 0, "H": 1} 

16 

17 

18@nx._dispatch(graphs=_G_H, preserve_all_attrs=True) 

19def union(G, H, rename=()): 

20 """Combine graphs G and H. The names of nodes must be unique. 

21 

22 A name collision between the graphs will raise an exception. 

23 

24 A renaming facility is provided to avoid name collisions. 

25 

26 

27 Parameters 

28 ---------- 

29 G, H : graph 

30 A NetworkX graph 

31 

32 rename : iterable , optional 

33 Node names of G and H can be changed by specifying the tuple 

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

35 "G-u" and "v" in H is renamed "H-v". 

36 

37 Returns 

38 ------- 

39 U : A union graph with the same type as G. 

40 

41 See Also 

42 -------- 

43 compose 

44 :func:`~networkx.Graph.update` 

45 disjoint_union 

46 

47 Notes 

48 ----- 

49 To combine graphs that have common nodes, consider compose(G, H) 

50 or the method, Graph.update(). 

51 

52 disjoint_union() is similar to union() except that it avoids name clashes 

53 by relabeling the nodes with sequential integers. 

54 

55 Edge and node attributes are propagated from G and H to the union graph. 

56 Graph attributes are also propagated, but if they are present in both G and H, 

57 then the value from H is used. 

58 

59 Examples 

60 -------- 

61 >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)]) 

62 >>> H = nx.Graph([(0, 1), (0, 3), (1, 3), (1, 2)]) 

63 >>> U = nx.union(G, H, rename=("G", "H")) 

64 >>> U.nodes 

65 NodeView(('G0', 'G1', 'G2', 'H0', 'H1', 'H3', 'H2')) 

66 >>> U.edges 

67 EdgeView([('G0', 'G1'), ('G0', 'G2'), ('G1', 'G2'), ('H0', 'H1'), ('H0', 'H3'), ('H1', 'H3'), ('H1', 'H2')]) 

68 

69 

70 """ 

71 return nx.union_all([G, H], rename) 

72 

73 

74@nx._dispatch(graphs=_G_H, preserve_all_attrs=True) 

75def disjoint_union(G, H): 

76 """Combine graphs G and H. The nodes are assumed to be unique (disjoint). 

77 

78 This algorithm automatically relabels nodes to avoid name collisions. 

79 

80 Parameters 

81 ---------- 

82 G,H : graph 

83 A NetworkX graph 

84 

85 Returns 

86 ------- 

87 U : A union graph with the same type as G. 

88 

89 See Also 

90 -------- 

91 union 

92 compose 

93 :func:`~networkx.Graph.update` 

94 

95 Notes 

96 ----- 

97 A new graph is created, of the same class as G. It is recommended 

98 that G and H be either both directed or both undirected. 

99 

100 The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are 

101 relabeled len(G) to len(G)+len(H)-1. 

102 

103 Renumbering forces G and H to be disjoint, so no exception is ever raised for a name collision. 

104 To preserve the check for common nodes, use union(). 

105 

106 Edge and node attributes are propagated from G and H to the union graph. 

107 Graph attributes are also propagated, but if they are present in both G and H, 

108 then the value from H is used. 

109 

110 To combine graphs that have common nodes, consider compose(G, H) 

111 or the method, Graph.update(). 

112 

113 Examples 

114 -------- 

115 >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)]) 

116 >>> H = nx.Graph([(0, 3), (1, 2), (2, 3)]) 

117 >>> G.nodes[0]["key1"] = 5 

118 >>> H.nodes[0]["key2"] = 10 

119 >>> U = nx.disjoint_union(G, H) 

120 >>> U.nodes(data=True) 

121 NodeDataView({0: {'key1': 5}, 1: {}, 2: {}, 3: {'key2': 10}, 4: {}, 5: {}, 6: {}}) 

122 >>> U.edges 

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

124 """ 

125 return nx.disjoint_union_all([G, H]) 

126 

127 

128@nx._dispatch(graphs=_G_H) 

129def intersection(G, H): 

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

131 both G and H. 

132 

133 Parameters 

134 ---------- 

135 G,H : graph 

136 A NetworkX graph. G and H can have different node sets but must be both graphs or both multigraphs. 

137 

138 Raises 

139 ------ 

140 NetworkXError 

141 If one is a MultiGraph and the other one is a graph. 

142 

143 Returns 

144 ------- 

145 GH : A new graph with the same type as G. 

146 

147 Notes 

148 ----- 

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

150 graph. If you want a new graph of the intersection of G and H 

151 with the attributes (including edge data) from G use remove_nodes_from() 

152 as follows 

153 

154 >>> G = nx.path_graph(3) 

155 >>> H = nx.path_graph(5) 

156 >>> R = G.copy() 

157 >>> R.remove_nodes_from(n for n in G if n not in H) 

158 >>> R.remove_edges_from(e for e in G.edges if e not in H.edges) 

159 

160 Examples 

161 -------- 

162 >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)]) 

163 >>> H = nx.Graph([(0, 3), (1, 2), (2, 3)]) 

164 >>> R = nx.intersection(G, H) 

165 >>> R.nodes 

166 NodeView((0, 1, 2)) 

167 >>> R.edges 

168 EdgeView([(1, 2)]) 

169 """ 

170 return nx.intersection_all([G, H]) 

171 

172 

173@nx._dispatch(graphs=_G_H) 

174def difference(G, H): 

175 """Returns a new graph that contains the edges that exist in G but not in H. 

176 

177 The node sets of H and G must be the same. 

178 

179 Parameters 

180 ---------- 

181 G,H : graph 

182 A NetworkX graph. G and H must have the same node sets. 

183 

184 Returns 

185 ------- 

186 D : A new graph with the same type as G. 

187 

188 Notes 

189 ----- 

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

191 graph. If you want a new graph of the difference of G and H with 

192 the attributes (including edge data) from G use remove_nodes_from() 

193 as follows: 

194 

195 >>> G = nx.path_graph(3) 

196 >>> H = nx.path_graph(5) 

197 >>> R = G.copy() 

198 >>> R.remove_nodes_from(n for n in G if n in H) 

199 

200 Examples 

201 -------- 

202 >>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3)]) 

203 >>> H = nx.Graph([(0, 1), (1, 2), (0, 3)]) 

204 >>> R = nx.difference(G, H) 

205 >>> R.nodes 

206 NodeView((0, 1, 2, 3)) 

207 >>> R.edges 

208 EdgeView([(0, 2), (1, 3)]) 

209 """ 

210 # create new graph 

211 if not G.is_multigraph() == H.is_multigraph(): 

212 raise nx.NetworkXError("G and H must both be graphs or multigraphs.") 

213 R = nx.create_empty_copy(G) 

214 

215 if set(G) != set(H): 

216 raise nx.NetworkXError("Node sets of graphs not equal") 

217 

218 if G.is_multigraph(): 

219 edges = G.edges(keys=True) 

220 else: 

221 edges = G.edges() 

222 for e in edges: 

223 if not H.has_edge(*e): 

224 R.add_edge(*e) 

225 return R 

226 

227 

228@nx._dispatch(graphs=_G_H) 

229def symmetric_difference(G, H): 

230 """Returns new graph with edges that exist in either G or H but not both. 

231 

232 The node sets of H and G must be the same. 

233 

234 Parameters 

235 ---------- 

236 G,H : graph 

237 A NetworkX graph. G and H must have the same node sets. 

238 

239 Returns 

240 ------- 

241 D : A new graph with the same type as G. 

242 

243 Notes 

244 ----- 

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

246 graph. 

247 

248 Examples 

249 -------- 

250 >>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3)]) 

251 >>> H = nx.Graph([(0, 1), (1, 2), (0, 3)]) 

252 >>> R = nx.symmetric_difference(G, H) 

253 >>> R.nodes 

254 NodeView((0, 1, 2, 3)) 

255 >>> R.edges 

256 EdgeView([(0, 2), (0, 3), (1, 3)]) 

257 """ 

258 # create new graph 

259 if not G.is_multigraph() == H.is_multigraph(): 

260 raise nx.NetworkXError("G and H must both be graphs or multigraphs.") 

261 R = nx.create_empty_copy(G) 

262 

263 if set(G) != set(H): 

264 raise nx.NetworkXError("Node sets of graphs not equal") 

265 

266 gnodes = set(G) # set of nodes in G 

267 hnodes = set(H) # set of nodes in H 

268 nodes = gnodes.symmetric_difference(hnodes) 

269 R.add_nodes_from(nodes) 

270 

271 if G.is_multigraph(): 

272 edges = G.edges(keys=True) 

273 else: 

274 edges = G.edges() 

275 # we could copy the data here but then this function doesn't 

276 # match intersection and difference 

277 for e in edges: 

278 if not H.has_edge(*e): 

279 R.add_edge(*e) 

280 

281 if H.is_multigraph(): 

282 edges = H.edges(keys=True) 

283 else: 

284 edges = H.edges() 

285 for e in edges: 

286 if not G.has_edge(*e): 

287 R.add_edge(*e) 

288 return R 

289 

290 

291@nx._dispatch(graphs=_G_H, preserve_all_attrs=True) 

292def compose(G, H): 

293 """Compose graph G with H by combining nodes and edges into a single graph. 

294 

295 The node sets and edges sets do not need to be disjoint. 

296 

297 Composing preserves the attributes of nodes and edges. 

298 Attribute values from H take precedent over attribute values from G. 

299 

300 Parameters 

301 ---------- 

302 G, H : graph 

303 A NetworkX graph 

304 

305 Returns 

306 ------- 

307 C: A new graph with the same type as G 

308 

309 See Also 

310 -------- 

311 :func:`~networkx.Graph.update` 

312 union 

313 disjoint_union 

314 

315 Notes 

316 ----- 

317 It is recommended that G and H be either both directed or both undirected. 

318 

319 For MultiGraphs, the edges are identified by incident nodes AND edge-key. 

320 This can cause surprises (i.e., edge `(1, 2)` may or may not be the same 

321 in two graphs) if you use MultiGraph without keeping track of edge keys. 

322 

323 If combining the attributes of common nodes is not desired, consider union(), 

324 which raises an exception for name collisions. 

325 

326 Examples 

327 -------- 

328 >>> G = nx.Graph([(0, 1), (0, 2)]) 

329 >>> H = nx.Graph([(0, 1), (1, 2)]) 

330 >>> R = nx.compose(G, H) 

331 >>> R.nodes 

332 NodeView((0, 1, 2)) 

333 >>> R.edges 

334 EdgeView([(0, 1), (0, 2), (1, 2)]) 

335 

336 By default, the attributes from `H` take precedent over attributes from `G`. 

337 If you prefer another way of combining attributes, you can update them after the compose operation: 

338 

339 >>> G = nx.Graph([(0, 1, {'weight': 2.0}), (3, 0, {'weight': 100.0})]) 

340 >>> H = nx.Graph([(0, 1, {'weight': 10.0}), (1, 2, {'weight': -1.0})]) 

341 >>> nx.set_node_attributes(G, {0: 'dark', 1: 'light', 3: 'black'}, name='color') 

342 >>> nx.set_node_attributes(H, {0: 'green', 1: 'orange', 2: 'yellow'}, name='color') 

343 >>> GcomposeH = nx.compose(G, H) 

344 

345 Normally, color attribute values of nodes of GcomposeH come from H. We can workaround this as follows: 

346 

347 >>> node_data = {n: G.nodes[n]['color'] + " " + H.nodes[n]['color'] for n in G.nodes & H.nodes} 

348 >>> nx.set_node_attributes(GcomposeH, node_data, 'color') 

349 >>> print(GcomposeH.nodes[0]['color']) 

350 dark green 

351 

352 >>> print(GcomposeH.nodes[3]['color']) 

353 black 

354 

355 Similarly, we can update edge attributes after the compose operation in a way we prefer: 

356 

357 >>> edge_data = {e: G.edges[e]['weight'] * H.edges[e]['weight'] for e in G.edges & H.edges} 

358 >>> nx.set_edge_attributes(GcomposeH, edge_data, 'weight') 

359 >>> print(GcomposeH.edges[(0, 1)]['weight']) 

360 20.0 

361 

362 >>> print(GcomposeH.edges[(3, 0)]['weight']) 

363 100.0 

364 """ 

365 return nx.compose_all([G, H]) 

366 

367 

368@nx._dispatch(graphs=_G_H, preserve_all_attrs=True) 

369def full_join(G, H, rename=(None, None)): 

370 """Returns the full join of graphs G and H. 

371 

372 Full join is the union of G and H in which all edges between 

373 G and H are added. 

374 The node sets of G and H must be disjoint, 

375 otherwise an exception is raised. 

376 

377 Parameters 

378 ---------- 

379 G, H : graph 

380 A NetworkX graph 

381 

382 rename : tuple , default=(None, None) 

383 Node names of G and H can be changed by specifying the tuple 

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

385 "G-u" and "v" in H is renamed "H-v". 

386 

387 Returns 

388 ------- 

389 U : The full join graph with the same type as G. 

390 

391 Notes 

392 ----- 

393 It is recommended that G and H be either both directed or both undirected. 

394 

395 If G is directed, then edges from G to H are added as well as from H to G. 

396 

397 Note that full_join() does not produce parallel edges for MultiGraphs. 

398 

399 The full join operation of graphs G and H is the same as getting 

400 their complement, performing a disjoint union, and finally getting 

401 the complement of the resulting graph. 

402 

403 Graph, edge, and node attributes are propagated from G and H 

404 to the union graph. If a graph attribute is present in both 

405 G and H the value from H is used. 

406 

407 Examples 

408 -------- 

409 >>> G = nx.Graph([(0, 1), (0, 2)]) 

410 >>> H = nx.Graph([(3, 4)]) 

411 >>> R = nx.full_join(G, H, rename=("G", "H")) 

412 >>> R.nodes 

413 NodeView(('G0', 'G1', 'G2', 'H3', 'H4')) 

414 >>> R.edges 

415 EdgeView([('G0', 'G1'), ('G0', 'G2'), ('G0', 'H3'), ('G0', 'H4'), ('G1', 'H3'), ('G1', 'H4'), ('G2', 'H3'), ('G2', 'H4'), ('H3', 'H4')]) 

416 

417 See Also 

418 -------- 

419 union 

420 disjoint_union 

421 """ 

422 R = union(G, H, rename) 

423 

424 def add_prefix(graph, prefix): 

425 if prefix is None: 

426 return graph 

427 

428 def label(x): 

429 return f"{prefix}{x}" 

430 

431 return nx.relabel_nodes(graph, label) 

432 

433 G = add_prefix(G, rename[0]) 

434 H = add_prefix(H, rename[1]) 

435 

436 for i in G: 

437 for j in H: 

438 R.add_edge(i, j) 

439 if R.is_directed(): 

440 for i in H: 

441 for j in G: 

442 R.add_edge(i, j) 

443 

444 return R