Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/operators/product.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

186 statements  

1""" 

2Graph products. 

3""" 

4 

5from itertools import product 

6 

7import networkx as nx 

8from networkx.utils import not_implemented_for 

9 

10__all__ = [ 

11 "tensor_product", 

12 "cartesian_product", 

13 "lexicographic_product", 

14 "strong_product", 

15 "power", 

16 "rooted_product", 

17 "corona_product", 

18 "modular_product", 

19] 

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

21 

22 

23def _dict_product(d1, d2): 

24 return {k: (d1.get(k), d2.get(k)) for k in set(d1) | set(d2)} 

25 

26 

27# Generators for producing graph products 

28def _node_product(G, H): 

29 for u, v in product(G, H): 

30 yield ((u, v), _dict_product(G.nodes[u], H.nodes[v])) 

31 

32 

33def _directed_edges_cross_edges(G, H): 

34 if not G.is_multigraph() and not H.is_multigraph(): 

35 for u, v, c in G.edges(data=True): 

36 for x, y, d in H.edges(data=True): 

37 yield (u, x), (v, y), _dict_product(c, d) 

38 if not G.is_multigraph() and H.is_multigraph(): 

39 for u, v, c in G.edges(data=True): 

40 for x, y, k, d in H.edges(data=True, keys=True): 

41 yield (u, x), (v, y), k, _dict_product(c, d) 

42 if G.is_multigraph() and not H.is_multigraph(): 

43 for u, v, k, c in G.edges(data=True, keys=True): 

44 for x, y, d in H.edges(data=True): 

45 yield (u, x), (v, y), k, _dict_product(c, d) 

46 if G.is_multigraph() and H.is_multigraph(): 

47 for u, v, j, c in G.edges(data=True, keys=True): 

48 for x, y, k, d in H.edges(data=True, keys=True): 

49 yield (u, x), (v, y), (j, k), _dict_product(c, d) 

50 

51 

52def _undirected_edges_cross_edges(G, H): 

53 if not G.is_multigraph() and not H.is_multigraph(): 

54 for u, v, c in G.edges(data=True): 

55 for x, y, d in H.edges(data=True): 

56 yield (v, x), (u, y), _dict_product(c, d) 

57 if not G.is_multigraph() and H.is_multigraph(): 

58 for u, v, c in G.edges(data=True): 

59 for x, y, k, d in H.edges(data=True, keys=True): 

60 yield (v, x), (u, y), k, _dict_product(c, d) 

61 if G.is_multigraph() and not H.is_multigraph(): 

62 for u, v, k, c in G.edges(data=True, keys=True): 

63 for x, y, d in H.edges(data=True): 

64 yield (v, x), (u, y), k, _dict_product(c, d) 

65 if G.is_multigraph() and H.is_multigraph(): 

66 for u, v, j, c in G.edges(data=True, keys=True): 

67 for x, y, k, d in H.edges(data=True, keys=True): 

68 yield (v, x), (u, y), (j, k), _dict_product(c, d) 

69 

70 

71def _edges_cross_nodes(G, H): 

72 if G.is_multigraph(): 

73 for u, v, k, d in G.edges(data=True, keys=True): 

74 for x in H: 

75 yield (u, x), (v, x), k, d 

76 else: 

77 for u, v, d in G.edges(data=True): 

78 for x in H: 

79 if H.is_multigraph(): 

80 yield (u, x), (v, x), None, d 

81 else: 

82 yield (u, x), (v, x), d 

83 

84 

85def _nodes_cross_edges(G, H): 

86 if H.is_multigraph(): 

87 for x in G: 

88 for u, v, k, d in H.edges(data=True, keys=True): 

89 yield (x, u), (x, v), k, d 

90 else: 

91 for x in G: 

92 for u, v, d in H.edges(data=True): 

93 if G.is_multigraph(): 

94 yield (x, u), (x, v), None, d 

95 else: 

96 yield (x, u), (x, v), d 

97 

98 

99def _edges_cross_nodes_and_nodes(G, H): 

100 if G.is_multigraph(): 

101 for u, v, k, d in G.edges(data=True, keys=True): 

102 for x in H: 

103 for y in H: 

104 yield (u, x), (v, y), k, d 

105 else: 

106 for u, v, d in G.edges(data=True): 

107 for x in H: 

108 for y in H: 

109 if H.is_multigraph(): 

110 yield (u, x), (v, y), None, d 

111 else: 

112 yield (u, x), (v, y), d 

113 

114 

115def _init_product_graph(G, H): 

116 if G.is_directed() != H.is_directed(): 

117 msg = "G and H must be both directed or both undirected" 

118 raise nx.NetworkXError(msg) 

119 if G.is_multigraph() or H.is_multigraph(): 

120 GH = nx.MultiGraph() 

121 else: 

122 GH = nx.Graph() 

123 if G.is_directed(): 

124 GH = GH.to_directed() 

125 return GH 

126 

127 

128@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) 

129def tensor_product(G, H): 

130 r"""Returns the tensor product of G and H. 

131 

132 The tensor product $P$ of the graphs $G$ and $H$ has a node set that 

133 is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. 

134 $P$ has an edge $((u,v), (x,y))$ if and only if $(u,x)$ is an edge in $G$ 

135 and $(v,y)$ is an edge in $H$. 

136 

137 Tensor product is sometimes also referred to as the categorical product, 

138 direct product, cardinal product or conjunction. 

139 

140 

141 Parameters 

142 ---------- 

143 G, H: graphs 

144 Networkx graphs. 

145 

146 Returns 

147 ------- 

148 P: NetworkX graph 

149 The tensor product of G and H. P will be a multi-graph if either G 

150 or H is a multi-graph, will be a directed if G and H are directed, 

151 and undirected if G and H are undirected. 

152 

153 Raises 

154 ------ 

155 NetworkXError 

156 If G and H are not both directed or both undirected. 

157 

158 Notes 

159 ----- 

160 Node attributes in P are two-tuple of the G and H node attributes. 

161 Missing attributes are assigned None. 

162 

163 Examples 

164 -------- 

165 >>> G = nx.Graph() 

166 >>> H = nx.Graph() 

167 >>> G.add_node(0, a1=True) 

168 >>> H.add_node("a", a2="Spam") 

169 >>> P = nx.tensor_product(G, H) 

170 >>> list(P) 

171 [(0, 'a')] 

172 

173 Edge attributes and edge keys (for multigraphs) are also copied to the 

174 new product graph 

175 """ 

176 GH = _init_product_graph(G, H) 

177 GH.add_nodes_from(_node_product(G, H)) 

178 GH.add_edges_from(_directed_edges_cross_edges(G, H)) 

179 if not GH.is_directed(): 

180 GH.add_edges_from(_undirected_edges_cross_edges(G, H)) 

181 return GH 

182 

183 

184@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) 

185def cartesian_product(G, H): 

186 r"""Returns the Cartesian product of G and H. 

187 

188 The Cartesian product $P$ of the graphs $G$ and $H$ has a node set that 

189 is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. 

190 $P$ has an edge $((u,v),(x,y))$ if and only if either $u$ is equal to $x$ 

191 and both $v$ and $y$ are adjacent in $H$ or if $v$ is equal to $y$ and 

192 both $u$ and $x$ are adjacent in $G$. 

193 

194 Parameters 

195 ---------- 

196 G, H: graphs 

197 Networkx graphs. 

198 

199 Returns 

200 ------- 

201 P: NetworkX graph 

202 The Cartesian product of G and H. P will be a multi-graph if either G 

203 or H is a multi-graph. Will be a directed if G and H are directed, 

204 and undirected if G and H are undirected. 

205 

206 Raises 

207 ------ 

208 NetworkXError 

209 If G and H are not both directed or both undirected. 

210 

211 Notes 

212 ----- 

213 Node attributes in P are two-tuple of the G and H node attributes. 

214 Missing attributes are assigned None. 

215 

216 Examples 

217 -------- 

218 >>> G = nx.Graph() 

219 >>> H = nx.Graph() 

220 >>> G.add_node(0, a1=True) 

221 >>> H.add_node("a", a2="Spam") 

222 >>> P = nx.cartesian_product(G, H) 

223 >>> list(P) 

224 [(0, 'a')] 

225 

226 Edge attributes and edge keys (for multigraphs) are also copied to the 

227 new product graph 

228 """ 

229 GH = _init_product_graph(G, H) 

230 GH.add_nodes_from(_node_product(G, H)) 

231 GH.add_edges_from(_edges_cross_nodes(G, H)) 

232 GH.add_edges_from(_nodes_cross_edges(G, H)) 

233 return GH 

234 

235 

236@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) 

237def lexicographic_product(G, H): 

238 r"""Returns the lexicographic product of G and H. 

239 

240 The lexicographical product $P$ of the graphs $G$ and $H$ has a node set 

241 that is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. 

242 $P$ has an edge $((u,v), (x,y))$ if and only if $(u,v)$ is an edge in $G$ 

243 or $u==v$ and $(x,y)$ is an edge in $H$. 

244 

245 Parameters 

246 ---------- 

247 G, H: graphs 

248 Networkx graphs. 

249 

250 Returns 

251 ------- 

252 P: NetworkX graph 

253 The Cartesian product of G and H. P will be a multi-graph if either G 

254 or H is a multi-graph. Will be a directed if G and H are directed, 

255 and undirected if G and H are undirected. 

256 

257 Raises 

258 ------ 

259 NetworkXError 

260 If G and H are not both directed or both undirected. 

261 

262 Notes 

263 ----- 

264 Node attributes in P are two-tuple of the G and H node attributes. 

265 Missing attributes are assigned None. 

266 

267 Examples 

268 -------- 

269 >>> G = nx.Graph() 

270 >>> H = nx.Graph() 

271 >>> G.add_node(0, a1=True) 

272 >>> H.add_node("a", a2="Spam") 

273 >>> P = nx.lexicographic_product(G, H) 

274 >>> list(P) 

275 [(0, 'a')] 

276 

277 Edge attributes and edge keys (for multigraphs) are also copied to the 

278 new product graph 

279 """ 

280 GH = _init_product_graph(G, H) 

281 GH.add_nodes_from(_node_product(G, H)) 

282 # Edges in G regardless of H designation 

283 GH.add_edges_from(_edges_cross_nodes_and_nodes(G, H)) 

284 # For each x in G, only if there is an edge in H 

285 GH.add_edges_from(_nodes_cross_edges(G, H)) 

286 return GH 

287 

288 

289@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) 

290def strong_product(G, H): 

291 r"""Returns the strong product of G and H. 

292 

293 The strong product $P$ of the graphs $G$ and $H$ has a node set that 

294 is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. 

295 $P$ has an edge $((u,x), (v,y))$ if any of the following conditions 

296 are met: 

297 

298 - $u=v$ and $(x,y)$ is an edge in $H$ 

299 - $x=y$ and $(u,v)$ is an edge in $G$ 

300 - $(u,v)$ is an edge in $G$ and $(x,y)$ is an edge in $H$ 

301 

302 Parameters 

303 ---------- 

304 G, H: graphs 

305 Networkx graphs. 

306 

307 Returns 

308 ------- 

309 P: NetworkX graph 

310 The Cartesian product of G and H. P will be a multi-graph if either G 

311 or H is a multi-graph. Will be a directed if G and H are directed, 

312 and undirected if G and H are undirected. 

313 

314 Raises 

315 ------ 

316 NetworkXError 

317 If G and H are not both directed or both undirected. 

318 

319 Notes 

320 ----- 

321 Node attributes in P are two-tuple of the G and H node attributes. 

322 Missing attributes are assigned None. 

323 

324 Examples 

325 -------- 

326 >>> G = nx.Graph() 

327 >>> H = nx.Graph() 

328 >>> G.add_node(0, a1=True) 

329 >>> H.add_node("a", a2="Spam") 

330 >>> P = nx.strong_product(G, H) 

331 >>> list(P) 

332 [(0, 'a')] 

333 

334 Edge attributes and edge keys (for multigraphs) are also copied to the 

335 new product graph 

336 """ 

337 GH = _init_product_graph(G, H) 

338 GH.add_nodes_from(_node_product(G, H)) 

339 GH.add_edges_from(_nodes_cross_edges(G, H)) 

340 GH.add_edges_from(_edges_cross_nodes(G, H)) 

341 GH.add_edges_from(_directed_edges_cross_edges(G, H)) 

342 if not GH.is_directed(): 

343 GH.add_edges_from(_undirected_edges_cross_edges(G, H)) 

344 return GH 

345 

346 

347@not_implemented_for("directed") 

348@not_implemented_for("multigraph") 

349@nx._dispatchable(returns_graph=True) 

350def power(G, k): 

351 """Returns the specified power of a graph. 

352 

353 The $k$th power of a simple graph $G$, denoted $G^k$, is a 

354 graph on the same set of nodes in which two distinct nodes $u$ and 

355 $v$ are adjacent in $G^k$ if and only if the shortest path 

356 distance between $u$ and $v$ in $G$ is at most $k$. 

357 

358 Parameters 

359 ---------- 

360 G : graph 

361 A NetworkX simple graph object. 

362 

363 k : positive integer 

364 The power to which to raise the graph `G`. 

365 

366 Returns 

367 ------- 

368 NetworkX simple graph 

369 `G` to the power `k`. 

370 

371 Raises 

372 ------ 

373 ValueError 

374 If the exponent `k` is not positive. 

375 

376 NetworkXNotImplemented 

377 If `G` is not a simple graph. 

378 

379 Examples 

380 -------- 

381 The number of edges will never decrease when taking successive 

382 powers: 

383 

384 >>> G = nx.path_graph(4) 

385 >>> list(nx.power(G, 2).edges) 

386 [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)] 

387 >>> list(nx.power(G, 3).edges) 

388 [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] 

389 

390 The `k` th power of a cycle graph on *n* nodes is the complete graph 

391 on *n* nodes, if `k` is at least ``n // 2``: 

392 

393 >>> G = nx.cycle_graph(5) 

394 >>> H = nx.complete_graph(5) 

395 >>> nx.is_isomorphic(nx.power(G, 2), H) 

396 True 

397 >>> G = nx.cycle_graph(8) 

398 >>> H = nx.complete_graph(8) 

399 >>> nx.is_isomorphic(nx.power(G, 4), H) 

400 True 

401 

402 References 

403 ---------- 

404 .. [1] J. A. Bondy, U. S. R. Murty, *Graph Theory*. Springer, 2008. 

405 

406 Notes 

407 ----- 

408 This definition of "power graph" comes from Exercise 3.1.6 of 

409 *Graph Theory* by Bondy and Murty [1]_. 

410 

411 """ 

412 if k <= 0: 

413 raise ValueError("k must be a positive integer") 

414 H = nx.Graph() 

415 H.add_nodes_from(G) 

416 # update BFS code to ignore self loops. 

417 for n in G: 

418 seen = {} # level (number of hops) when seen in BFS 

419 level = 1 # the current level 

420 nextlevel = G[n] 

421 while nextlevel: 

422 thislevel = nextlevel # advance to next level 

423 nextlevel = {} # and start a new list (fringe) 

424 for v in thislevel: 

425 if v == n: # avoid self loop 

426 continue 

427 if v not in seen: 

428 seen[v] = level # set the level of vertex v 

429 nextlevel.update(G[v]) # add neighbors of v 

430 if k <= level: 

431 break 

432 level += 1 

433 H.add_edges_from((n, nbr) for nbr in seen) 

434 return H 

435 

436 

437@not_implemented_for("multigraph") 

438@nx._dispatchable(graphs=_G_H, returns_graph=True) 

439def rooted_product(G, H, root): 

440 """Return the rooted product of graphs G and H rooted at root in H. 

441 

442 A new graph is constructed representing the rooted product of 

443 the inputted graphs, G and H, with a root in H. 

444 A rooted product duplicates H for each nodes in G with the root 

445 of H corresponding to the node in G. Nodes are renamed as the direct 

446 product of G and H. The result is a subgraph of the cartesian product. 

447 

448 Parameters 

449 ---------- 

450 G,H : graph 

451 A NetworkX graph 

452 root : node 

453 A node in H 

454 

455 Returns 

456 ------- 

457 R : The rooted product of G and H with a specified root in H 

458 

459 Notes 

460 ----- 

461 The nodes of R are the Cartesian Product of the nodes of G and H. 

462 The nodes of G and H are not relabeled. 

463 """ 

464 if root not in H: 

465 raise nx.NodeNotFound("root must be a vertex in H") 

466 

467 R = nx.Graph() 

468 R.add_nodes_from(product(G, H)) 

469 

470 R.add_edges_from(((e[0], root), (e[1], root)) for e in G.edges()) 

471 R.add_edges_from(((g, e[0]), (g, e[1])) for g in G for e in H.edges()) 

472 

473 return R 

474 

475 

476@not_implemented_for("directed") 

477@not_implemented_for("multigraph") 

478@nx._dispatchable(graphs=_G_H, returns_graph=True) 

479def corona_product(G, H): 

480 r"""Returns the Corona product of G and H. 

481 

482 The corona product of $G$ and $H$ is the graph $C = G \circ H$ obtained by 

483 taking one copy of $G$, called the center graph, $|V(G)|$ copies of $H$, 

484 called the outer graph, and making the $i$-th vertex of $G$ adjacent to 

485 every vertex of the $i$-th copy of $H$, where $1 ≤ i ≤ |V(G)|$. 

486 

487 Parameters 

488 ---------- 

489 G, H: NetworkX graphs 

490 The graphs to take the carona product of. 

491 `G` is the center graph and `H` is the outer graph 

492 

493 Returns 

494 ------- 

495 C: NetworkX graph 

496 The Corona product of G and H. 

497 

498 Raises 

499 ------ 

500 NetworkXError 

501 If G and H are not both directed or both undirected. 

502 

503 Examples 

504 -------- 

505 >>> G = nx.cycle_graph(4) 

506 >>> H = nx.path_graph(2) 

507 >>> C = nx.corona_product(G, H) 

508 >>> list(C) 

509 [0, 1, 2, 3, (0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)] 

510 >>> print(C) 

511 Graph with 12 nodes and 16 edges 

512 

513 References 

514 ---------- 

515 [1] M. Tavakoli, F. Rahbarnia, and A. R. Ashrafi, 

516 "Studying the corona product of graphs under some graph invariants," 

517 Transactions on Combinatorics, vol. 3, no. 3, pp. 43–49, Sep. 2014, 

518 doi: 10.22108/toc.2014.5542. 

519 [2] A. Faraji, "Corona Product in Graph Theory," Ali Faraji, May 11, 2021. 

520 https://blog.alifaraji.ir/math/graph-theory/corona-product.html (accessed Dec. 07, 2021). 

521 """ 

522 GH = _init_product_graph(G, H) 

523 GH.add_nodes_from(G) 

524 GH.add_edges_from(G.edges) 

525 

526 for G_node in G: 

527 # copy nodes of H in GH, call it H_i 

528 GH.add_nodes_from((G_node, v) for v in H) 

529 

530 # copy edges of H_i based on H 

531 GH.add_edges_from( 

532 ((G_node, e0), (G_node, e1), d) for e0, e1, d in H.edges.data() 

533 ) 

534 

535 # creating new edges between H_i and a G's node 

536 GH.add_edges_from((G_node, (G_node, H_node)) for H_node in H) 

537 

538 return GH 

539 

540 

541@nx._dispatchable( 

542 graphs=_G_H, preserve_edge_attrs=True, preserve_node_attrs=True, returns_graph=True 

543) 

544def modular_product(G, H): 

545 r"""Returns the Modular product of G and H. 

546 

547 The modular product of `G` and `H` is the graph $M = G \nabla H$, 

548 consisting of the node set $V(M) = V(G) \times V(H)$ that is the Cartesian 

549 product of the node sets of `G` and `H`. Further, M contains an edge ((u, v), (x, y)): 

550 

551 - if u is adjacent to x in `G` and v is adjacent to y in `H`, or 

552 - if u is not adjacent to x in `G` and v is not adjacent to y in `H`. 

553 

554 More formally:: 

555 

556 E(M) = {((u, v), (x, y)) | ((u, x) in E(G) and (v, y) in E(H)) or 

557 ((u, x) not in E(G) and (v, y) not in E(H))} 

558 

559 Parameters 

560 ---------- 

561 G, H: NetworkX graphs 

562 The graphs to take the modular product of. 

563 

564 Returns 

565 ------- 

566 M: NetworkX graph 

567 The Modular product of `G` and `H`. 

568 

569 Raises 

570 ------ 

571 NetworkXNotImplemented 

572 If `G` is not a simple graph. 

573 

574 Examples 

575 -------- 

576 >>> G = nx.cycle_graph(4) 

577 >>> H = nx.path_graph(2) 

578 >>> M = nx.modular_product(G, H) 

579 >>> list(M) 

580 [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)] 

581 >>> print(M) 

582 Graph with 8 nodes and 8 edges 

583 

584 Notes 

585 ----- 

586 The *modular product* is defined in [1]_ and was first 

587 introduced as the *weak modular product*. 

588 

589 The modular product reduces the problem of counting isomorphic subgraphs 

590 in `G` and `H` to the problem of counting cliques in M. The subgraphs of 

591 `G` and `H` that are induced by the nodes of a clique in M are 

592 isomorphic [2]_ [3]_. 

593 

594 References 

595 ---------- 

596 .. [1] R. Hammack, W. Imrich, and S. Klavžar, 

597 "Handbook of Product Graphs", CRC Press, 2011. 

598 

599 .. [2] H. G. Barrow and R. M. Burstall, 

600 "Subgraph isomorphism, matching relational structures and maximal 

601 cliques", Information Processing Letters, vol. 4, issue 4, pp. 83-84, 

602 1976, https://doi.org/10.1016/0020-0190(76)90049-1. 

603 

604 .. [3] V. G. Vizing, "Reduction of the problem of isomorphism and isomorphic 

605 entrance to the task of finding the nondensity of a graph." Proc. Third 

606 All-Union Conference on Problems of Theoretical Cybernetics. 1974. 

607 """ 

608 if G.is_directed() or H.is_directed(): 

609 raise nx.NetworkXNotImplemented( 

610 "Modular product not implemented for directed graphs" 

611 ) 

612 if G.is_multigraph() or H.is_multigraph(): 

613 raise nx.NetworkXNotImplemented( 

614 "Modular product not implemented for multigraphs" 

615 ) 

616 

617 GH = _init_product_graph(G, H) 

618 GH.add_nodes_from(_node_product(G, H)) 

619 

620 for u, v, c in G.edges(data=True): 

621 for x, y, d in H.edges(data=True): 

622 GH.add_edge((u, x), (v, y), **_dict_product(c, d)) 

623 GH.add_edge((v, x), (u, y), **_dict_product(c, d)) 

624 

625 G = nx.complement(G) 

626 H = nx.complement(H) 

627 

628 for u, v, c in G.edges(data=True): 

629 for x, y, d in H.edges(data=True): 

630 GH.add_edge((u, x), (v, y), **_dict_product(c, d)) 

631 GH.add_edge((v, x), (u, y), **_dict_product(c, d)) 

632 

633 return GH