Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/clique.py: 15%

200 statements  

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

1"""Functions for finding and manipulating cliques. 

2 

3Finding the largest clique in a graph is NP-complete problem, so most of 

4these algorithms have an exponential running time; for more information, 

5see the Wikipedia article on the clique problem [1]_. 

6 

7.. [1] clique problem:: https://en.wikipedia.org/wiki/Clique_problem 

8 

9""" 

10from collections import defaultdict, deque 

11from itertools import chain, combinations, islice 

12 

13import networkx as nx 

14from networkx.utils import not_implemented_for 

15 

16__all__ = [ 

17 "find_cliques", 

18 "find_cliques_recursive", 

19 "make_max_clique_graph", 

20 "make_clique_bipartite", 

21 "node_clique_number", 

22 "number_of_cliques", 

23 "enumerate_all_cliques", 

24 "max_weight_clique", 

25] 

26 

27 

28@not_implemented_for("directed") 

29@nx._dispatch 

30def enumerate_all_cliques(G): 

31 """Returns all cliques in an undirected graph. 

32 

33 This function returns an iterator over cliques, each of which is a 

34 list of nodes. The iteration is ordered by cardinality of the 

35 cliques: first all cliques of size one, then all cliques of size 

36 two, etc. 

37 

38 Parameters 

39 ---------- 

40 G : NetworkX graph 

41 An undirected graph. 

42 

43 Returns 

44 ------- 

45 iterator 

46 An iterator over cliques, each of which is a list of nodes in 

47 `G`. The cliques are ordered according to size. 

48 

49 Notes 

50 ----- 

51 To obtain a list of all cliques, use 

52 `list(enumerate_all_cliques(G))`. However, be aware that in the 

53 worst-case, the length of this list can be exponential in the number 

54 of nodes in the graph (for example, when the graph is the complete 

55 graph). This function avoids storing all cliques in memory by only 

56 keeping current candidate node lists in memory during its search. 

57 

58 The implementation is adapted from the algorithm by Zhang, et 

59 al. (2005) [1]_ to output all cliques discovered. 

60 

61 This algorithm ignores self-loops and parallel edges, since cliques 

62 are not conventionally defined with such edges. 

63 

64 References 

65 ---------- 

66 .. [1] Yun Zhang, Abu-Khzam, F.N., Baldwin, N.E., Chesler, E.J., 

67 Langston, M.A., Samatova, N.F., 

68 "Genome-Scale Computational Approaches to Memory-Intensive 

69 Applications in Systems Biology". 

70 *Supercomputing*, 2005. Proceedings of the ACM/IEEE SC 2005 

71 Conference, pp. 12, 12--18 Nov. 2005. 

72 <https://doi.org/10.1109/SC.2005.29>. 

73 

74 """ 

75 index = {} 

76 nbrs = {} 

77 for u in G: 

78 index[u] = len(index) 

79 # Neighbors of u that appear after u in the iteration order of G. 

80 nbrs[u] = {v for v in G[u] if v not in index} 

81 

82 queue = deque(([u], sorted(nbrs[u], key=index.__getitem__)) for u in G) 

83 # Loop invariants: 

84 # 1. len(base) is nondecreasing. 

85 # 2. (base + cnbrs) is sorted with respect to the iteration order of G. 

86 # 3. cnbrs is a set of common neighbors of nodes in base. 

87 while queue: 

88 base, cnbrs = map(list, queue.popleft()) 

89 yield base 

90 for i, u in enumerate(cnbrs): 

91 # Use generators to reduce memory consumption. 

92 queue.append( 

93 ( 

94 chain(base, [u]), 

95 filter(nbrs[u].__contains__, islice(cnbrs, i + 1, None)), 

96 ) 

97 ) 

98 

99 

100@not_implemented_for("directed") 

101@nx._dispatch 

102def find_cliques(G, nodes=None): 

103 """Returns all maximal cliques in an undirected graph. 

104 

105 For each node *n*, a *maximal clique for n* is a largest complete 

106 subgraph containing *n*. The largest maximal clique is sometimes 

107 called the *maximum clique*. 

108 

109 This function returns an iterator over cliques, each of which is a 

110 list of nodes. It is an iterative implementation, so should not 

111 suffer from recursion depth issues. 

112 

113 This function accepts a list of `nodes` and only the maximal cliques 

114 containing all of these `nodes` are returned. It can considerably speed up 

115 the running time if some specific cliques are desired. 

116 

117 Parameters 

118 ---------- 

119 G : NetworkX graph 

120 An undirected graph. 

121 

122 nodes : list, optional (default=None) 

123 If provided, only yield *maximal cliques* containing all nodes in `nodes`. 

124 If `nodes` isn't a clique itself, a ValueError is raised. 

125 

126 Returns 

127 ------- 

128 iterator 

129 An iterator over maximal cliques, each of which is a list of 

130 nodes in `G`. If `nodes` is provided, only the maximal cliques 

131 containing all the nodes in `nodes` are returned. The order of 

132 cliques is arbitrary. 

133 

134 Raises 

135 ------ 

136 ValueError 

137 If `nodes` is not a clique. 

138 

139 Examples 

140 -------- 

141 >>> from pprint import pprint # For nice dict formatting 

142 >>> G = nx.karate_club_graph() 

143 >>> sum(1 for c in nx.find_cliques(G)) # The number of maximal cliques in G 

144 36 

145 >>> max(nx.find_cliques(G), key=len) # The largest maximal clique in G 

146 [0, 1, 2, 3, 13] 

147 

148 The size of the largest maximal clique is known as the *clique number* of 

149 the graph, which can be found directly with: 

150 

151 >>> max(len(c) for c in nx.find_cliques(G)) 

152 5 

153 

154 One can also compute the number of maximal cliques in `G` that contain a given 

155 node. The following produces a dictionary keyed by node whose 

156 values are the number of maximal cliques in `G` that contain the node: 

157 

158 >>> pprint({n: sum(1 for c in nx.find_cliques(G) if n in c) for n in G}) 

159 {0: 13, 

160 1: 6, 

161 2: 7, 

162 3: 3, 

163 4: 2, 

164 5: 3, 

165 6: 3, 

166 7: 1, 

167 8: 3, 

168 9: 2, 

169 10: 2, 

170 11: 1, 

171 12: 1, 

172 13: 2, 

173 14: 1, 

174 15: 1, 

175 16: 1, 

176 17: 1, 

177 18: 1, 

178 19: 2, 

179 20: 1, 

180 21: 1, 

181 22: 1, 

182 23: 3, 

183 24: 2, 

184 25: 2, 

185 26: 1, 

186 27: 3, 

187 28: 2, 

188 29: 2, 

189 30: 2, 

190 31: 4, 

191 32: 9, 

192 33: 14} 

193 

194 Or, similarly, the maximal cliques in `G` that contain a given node. 

195 For example, the 4 maximal cliques that contain node 31: 

196 

197 >>> [c for c in nx.find_cliques(G) if 31 in c] 

198 [[0, 31], [33, 32, 31], [33, 28, 31], [24, 25, 31]] 

199 

200 See Also 

201 -------- 

202 find_cliques_recursive 

203 A recursive version of the same algorithm. 

204 

205 Notes 

206 ----- 

207 To obtain a list of all maximal cliques, use 

208 `list(find_cliques(G))`. However, be aware that in the worst-case, 

209 the length of this list can be exponential in the number of nodes in 

210 the graph. This function avoids storing all cliques in memory by 

211 only keeping current candidate node lists in memory during its search. 

212 

213 This implementation is based on the algorithm published by Bron and 

214 Kerbosch (1973) [1]_, as adapted by Tomita, Tanaka and Takahashi 

215 (2006) [2]_ and discussed in Cazals and Karande (2008) [3]_. It 

216 essentially unrolls the recursion used in the references to avoid 

217 issues of recursion stack depth (for a recursive implementation, see 

218 :func:`find_cliques_recursive`). 

219 

220 This algorithm ignores self-loops and parallel edges, since cliques 

221 are not conventionally defined with such edges. 

222 

223 References 

224 ---------- 

225 .. [1] Bron, C. and Kerbosch, J. 

226 "Algorithm 457: finding all cliques of an undirected graph". 

227 *Communications of the ACM* 16, 9 (Sep. 1973), 575--577. 

228 <http://portal.acm.org/citation.cfm?doid=362342.362367> 

229 

230 .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi, 

231 "The worst-case time complexity for generating all maximal 

232 cliques and computational experiments", 

233 *Theoretical Computer Science*, Volume 363, Issue 1, 

234 Computing and Combinatorics, 

235 10th Annual International Conference on 

236 Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28--42 

237 <https://doi.org/10.1016/j.tcs.2006.06.015> 

238 

239 .. [3] F. Cazals, C. Karande, 

240 "A note on the problem of reporting maximal cliques", 

241 *Theoretical Computer Science*, 

242 Volume 407, Issues 1--3, 6 November 2008, Pages 564--568, 

243 <https://doi.org/10.1016/j.tcs.2008.05.010> 

244 

245 """ 

246 if len(G) == 0: 

247 return 

248 

249 adj = {u: {v for v in G[u] if v != u} for u in G} 

250 

251 # Initialize Q with the given nodes and subg, cand with their nbrs 

252 Q = nodes[:] if nodes is not None else [] 

253 cand = set(G) 

254 for node in Q: 

255 if node not in cand: 

256 raise ValueError(f"The given `nodes` {nodes} do not form a clique") 

257 cand &= adj[node] 

258 

259 if not cand: 

260 yield Q[:] 

261 return 

262 

263 subg = cand.copy() 

264 stack = [] 

265 Q.append(None) 

266 

267 u = max(subg, key=lambda u: len(cand & adj[u])) 

268 ext_u = cand - adj[u] 

269 

270 try: 

271 while True: 

272 if ext_u: 

273 q = ext_u.pop() 

274 cand.remove(q) 

275 Q[-1] = q 

276 adj_q = adj[q] 

277 subg_q = subg & adj_q 

278 if not subg_q: 

279 yield Q[:] 

280 else: 

281 cand_q = cand & adj_q 

282 if cand_q: 

283 stack.append((subg, cand, ext_u)) 

284 Q.append(None) 

285 subg = subg_q 

286 cand = cand_q 

287 u = max(subg, key=lambda u: len(cand & adj[u])) 

288 ext_u = cand - adj[u] 

289 else: 

290 Q.pop() 

291 subg, cand, ext_u = stack.pop() 

292 except IndexError: 

293 pass 

294 

295 

296# TODO Should this also be not implemented for directed graphs? 

297@nx._dispatch 

298def find_cliques_recursive(G, nodes=None): 

299 """Returns all maximal cliques in a graph. 

300 

301 For each node *v*, a *maximal clique for v* is a largest complete 

302 subgraph containing *v*. The largest maximal clique is sometimes 

303 called the *maximum clique*. 

304 

305 This function returns an iterator over cliques, each of which is a 

306 list of nodes. It is a recursive implementation, so may suffer from 

307 recursion depth issues, but is included for pedagogical reasons. 

308 For a non-recursive implementation, see :func:`find_cliques`. 

309 

310 This function accepts a list of `nodes` and only the maximal cliques 

311 containing all of these `nodes` are returned. It can considerably speed up 

312 the running time if some specific cliques are desired. 

313 

314 Parameters 

315 ---------- 

316 G : NetworkX graph 

317 

318 nodes : list, optional (default=None) 

319 If provided, only yield *maximal cliques* containing all nodes in `nodes`. 

320 If `nodes` isn't a clique itself, a ValueError is raised. 

321 

322 Returns 

323 ------- 

324 iterator 

325 An iterator over maximal cliques, each of which is a list of 

326 nodes in `G`. If `nodes` is provided, only the maximal cliques 

327 containing all the nodes in `nodes` are yielded. The order of 

328 cliques is arbitrary. 

329 

330 Raises 

331 ------ 

332 ValueError 

333 If `nodes` is not a clique. 

334 

335 See Also 

336 -------- 

337 find_cliques 

338 An iterative version of the same algorithm. See docstring for examples. 

339 

340 Notes 

341 ----- 

342 To obtain a list of all maximal cliques, use 

343 `list(find_cliques_recursive(G))`. However, be aware that in the 

344 worst-case, the length of this list can be exponential in the number 

345 of nodes in the graph. This function avoids storing all cliques in memory 

346 by only keeping current candidate node lists in memory during its search. 

347 

348 This implementation is based on the algorithm published by Bron and 

349 Kerbosch (1973) [1]_, as adapted by Tomita, Tanaka and Takahashi 

350 (2006) [2]_ and discussed in Cazals and Karande (2008) [3]_. For a 

351 non-recursive implementation, see :func:`find_cliques`. 

352 

353 This algorithm ignores self-loops and parallel edges, since cliques 

354 are not conventionally defined with such edges. 

355 

356 References 

357 ---------- 

358 .. [1] Bron, C. and Kerbosch, J. 

359 "Algorithm 457: finding all cliques of an undirected graph". 

360 *Communications of the ACM* 16, 9 (Sep. 1973), 575--577. 

361 <http://portal.acm.org/citation.cfm?doid=362342.362367> 

362 

363 .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi, 

364 "The worst-case time complexity for generating all maximal 

365 cliques and computational experiments", 

366 *Theoretical Computer Science*, Volume 363, Issue 1, 

367 Computing and Combinatorics, 

368 10th Annual International Conference on 

369 Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28--42 

370 <https://doi.org/10.1016/j.tcs.2006.06.015> 

371 

372 .. [3] F. Cazals, C. Karande, 

373 "A note on the problem of reporting maximal cliques", 

374 *Theoretical Computer Science*, 

375 Volume 407, Issues 1--3, 6 November 2008, Pages 564--568, 

376 <https://doi.org/10.1016/j.tcs.2008.05.010> 

377 

378 """ 

379 if len(G) == 0: 

380 return iter([]) 

381 

382 adj = {u: {v for v in G[u] if v != u} for u in G} 

383 

384 # Initialize Q with the given nodes and subg, cand with their nbrs 

385 Q = nodes[:] if nodes is not None else [] 

386 cand_init = set(G) 

387 for node in Q: 

388 if node not in cand_init: 

389 raise ValueError(f"The given `nodes` {nodes} do not form a clique") 

390 cand_init &= adj[node] 

391 

392 if not cand_init: 

393 return iter([Q]) 

394 

395 subg_init = cand_init.copy() 

396 

397 def expand(subg, cand): 

398 u = max(subg, key=lambda u: len(cand & adj[u])) 

399 for q in cand - adj[u]: 

400 cand.remove(q) 

401 Q.append(q) 

402 adj_q = adj[q] 

403 subg_q = subg & adj_q 

404 if not subg_q: 

405 yield Q[:] 

406 else: 

407 cand_q = cand & adj_q 

408 if cand_q: 

409 yield from expand(subg_q, cand_q) 

410 Q.pop() 

411 

412 return expand(subg_init, cand_init) 

413 

414 

415@nx._dispatch 

416def make_max_clique_graph(G, create_using=None): 

417 """Returns the maximal clique graph of the given graph. 

418 

419 The nodes of the maximal clique graph of `G` are the cliques of 

420 `G` and an edge joins two cliques if the cliques are not disjoint. 

421 

422 Parameters 

423 ---------- 

424 G : NetworkX graph 

425 

426 create_using : NetworkX graph constructor, optional (default=nx.Graph) 

427 Graph type to create. If graph instance, then cleared before populated. 

428 

429 Returns 

430 ------- 

431 NetworkX graph 

432 A graph whose nodes are the cliques of `G` and whose edges 

433 join two cliques if they are not disjoint. 

434 

435 Notes 

436 ----- 

437 This function behaves like the following code:: 

438 

439 import networkx as nx 

440 G = nx.make_clique_bipartite(G) 

441 cliques = [v for v in G.nodes() if G.nodes[v]['bipartite'] == 0] 

442 G = nx.bipartite.projected_graph(G, cliques) 

443 G = nx.relabel_nodes(G, {-v: v - 1 for v in G}) 

444 

445 It should be faster, though, since it skips all the intermediate 

446 steps. 

447 

448 """ 

449 if create_using is None: 

450 B = G.__class__() 

451 else: 

452 B = nx.empty_graph(0, create_using) 

453 cliques = list(enumerate(set(c) for c in find_cliques(G))) 

454 # Add a numbered node for each clique. 

455 B.add_nodes_from(i for i, c in cliques) 

456 # Join cliques by an edge if they share a node. 

457 clique_pairs = combinations(cliques, 2) 

458 B.add_edges_from((i, j) for (i, c1), (j, c2) in clique_pairs if c1 & c2) 

459 return B 

460 

461 

462@nx._dispatch 

463def make_clique_bipartite(G, fpos=None, create_using=None, name=None): 

464 """Returns the bipartite clique graph corresponding to `G`. 

465 

466 In the returned bipartite graph, the "bottom" nodes are the nodes of 

467 `G` and the "top" nodes represent the maximal cliques of `G`. 

468 There is an edge from node *v* to clique *C* in the returned graph 

469 if and only if *v* is an element of *C*. 

470 

471 Parameters 

472 ---------- 

473 G : NetworkX graph 

474 An undirected graph. 

475 

476 fpos : bool 

477 If True or not None, the returned graph will have an 

478 additional attribute, `pos`, a dictionary mapping node to 

479 position in the Euclidean plane. 

480 

481 create_using : NetworkX graph constructor, optional (default=nx.Graph) 

482 Graph type to create. If graph instance, then cleared before populated. 

483 

484 Returns 

485 ------- 

486 NetworkX graph 

487 A bipartite graph whose "bottom" set is the nodes of the graph 

488 `G`, whose "top" set is the cliques of `G`, and whose edges 

489 join nodes of `G` to the cliques that contain them. 

490 

491 The nodes of the graph `G` have the node attribute 

492 'bipartite' set to 1 and the nodes representing cliques 

493 have the node attribute 'bipartite' set to 0, as is the 

494 convention for bipartite graphs in NetworkX. 

495 

496 """ 

497 B = nx.empty_graph(0, create_using) 

498 B.clear() 

499 # The "bottom" nodes in the bipartite graph are the nodes of the 

500 # original graph, G. 

501 B.add_nodes_from(G, bipartite=1) 

502 for i, cl in enumerate(find_cliques(G)): 

503 # The "top" nodes in the bipartite graph are the cliques. These 

504 # nodes get negative numbers as labels. 

505 name = -i - 1 

506 B.add_node(name, bipartite=0) 

507 B.add_edges_from((v, name) for v in cl) 

508 return B 

509 

510 

511@nx._dispatch 

512def node_clique_number(G, nodes=None, cliques=None, separate_nodes=False): 

513 """Returns the size of the largest maximal clique containing each given node. 

514 

515 Returns a single or list depending on input nodes. 

516 An optional list of cliques can be input if already computed. 

517 

518 Parameters 

519 ---------- 

520 G : NetworkX graph 

521 An undirected graph. 

522 

523 cliques : list, optional (default=None) 

524 A list of cliques, each of which is itself a list of nodes. 

525 If not specified, the list of all cliques will be computed 

526 using :func:`find_cliques`. 

527 

528 Returns 

529 ------- 

530 int or dict 

531 If `nodes` is a single node, returns the size of the 

532 largest maximal clique in `G` containing that node. 

533 Otherwise return a dict keyed by node to the size 

534 of the largest maximal clique containing that node. 

535 

536 See Also 

537 -------- 

538 find_cliques 

539 find_cliques yields the maximal cliques of G. 

540 It accepts a `nodes` argument which restricts consideration to 

541 maximal cliques containing all the given `nodes`. 

542 The search for the cliques is optimized for `nodes`. 

543 """ 

544 if cliques is None: 

545 if nodes is not None: 

546 # Use ego_graph to decrease size of graph 

547 # check for single node 

548 if nodes in G: 

549 return max(len(c) for c in find_cliques(nx.ego_graph(G, nodes))) 

550 # handle multiple nodes 

551 return { 

552 n: max(len(c) for c in find_cliques(nx.ego_graph(G, n))) for n in nodes 

553 } 

554 

555 # nodes is None--find all cliques 

556 cliques = list(find_cliques(G)) 

557 

558 # single node requested 

559 if nodes in G: 

560 return max(len(c) for c in cliques if nodes in c) 

561 

562 # multiple nodes requested 

563 # preprocess all nodes (faster than one at a time for even 2 nodes) 

564 size_for_n = defaultdict(int) 

565 for c in cliques: 

566 size_of_c = len(c) 

567 for n in c: 

568 if size_for_n[n] < size_of_c: 

569 size_for_n[n] = size_of_c 

570 if nodes is None: 

571 return size_for_n 

572 return {n: size_for_n[n] for n in nodes} 

573 

574 

575def number_of_cliques(G, nodes=None, cliques=None): 

576 """Returns the number of maximal cliques for each node. 

577 

578 Returns a single or list depending on input nodes. 

579 Optional list of cliques can be input if already computed. 

580 """ 

581 if cliques is None: 

582 cliques = list(find_cliques(G)) 

583 

584 if nodes is None: 

585 nodes = list(G.nodes()) # none, get entire graph 

586 

587 if not isinstance(nodes, list): # check for a list 

588 v = nodes 

589 # assume it is a single value 

590 numcliq = len([1 for c in cliques if v in c]) 

591 else: 

592 numcliq = {} 

593 for v in nodes: 

594 numcliq[v] = len([1 for c in cliques if v in c]) 

595 return numcliq 

596 

597 

598class MaxWeightClique: 

599 """A class for the maximum weight clique algorithm. 

600 

601 This class is a helper for the `max_weight_clique` function. The class 

602 should not normally be used directly. 

603 

604 Parameters 

605 ---------- 

606 G : NetworkX graph 

607 The undirected graph for which a maximum weight clique is sought 

608 weight : string or None, optional (default='weight') 

609 The node attribute that holds the integer value used as a weight. 

610 If None, then each node has weight 1. 

611 

612 Attributes 

613 ---------- 

614 G : NetworkX graph 

615 The undirected graph for which a maximum weight clique is sought 

616 node_weights: dict 

617 The weight of each node 

618 incumbent_nodes : list 

619 The nodes of the incumbent clique (the best clique found so far) 

620 incumbent_weight: int 

621 The weight of the incumbent clique 

622 """ 

623 

624 def __init__(self, G, weight): 

625 self.G = G 

626 self.incumbent_nodes = [] 

627 self.incumbent_weight = 0 

628 

629 if weight is None: 

630 self.node_weights = {v: 1 for v in G.nodes()} 

631 else: 

632 for v in G.nodes(): 

633 if weight not in G.nodes[v]: 

634 errmsg = f"Node {v!r} does not have the requested weight field." 

635 raise KeyError(errmsg) 

636 if not isinstance(G.nodes[v][weight], int): 

637 errmsg = f"The {weight!r} field of node {v!r} is not an integer." 

638 raise ValueError(errmsg) 

639 self.node_weights = {v: G.nodes[v][weight] for v in G.nodes()} 

640 

641 def update_incumbent_if_improved(self, C, C_weight): 

642 """Update the incumbent if the node set C has greater weight. 

643 

644 C is assumed to be a clique. 

645 """ 

646 if C_weight > self.incumbent_weight: 

647 self.incumbent_nodes = C[:] 

648 self.incumbent_weight = C_weight 

649 

650 def greedily_find_independent_set(self, P): 

651 """Greedily find an independent set of nodes from a set of 

652 nodes P.""" 

653 independent_set = [] 

654 P = P[:] 

655 while P: 

656 v = P[0] 

657 independent_set.append(v) 

658 P = [w for w in P if v != w and not self.G.has_edge(v, w)] 

659 return independent_set 

660 

661 def find_branching_nodes(self, P, target): 

662 """Find a set of nodes to branch on.""" 

663 residual_wt = {v: self.node_weights[v] for v in P} 

664 total_wt = 0 

665 P = P[:] 

666 while P: 

667 independent_set = self.greedily_find_independent_set(P) 

668 min_wt_in_class = min(residual_wt[v] for v in independent_set) 

669 total_wt += min_wt_in_class 

670 if total_wt > target: 

671 break 

672 for v in independent_set: 

673 residual_wt[v] -= min_wt_in_class 

674 P = [v for v in P if residual_wt[v] != 0] 

675 return P 

676 

677 def expand(self, C, C_weight, P): 

678 """Look for the best clique that contains all the nodes in C and zero or 

679 more of the nodes in P, backtracking if it can be shown that no such 

680 clique has greater weight than the incumbent. 

681 """ 

682 self.update_incumbent_if_improved(C, C_weight) 

683 branching_nodes = self.find_branching_nodes(P, self.incumbent_weight - C_weight) 

684 while branching_nodes: 

685 v = branching_nodes.pop() 

686 P.remove(v) 

687 new_C = C + [v] 

688 new_C_weight = C_weight + self.node_weights[v] 

689 new_P = [w for w in P if self.G.has_edge(v, w)] 

690 self.expand(new_C, new_C_weight, new_P) 

691 

692 def find_max_weight_clique(self): 

693 """Find a maximum weight clique.""" 

694 # Sort nodes in reverse order of degree for speed 

695 nodes = sorted(self.G.nodes(), key=lambda v: self.G.degree(v), reverse=True) 

696 nodes = [v for v in nodes if self.node_weights[v] > 0] 

697 self.expand([], 0, nodes) 

698 

699 

700@not_implemented_for("directed") 

701@nx._dispatch(node_attrs="weight") 

702def max_weight_clique(G, weight="weight"): 

703 """Find a maximum weight clique in G. 

704 

705 A *clique* in a graph is a set of nodes such that every two distinct nodes 

706 are adjacent. The *weight* of a clique is the sum of the weights of its 

707 nodes. A *maximum weight clique* of graph G is a clique C in G such that 

708 no clique in G has weight greater than the weight of C. 

709 

710 Parameters 

711 ---------- 

712 G : NetworkX graph 

713 Undirected graph 

714 weight : string or None, optional (default='weight') 

715 The node attribute that holds the integer value used as a weight. 

716 If None, then each node has weight 1. 

717 

718 Returns 

719 ------- 

720 clique : list 

721 the nodes of a maximum weight clique 

722 weight : int 

723 the weight of a maximum weight clique 

724 

725 Notes 

726 ----- 

727 The implementation is recursive, and therefore it may run into recursion 

728 depth issues if G contains a clique whose number of nodes is close to the 

729 recursion depth limit. 

730 

731 At each search node, the algorithm greedily constructs a weighted 

732 independent set cover of part of the graph in order to find a small set of 

733 nodes on which to branch. The algorithm is very similar to the algorithm 

734 of Tavares et al. [1]_, other than the fact that the NetworkX version does 

735 not use bitsets. This style of algorithm for maximum weight clique (and 

736 maximum weight independent set, which is the same problem but on the 

737 complement graph) has a decades-long history. See Algorithm B of Warren 

738 and Hicks [2]_ and the references in that paper. 

739 

740 References 

741 ---------- 

742 .. [1] Tavares, W.A., Neto, M.B.C., Rodrigues, C.D., Michelon, P.: Um 

743 algoritmo de branch and bound para o problema da clique máxima 

744 ponderada. Proceedings of XLVII SBPO 1 (2015). 

745 

746 .. [2] Warren, Jeffrey S, Hicks, Illya V.: Combinatorial Branch-and-Bound 

747 for the Maximum Weight Independent Set Problem. Technical Report, 

748 Texas A&M University (2016). 

749 """ 

750 

751 mwc = MaxWeightClique(G, weight) 

752 mwc.find_max_weight_clique() 

753 return mwc.incumbent_nodes, mwc.incumbent_weight