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

511 statements  

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

1"""Functions for computing and verifying matchings in a graph.""" 

2from collections import Counter 

3from itertools import combinations, repeat 

4 

5import networkx as nx 

6from networkx.utils import not_implemented_for 

7 

8__all__ = [ 

9 "is_matching", 

10 "is_maximal_matching", 

11 "is_perfect_matching", 

12 "max_weight_matching", 

13 "min_weight_matching", 

14 "maximal_matching", 

15] 

16 

17 

18@not_implemented_for("multigraph") 

19@not_implemented_for("directed") 

20@nx._dispatch 

21def maximal_matching(G): 

22 r"""Find a maximal matching in the graph. 

23 

24 A matching is a subset of edges in which no node occurs more than once. 

25 A maximal matching cannot add more edges and still be a matching. 

26 

27 Parameters 

28 ---------- 

29 G : NetworkX graph 

30 Undirected graph 

31 

32 Returns 

33 ------- 

34 matching : set 

35 A maximal matching of the graph. 

36 

37 Examples 

38 -------- 

39 >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)]) 

40 >>> sorted(nx.maximal_matching(G)) 

41 [(1, 2), (3, 5)] 

42 

43 Notes 

44 ----- 

45 The algorithm greedily selects a maximal matching M of the graph G 

46 (i.e. no superset of M exists). It runs in $O(|E|)$ time. 

47 """ 

48 matching = set() 

49 nodes = set() 

50 for edge in G.edges(): 

51 # If the edge isn't covered, add it to the matching 

52 # then remove neighborhood of u and v from consideration. 

53 u, v = edge 

54 if u not in nodes and v not in nodes and u != v: 

55 matching.add(edge) 

56 nodes.update(edge) 

57 return matching 

58 

59 

60def matching_dict_to_set(matching): 

61 """Converts matching dict format to matching set format 

62 

63 Converts a dictionary representing a matching (as returned by 

64 :func:`max_weight_matching`) to a set representing a matching (as 

65 returned by :func:`maximal_matching`). 

66 

67 In the definition of maximal matching adopted by NetworkX, 

68 self-loops are not allowed, so the provided dictionary is expected 

69 to never have any mapping from a key to itself. However, the 

70 dictionary is expected to have mirrored key/value pairs, for 

71 example, key ``u`` with value ``v`` and key ``v`` with value ``u``. 

72 

73 """ 

74 edges = set() 

75 for edge in matching.items(): 

76 u, v = edge 

77 if (v, u) in edges or edge in edges: 

78 continue 

79 if u == v: 

80 raise nx.NetworkXError(f"Selfloops cannot appear in matchings {edge}") 

81 edges.add(edge) 

82 return edges 

83 

84 

85@nx._dispatch 

86def is_matching(G, matching): 

87 """Return True if ``matching`` is a valid matching of ``G`` 

88 

89 A *matching* in a graph is a set of edges in which no two distinct 

90 edges share a common endpoint. Each node is incident to at most one 

91 edge in the matching. The edges are said to be independent. 

92 

93 Parameters 

94 ---------- 

95 G : NetworkX graph 

96 

97 matching : dict or set 

98 A dictionary or set representing a matching. If a dictionary, it 

99 must have ``matching[u] == v`` and ``matching[v] == u`` for each 

100 edge ``(u, v)`` in the matching. If a set, it must have elements 

101 of the form ``(u, v)``, where ``(u, v)`` is an edge in the 

102 matching. 

103 

104 Returns 

105 ------- 

106 bool 

107 Whether the given set or dictionary represents a valid matching 

108 in the graph. 

109 

110 Raises 

111 ------ 

112 NetworkXError 

113 If the proposed matching has an edge to a node not in G. 

114 Or if the matching is not a collection of 2-tuple edges. 

115 

116 Examples 

117 -------- 

118 >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)]) 

119 >>> nx.is_maximal_matching(G, {1: 3, 2: 4}) # using dict to represent matching 

120 True 

121 

122 >>> nx.is_matching(G, {(1, 3), (2, 4)}) # using set to represent matching 

123 True 

124 

125 """ 

126 if isinstance(matching, dict): 

127 matching = matching_dict_to_set(matching) 

128 

129 nodes = set() 

130 for edge in matching: 

131 if len(edge) != 2: 

132 raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}") 

133 u, v = edge 

134 if u not in G or v not in G: 

135 raise nx.NetworkXError(f"matching contains edge {edge} with node not in G") 

136 if u == v: 

137 return False 

138 if not G.has_edge(u, v): 

139 return False 

140 if u in nodes or v in nodes: 

141 return False 

142 nodes.update(edge) 

143 return True 

144 

145 

146@nx._dispatch 

147def is_maximal_matching(G, matching): 

148 """Return True if ``matching`` is a maximal matching of ``G`` 

149 

150 A *maximal matching* in a graph is a matching in which adding any 

151 edge would cause the set to no longer be a valid matching. 

152 

153 Parameters 

154 ---------- 

155 G : NetworkX graph 

156 

157 matching : dict or set 

158 A dictionary or set representing a matching. If a dictionary, it 

159 must have ``matching[u] == v`` and ``matching[v] == u`` for each 

160 edge ``(u, v)`` in the matching. If a set, it must have elements 

161 of the form ``(u, v)``, where ``(u, v)`` is an edge in the 

162 matching. 

163 

164 Returns 

165 ------- 

166 bool 

167 Whether the given set or dictionary represents a valid maximal 

168 matching in the graph. 

169 

170 Examples 

171 -------- 

172 >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (3, 5)]) 

173 >>> nx.is_maximal_matching(G, {(1, 2), (3, 4)}) 

174 True 

175 

176 """ 

177 if isinstance(matching, dict): 

178 matching = matching_dict_to_set(matching) 

179 # If the given set is not a matching, then it is not a maximal matching. 

180 edges = set() 

181 nodes = set() 

182 for edge in matching: 

183 if len(edge) != 2: 

184 raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}") 

185 u, v = edge 

186 if u not in G or v not in G: 

187 raise nx.NetworkXError(f"matching contains edge {edge} with node not in G") 

188 if u == v: 

189 return False 

190 if not G.has_edge(u, v): 

191 return False 

192 if u in nodes or v in nodes: 

193 return False 

194 nodes.update(edge) 

195 edges.add(edge) 

196 edges.add((v, u)) 

197 # A matching is maximal if adding any new edge from G to it 

198 # causes the resulting set to match some node twice. 

199 # Be careful to check for adding selfloops 

200 for u, v in G.edges: 

201 if (u, v) not in edges: 

202 # could add edge (u, v) to edges and have a bigger matching 

203 if u not in nodes and v not in nodes and u != v: 

204 return False 

205 return True 

206 

207 

208@nx._dispatch 

209def is_perfect_matching(G, matching): 

210 """Return True if ``matching`` is a perfect matching for ``G`` 

211 

212 A *perfect matching* in a graph is a matching in which exactly one edge 

213 is incident upon each vertex. 

214 

215 Parameters 

216 ---------- 

217 G : NetworkX graph 

218 

219 matching : dict or set 

220 A dictionary or set representing a matching. If a dictionary, it 

221 must have ``matching[u] == v`` and ``matching[v] == u`` for each 

222 edge ``(u, v)`` in the matching. If a set, it must have elements 

223 of the form ``(u, v)``, where ``(u, v)`` is an edge in the 

224 matching. 

225 

226 Returns 

227 ------- 

228 bool 

229 Whether the given set or dictionary represents a valid perfect 

230 matching in the graph. 

231 

232 Examples 

233 -------- 

234 >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5), (4, 6)]) 

235 >>> my_match = {1: 2, 3: 5, 4: 6} 

236 >>> nx.is_perfect_matching(G, my_match) 

237 True 

238 

239 """ 

240 if isinstance(matching, dict): 

241 matching = matching_dict_to_set(matching) 

242 

243 nodes = set() 

244 for edge in matching: 

245 if len(edge) != 2: 

246 raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}") 

247 u, v = edge 

248 if u not in G or v not in G: 

249 raise nx.NetworkXError(f"matching contains edge {edge} with node not in G") 

250 if u == v: 

251 return False 

252 if not G.has_edge(u, v): 

253 return False 

254 if u in nodes or v in nodes: 

255 return False 

256 nodes.update(edge) 

257 return len(nodes) == len(G) 

258 

259 

260@not_implemented_for("multigraph") 

261@not_implemented_for("directed") 

262@nx._dispatch(edge_attrs="weight") 

263def min_weight_matching(G, weight="weight"): 

264 """Computing a minimum-weight maximal matching of G. 

265 

266 Use the maximum-weight algorithm with edge weights subtracted 

267 from the maximum weight of all edges. 

268 

269 A matching is a subset of edges in which no node occurs more than once. 

270 The weight of a matching is the sum of the weights of its edges. 

271 A maximal matching cannot add more edges and still be a matching. 

272 The cardinality of a matching is the number of matched edges. 

273 

274 This method replaces the edge weights with 1 plus the maximum edge weight 

275 minus the original edge weight. 

276 

277 new_weight = (max_weight + 1) - edge_weight 

278 

279 then runs :func:`max_weight_matching` with the new weights. 

280 The max weight matching with these new weights corresponds 

281 to the min weight matching using the original weights. 

282 Adding 1 to the max edge weight keeps all edge weights positive 

283 and as integers if they started as integers. 

284 

285 You might worry that adding 1 to each weight would make the algorithm 

286 favor matchings with more edges. But we use the parameter 

287 `maxcardinality=True` in `max_weight_matching` to ensure that the 

288 number of edges in the competing matchings are the same and thus 

289 the optimum does not change due to changes in the number of edges. 

290 

291 Read the documentation of `max_weight_matching` for more information. 

292 

293 Parameters 

294 ---------- 

295 G : NetworkX graph 

296 Undirected graph 

297 

298 weight: string, optional (default='weight') 

299 Edge data key corresponding to the edge weight. 

300 If key not found, uses 1 as weight. 

301 

302 Returns 

303 ------- 

304 matching : set 

305 A minimal weight matching of the graph. 

306 

307 See Also 

308 -------- 

309 max_weight_matching 

310 """ 

311 if len(G.edges) == 0: 

312 return max_weight_matching(G, maxcardinality=True, weight=weight) 

313 G_edges = G.edges(data=weight, default=1) 

314 max_weight = 1 + max(w for _, _, w in G_edges) 

315 InvG = nx.Graph() 

316 edges = ((u, v, max_weight - w) for u, v, w in G_edges) 

317 InvG.add_weighted_edges_from(edges, weight=weight) 

318 return max_weight_matching(InvG, maxcardinality=True, weight=weight) 

319 

320 

321@not_implemented_for("multigraph") 

322@not_implemented_for("directed") 

323@nx._dispatch(edge_attrs="weight") 

324def max_weight_matching(G, maxcardinality=False, weight="weight"): 

325 """Compute a maximum-weighted matching of G. 

326 

327 A matching is a subset of edges in which no node occurs more than once. 

328 The weight of a matching is the sum of the weights of its edges. 

329 A maximal matching cannot add more edges and still be a matching. 

330 The cardinality of a matching is the number of matched edges. 

331 

332 Parameters 

333 ---------- 

334 G : NetworkX graph 

335 Undirected graph 

336 

337 maxcardinality: bool, optional (default=False) 

338 If maxcardinality is True, compute the maximum-cardinality matching 

339 with maximum weight among all maximum-cardinality matchings. 

340 

341 weight: string, optional (default='weight') 

342 Edge data key corresponding to the edge weight. 

343 If key not found, uses 1 as weight. 

344 

345 

346 Returns 

347 ------- 

348 matching : set 

349 A maximal matching of the graph. 

350 

351 Examples 

352 -------- 

353 >>> G = nx.Graph() 

354 >>> edges = [(1, 2, 6), (1, 3, 2), (2, 3, 1), (2, 4, 7), (3, 5, 9), (4, 5, 3)] 

355 >>> G.add_weighted_edges_from(edges) 

356 >>> sorted(nx.max_weight_matching(G)) 

357 [(2, 4), (5, 3)] 

358 

359 Notes 

360 ----- 

361 If G has edges with weight attributes the edge data are used as 

362 weight values else the weights are assumed to be 1. 

363 

364 This function takes time O(number_of_nodes ** 3). 

365 

366 If all edge weights are integers, the algorithm uses only integer 

367 computations. If floating point weights are used, the algorithm 

368 could return a slightly suboptimal matching due to numeric 

369 precision errors. 

370 

371 This method is based on the "blossom" method for finding augmenting 

372 paths and the "primal-dual" method for finding a matching of maximum 

373 weight, both methods invented by Jack Edmonds [1]_. 

374 

375 Bipartite graphs can also be matched using the functions present in 

376 :mod:`networkx.algorithms.bipartite.matching`. 

377 

378 References 

379 ---------- 

380 .. [1] "Efficient Algorithms for Finding Maximum Matching in Graphs", 

381 Zvi Galil, ACM Computing Surveys, 1986. 

382 """ 

383 # 

384 # The algorithm is taken from "Efficient Algorithms for Finding Maximum 

385 # Matching in Graphs" by Zvi Galil, ACM Computing Surveys, 1986. 

386 # It is based on the "blossom" method for finding augmenting paths and 

387 # the "primal-dual" method for finding a matching of maximum weight, both 

388 # methods invented by Jack Edmonds. 

389 # 

390 # A C program for maximum weight matching by Ed Rothberg was used 

391 # extensively to validate this new code. 

392 # 

393 # Many terms used in the code comments are explained in the paper 

394 # by Galil. You will probably need the paper to make sense of this code. 

395 # 

396 

397 class NoNode: 

398 """Dummy value which is different from any node.""" 

399 

400 class Blossom: 

401 """Representation of a non-trivial blossom or sub-blossom.""" 

402 

403 __slots__ = ["childs", "edges", "mybestedges"] 

404 

405 # b.childs is an ordered list of b's sub-blossoms, starting with 

406 # the base and going round the blossom. 

407 

408 # b.edges is the list of b's connecting edges, such that 

409 # b.edges[i] = (v, w) where v is a vertex in b.childs[i] 

410 # and w is a vertex in b.childs[wrap(i+1)]. 

411 

412 # If b is a top-level S-blossom, 

413 # b.mybestedges is a list of least-slack edges to neighbouring 

414 # S-blossoms, or None if no such list has been computed yet. 

415 # This is used for efficient computation of delta3. 

416 

417 # Generate the blossom's leaf vertices. 

418 def leaves(self): 

419 stack = [*self.childs] 

420 while stack: 

421 t = stack.pop() 

422 if isinstance(t, Blossom): 

423 stack.extend(t.childs) 

424 else: 

425 yield t 

426 

427 # Get a list of vertices. 

428 gnodes = list(G) 

429 if not gnodes: 

430 return set() # don't bother with empty graphs 

431 

432 # Find the maximum edge weight. 

433 maxweight = 0 

434 allinteger = True 

435 for i, j, d in G.edges(data=True): 

436 wt = d.get(weight, 1) 

437 if i != j and wt > maxweight: 

438 maxweight = wt 

439 allinteger = allinteger and (str(type(wt)).split("'")[1] in ("int", "long")) 

440 

441 # If v is a matched vertex, mate[v] is its partner vertex. 

442 # If v is a single vertex, v does not occur as a key in mate. 

443 # Initially all vertices are single; updated during augmentation. 

444 mate = {} 

445 

446 # If b is a top-level blossom, 

447 # label.get(b) is None if b is unlabeled (free), 

448 # 1 if b is an S-blossom, 

449 # 2 if b is a T-blossom. 

450 # The label of a vertex is found by looking at the label of its top-level 

451 # containing blossom. 

452 # If v is a vertex inside a T-blossom, label[v] is 2 iff v is reachable 

453 # from an S-vertex outside the blossom. 

454 # Labels are assigned during a stage and reset after each augmentation. 

455 label = {} 

456 

457 # If b is a labeled top-level blossom, 

458 # labeledge[b] = (v, w) is the edge through which b obtained its label 

459 # such that w is a vertex in b, or None if b's base vertex is single. 

460 # If w is a vertex inside a T-blossom and label[w] == 2, 

461 # labeledge[w] = (v, w) is an edge through which w is reachable from 

462 # outside the blossom. 

463 labeledge = {} 

464 

465 # If v is a vertex, inblossom[v] is the top-level blossom to which v 

466 # belongs. 

467 # If v is a top-level vertex, inblossom[v] == v since v is itself 

468 # a (trivial) top-level blossom. 

469 # Initially all vertices are top-level trivial blossoms. 

470 inblossom = dict(zip(gnodes, gnodes)) 

471 

472 # If b is a sub-blossom, 

473 # blossomparent[b] is its immediate parent (sub-)blossom. 

474 # If b is a top-level blossom, blossomparent[b] is None. 

475 blossomparent = dict(zip(gnodes, repeat(None))) 

476 

477 # If b is a (sub-)blossom, 

478 # blossombase[b] is its base VERTEX (i.e. recursive sub-blossom). 

479 blossombase = dict(zip(gnodes, gnodes)) 

480 

481 # If w is a free vertex (or an unreached vertex inside a T-blossom), 

482 # bestedge[w] = (v, w) is the least-slack edge from an S-vertex, 

483 # or None if there is no such edge. 

484 # If b is a (possibly trivial) top-level S-blossom, 

485 # bestedge[b] = (v, w) is the least-slack edge to a different S-blossom 

486 # (v inside b), or None if there is no such edge. 

487 # This is used for efficient computation of delta2 and delta3. 

488 bestedge = {} 

489 

490 # If v is a vertex, 

491 # dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual 

492 # optimization problem (if all edge weights are integers, multiplication 

493 # by two ensures that all values remain integers throughout the algorithm). 

494 # Initially, u(v) = maxweight / 2. 

495 dualvar = dict(zip(gnodes, repeat(maxweight))) 

496 

497 # If b is a non-trivial blossom, 

498 # blossomdual[b] = z(b) where z(b) is b's variable in the dual 

499 # optimization problem. 

500 blossomdual = {} 

501 

502 # If (v, w) in allowedge or (w, v) in allowedg, then the edge 

503 # (v, w) is known to have zero slack in the optimization problem; 

504 # otherwise the edge may or may not have zero slack. 

505 allowedge = {} 

506 

507 # Queue of newly discovered S-vertices. 

508 queue = [] 

509 

510 # Return 2 * slack of edge (v, w) (does not work inside blossoms). 

511 def slack(v, w): 

512 return dualvar[v] + dualvar[w] - 2 * G[v][w].get(weight, 1) 

513 

514 # Assign label t to the top-level blossom containing vertex w, 

515 # coming through an edge from vertex v. 

516 def assignLabel(w, t, v): 

517 b = inblossom[w] 

518 assert label.get(w) is None and label.get(b) is None 

519 label[w] = label[b] = t 

520 if v is not None: 

521 labeledge[w] = labeledge[b] = (v, w) 

522 else: 

523 labeledge[w] = labeledge[b] = None 

524 bestedge[w] = bestedge[b] = None 

525 if t == 1: 

526 # b became an S-vertex/blossom; add it(s vertices) to the queue. 

527 if isinstance(b, Blossom): 

528 queue.extend(b.leaves()) 

529 else: 

530 queue.append(b) 

531 elif t == 2: 

532 # b became a T-vertex/blossom; assign label S to its mate. 

533 # (If b is a non-trivial blossom, its base is the only vertex 

534 # with an external mate.) 

535 base = blossombase[b] 

536 assignLabel(mate[base], 1, base) 

537 

538 # Trace back from vertices v and w to discover either a new blossom 

539 # or an augmenting path. Return the base vertex of the new blossom, 

540 # or NoNode if an augmenting path was found. 

541 def scanBlossom(v, w): 

542 # Trace back from v and w, placing breadcrumbs as we go. 

543 path = [] 

544 base = NoNode 

545 while v is not NoNode: 

546 # Look for a breadcrumb in v's blossom or put a new breadcrumb. 

547 b = inblossom[v] 

548 if label[b] & 4: 

549 base = blossombase[b] 

550 break 

551 assert label[b] == 1 

552 path.append(b) 

553 label[b] = 5 

554 # Trace one step back. 

555 if labeledge[b] is None: 

556 # The base of blossom b is single; stop tracing this path. 

557 assert blossombase[b] not in mate 

558 v = NoNode 

559 else: 

560 assert labeledge[b][0] == mate[blossombase[b]] 

561 v = labeledge[b][0] 

562 b = inblossom[v] 

563 assert label[b] == 2 

564 # b is a T-blossom; trace one more step back. 

565 v = labeledge[b][0] 

566 # Swap v and w so that we alternate between both paths. 

567 if w is not NoNode: 

568 v, w = w, v 

569 # Remove breadcrumbs. 

570 for b in path: 

571 label[b] = 1 

572 # Return base vertex, if we found one. 

573 return base 

574 

575 # Construct a new blossom with given base, through S-vertices v and w. 

576 # Label the new blossom as S; set its dual variable to zero; 

577 # relabel its T-vertices to S and add them to the queue. 

578 def addBlossom(base, v, w): 

579 bb = inblossom[base] 

580 bv = inblossom[v] 

581 bw = inblossom[w] 

582 # Create blossom. 

583 b = Blossom() 

584 blossombase[b] = base 

585 blossomparent[b] = None 

586 blossomparent[bb] = b 

587 # Make list of sub-blossoms and their interconnecting edge endpoints. 

588 b.childs = path = [] 

589 b.edges = edgs = [(v, w)] 

590 # Trace back from v to base. 

591 while bv != bb: 

592 # Add bv to the new blossom. 

593 blossomparent[bv] = b 

594 path.append(bv) 

595 edgs.append(labeledge[bv]) 

596 assert label[bv] == 2 or ( 

597 label[bv] == 1 and labeledge[bv][0] == mate[blossombase[bv]] 

598 ) 

599 # Trace one step back. 

600 v = labeledge[bv][0] 

601 bv = inblossom[v] 

602 # Add base sub-blossom; reverse lists. 

603 path.append(bb) 

604 path.reverse() 

605 edgs.reverse() 

606 # Trace back from w to base. 

607 while bw != bb: 

608 # Add bw to the new blossom. 

609 blossomparent[bw] = b 

610 path.append(bw) 

611 edgs.append((labeledge[bw][1], labeledge[bw][0])) 

612 assert label[bw] == 2 or ( 

613 label[bw] == 1 and labeledge[bw][0] == mate[blossombase[bw]] 

614 ) 

615 # Trace one step back. 

616 w = labeledge[bw][0] 

617 bw = inblossom[w] 

618 # Set label to S. 

619 assert label[bb] == 1 

620 label[b] = 1 

621 labeledge[b] = labeledge[bb] 

622 # Set dual variable to zero. 

623 blossomdual[b] = 0 

624 # Relabel vertices. 

625 for v in b.leaves(): 

626 if label[inblossom[v]] == 2: 

627 # This T-vertex now turns into an S-vertex because it becomes 

628 # part of an S-blossom; add it to the queue. 

629 queue.append(v) 

630 inblossom[v] = b 

631 # Compute b.mybestedges. 

632 bestedgeto = {} 

633 for bv in path: 

634 if isinstance(bv, Blossom): 

635 if bv.mybestedges is not None: 

636 # Walk this subblossom's least-slack edges. 

637 nblist = bv.mybestedges 

638 # The sub-blossom won't need this data again. 

639 bv.mybestedges = None 

640 else: 

641 # This subblossom does not have a list of least-slack 

642 # edges; get the information from the vertices. 

643 nblist = [ 

644 (v, w) for v in bv.leaves() for w in G.neighbors(v) if v != w 

645 ] 

646 else: 

647 nblist = [(bv, w) for w in G.neighbors(bv) if bv != w] 

648 for k in nblist: 

649 (i, j) = k 

650 if inblossom[j] == b: 

651 i, j = j, i 

652 bj = inblossom[j] 

653 if ( 

654 bj != b 

655 and label.get(bj) == 1 

656 and ((bj not in bestedgeto) or slack(i, j) < slack(*bestedgeto[bj])) 

657 ): 

658 bestedgeto[bj] = k 

659 # Forget about least-slack edge of the subblossom. 

660 bestedge[bv] = None 

661 b.mybestedges = list(bestedgeto.values()) 

662 # Select bestedge[b]. 

663 mybestedge = None 

664 bestedge[b] = None 

665 for k in b.mybestedges: 

666 kslack = slack(*k) 

667 if mybestedge is None or kslack < mybestslack: 

668 mybestedge = k 

669 mybestslack = kslack 

670 bestedge[b] = mybestedge 

671 

672 # Expand the given top-level blossom. 

673 def expandBlossom(b, endstage): 

674 # This is an obnoxiously complicated recursive function for the sake of 

675 # a stack-transformation. So, we hack around the complexity by using 

676 # a trampoline pattern. By yielding the arguments to each recursive 

677 # call, we keep the actual callstack flat. 

678 

679 def _recurse(b, endstage): 

680 # Convert sub-blossoms into top-level blossoms. 

681 for s in b.childs: 

682 blossomparent[s] = None 

683 if isinstance(s, Blossom): 

684 if endstage and blossomdual[s] == 0: 

685 # Recursively expand this sub-blossom. 

686 yield s 

687 else: 

688 for v in s.leaves(): 

689 inblossom[v] = s 

690 else: 

691 inblossom[s] = s 

692 # If we expand a T-blossom during a stage, its sub-blossoms must be 

693 # relabeled. 

694 if (not endstage) and label.get(b) == 2: 

695 # Start at the sub-blossom through which the expanding 

696 # blossom obtained its label, and relabel sub-blossoms untili 

697 # we reach the base. 

698 # Figure out through which sub-blossom the expanding blossom 

699 # obtained its label initially. 

700 entrychild = inblossom[labeledge[b][1]] 

701 # Decide in which direction we will go round the blossom. 

702 j = b.childs.index(entrychild) 

703 if j & 1: 

704 # Start index is odd; go forward and wrap. 

705 j -= len(b.childs) 

706 jstep = 1 

707 else: 

708 # Start index is even; go backward. 

709 jstep = -1 

710 # Move along the blossom until we get to the base. 

711 v, w = labeledge[b] 

712 while j != 0: 

713 # Relabel the T-sub-blossom. 

714 if jstep == 1: 

715 p, q = b.edges[j] 

716 else: 

717 q, p = b.edges[j - 1] 

718 label[w] = None 

719 label[q] = None 

720 assignLabel(w, 2, v) 

721 # Step to the next S-sub-blossom and note its forward edge. 

722 allowedge[(p, q)] = allowedge[(q, p)] = True 

723 j += jstep 

724 if jstep == 1: 

725 v, w = b.edges[j] 

726 else: 

727 w, v = b.edges[j - 1] 

728 # Step to the next T-sub-blossom. 

729 allowedge[(v, w)] = allowedge[(w, v)] = True 

730 j += jstep 

731 # Relabel the base T-sub-blossom WITHOUT stepping through to 

732 # its mate (so don't call assignLabel). 

733 bw = b.childs[j] 

734 label[w] = label[bw] = 2 

735 labeledge[w] = labeledge[bw] = (v, w) 

736 bestedge[bw] = None 

737 # Continue along the blossom until we get back to entrychild. 

738 j += jstep 

739 while b.childs[j] != entrychild: 

740 # Examine the vertices of the sub-blossom to see whether 

741 # it is reachable from a neighbouring S-vertex outside the 

742 # expanding blossom. 

743 bv = b.childs[j] 

744 if label.get(bv) == 1: 

745 # This sub-blossom just got label S through one of its 

746 # neighbours; leave it be. 

747 j += jstep 

748 continue 

749 if isinstance(bv, Blossom): 

750 for v in bv.leaves(): 

751 if label.get(v): 

752 break 

753 else: 

754 v = bv 

755 # If the sub-blossom contains a reachable vertex, assign 

756 # label T to the sub-blossom. 

757 if label.get(v): 

758 assert label[v] == 2 

759 assert inblossom[v] == bv 

760 label[v] = None 

761 label[mate[blossombase[bv]]] = None 

762 assignLabel(v, 2, labeledge[v][0]) 

763 j += jstep 

764 # Remove the expanded blossom entirely. 

765 label.pop(b, None) 

766 labeledge.pop(b, None) 

767 bestedge.pop(b, None) 

768 del blossomparent[b] 

769 del blossombase[b] 

770 del blossomdual[b] 

771 

772 # Now, we apply the trampoline pattern. We simulate a recursive 

773 # callstack by maintaining a stack of generators, each yielding a 

774 # sequence of function arguments. We grow the stack by appending a call 

775 # to _recurse on each argument tuple, and shrink the stack whenever a 

776 # generator is exhausted. 

777 stack = [_recurse(b, endstage)] 

778 while stack: 

779 top = stack[-1] 

780 for s in top: 

781 stack.append(_recurse(s, endstage)) 

782 break 

783 else: 

784 stack.pop() 

785 

786 # Swap matched/unmatched edges over an alternating path through blossom b 

787 # between vertex v and the base vertex. Keep blossom bookkeeping 

788 # consistent. 

789 def augmentBlossom(b, v): 

790 # This is an obnoxiously complicated recursive function for the sake of 

791 # a stack-transformation. So, we hack around the complexity by using 

792 # a trampoline pattern. By yielding the arguments to each recursive 

793 # call, we keep the actual callstack flat. 

794 

795 def _recurse(b, v): 

796 # Bubble up through the blossom tree from vertex v to an immediate 

797 # sub-blossom of b. 

798 t = v 

799 while blossomparent[t] != b: 

800 t = blossomparent[t] 

801 # Recursively deal with the first sub-blossom. 

802 if isinstance(t, Blossom): 

803 yield (t, v) 

804 # Decide in which direction we will go round the blossom. 

805 i = j = b.childs.index(t) 

806 if i & 1: 

807 # Start index is odd; go forward and wrap. 

808 j -= len(b.childs) 

809 jstep = 1 

810 else: 

811 # Start index is even; go backward. 

812 jstep = -1 

813 # Move along the blossom until we get to the base. 

814 while j != 0: 

815 # Step to the next sub-blossom and augment it recursively. 

816 j += jstep 

817 t = b.childs[j] 

818 if jstep == 1: 

819 w, x = b.edges[j] 

820 else: 

821 x, w = b.edges[j - 1] 

822 if isinstance(t, Blossom): 

823 yield (t, w) 

824 # Step to the next sub-blossom and augment it recursively. 

825 j += jstep 

826 t = b.childs[j] 

827 if isinstance(t, Blossom): 

828 yield (t, x) 

829 # Match the edge connecting those sub-blossoms. 

830 mate[w] = x 

831 mate[x] = w 

832 # Rotate the list of sub-blossoms to put the new base at the front. 

833 b.childs = b.childs[i:] + b.childs[:i] 

834 b.edges = b.edges[i:] + b.edges[:i] 

835 blossombase[b] = blossombase[b.childs[0]] 

836 assert blossombase[b] == v 

837 

838 # Now, we apply the trampoline pattern. We simulate a recursive 

839 # callstack by maintaining a stack of generators, each yielding a 

840 # sequence of function arguments. We grow the stack by appending a call 

841 # to _recurse on each argument tuple, and shrink the stack whenever a 

842 # generator is exhausted. 

843 stack = [_recurse(b, v)] 

844 while stack: 

845 top = stack[-1] 

846 for args in top: 

847 stack.append(_recurse(*args)) 

848 break 

849 else: 

850 stack.pop() 

851 

852 # Swap matched/unmatched edges over an alternating path between two 

853 # single vertices. The augmenting path runs through S-vertices v and w. 

854 def augmentMatching(v, w): 

855 for s, j in ((v, w), (w, v)): 

856 # Match vertex s to vertex j. Then trace back from s 

857 # until we find a single vertex, swapping matched and unmatched 

858 # edges as we go. 

859 while 1: 

860 bs = inblossom[s] 

861 assert label[bs] == 1 

862 assert (labeledge[bs] is None and blossombase[bs] not in mate) or ( 

863 labeledge[bs][0] == mate[blossombase[bs]] 

864 ) 

865 # Augment through the S-blossom from s to base. 

866 if isinstance(bs, Blossom): 

867 augmentBlossom(bs, s) 

868 # Update mate[s] 

869 mate[s] = j 

870 # Trace one step back. 

871 if labeledge[bs] is None: 

872 # Reached single vertex; stop. 

873 break 

874 t = labeledge[bs][0] 

875 bt = inblossom[t] 

876 assert label[bt] == 2 

877 # Trace one more step back. 

878 s, j = labeledge[bt] 

879 # Augment through the T-blossom from j to base. 

880 assert blossombase[bt] == t 

881 if isinstance(bt, Blossom): 

882 augmentBlossom(bt, j) 

883 # Update mate[j] 

884 mate[j] = s 

885 

886 # Verify that the optimum solution has been reached. 

887 def verifyOptimum(): 

888 if maxcardinality: 

889 # Vertices may have negative dual; 

890 # find a constant non-negative number to add to all vertex duals. 

891 vdualoffset = max(0, -min(dualvar.values())) 

892 else: 

893 vdualoffset = 0 

894 # 0. all dual variables are non-negative 

895 assert min(dualvar.values()) + vdualoffset >= 0 

896 assert len(blossomdual) == 0 or min(blossomdual.values()) >= 0 

897 # 0. all edges have non-negative slack and 

898 # 1. all matched edges have zero slack; 

899 for i, j, d in G.edges(data=True): 

900 wt = d.get(weight, 1) 

901 if i == j: 

902 continue # ignore self-loops 

903 s = dualvar[i] + dualvar[j] - 2 * wt 

904 iblossoms = [i] 

905 jblossoms = [j] 

906 while blossomparent[iblossoms[-1]] is not None: 

907 iblossoms.append(blossomparent[iblossoms[-1]]) 

908 while blossomparent[jblossoms[-1]] is not None: 

909 jblossoms.append(blossomparent[jblossoms[-1]]) 

910 iblossoms.reverse() 

911 jblossoms.reverse() 

912 for bi, bj in zip(iblossoms, jblossoms): 

913 if bi != bj: 

914 break 

915 s += 2 * blossomdual[bi] 

916 assert s >= 0 

917 if mate.get(i) == j or mate.get(j) == i: 

918 assert mate[i] == j and mate[j] == i 

919 assert s == 0 

920 # 2. all single vertices have zero dual value; 

921 for v in gnodes: 

922 assert (v in mate) or dualvar[v] + vdualoffset == 0 

923 # 3. all blossoms with positive dual value are full. 

924 for b in blossomdual: 

925 if blossomdual[b] > 0: 

926 assert len(b.edges) % 2 == 1 

927 for i, j in b.edges[1::2]: 

928 assert mate[i] == j and mate[j] == i 

929 # Ok. 

930 

931 # Main loop: continue until no further improvement is possible. 

932 while 1: 

933 # Each iteration of this loop is a "stage". 

934 # A stage finds an augmenting path and uses that to improve 

935 # the matching. 

936 

937 # Remove labels from top-level blossoms/vertices. 

938 label.clear() 

939 labeledge.clear() 

940 

941 # Forget all about least-slack edges. 

942 bestedge.clear() 

943 for b in blossomdual: 

944 b.mybestedges = None 

945 

946 # Loss of labeling means that we can not be sure that currently 

947 # allowable edges remain allowable throughout this stage. 

948 allowedge.clear() 

949 

950 # Make queue empty. 

951 queue[:] = [] 

952 

953 # Label single blossoms/vertices with S and put them in the queue. 

954 for v in gnodes: 

955 if (v not in mate) and label.get(inblossom[v]) is None: 

956 assignLabel(v, 1, None) 

957 

958 # Loop until we succeed in augmenting the matching. 

959 augmented = 0 

960 while 1: 

961 # Each iteration of this loop is a "substage". 

962 # A substage tries to find an augmenting path; 

963 # if found, the path is used to improve the matching and 

964 # the stage ends. If there is no augmenting path, the 

965 # primal-dual method is used to pump some slack out of 

966 # the dual variables. 

967 

968 # Continue labeling until all vertices which are reachable 

969 # through an alternating path have got a label. 

970 while queue and not augmented: 

971 # Take an S vertex from the queue. 

972 v = queue.pop() 

973 assert label[inblossom[v]] == 1 

974 

975 # Scan its neighbours: 

976 for w in G.neighbors(v): 

977 if w == v: 

978 continue # ignore self-loops 

979 # w is a neighbour to v 

980 bv = inblossom[v] 

981 bw = inblossom[w] 

982 if bv == bw: 

983 # this edge is internal to a blossom; ignore it 

984 continue 

985 if (v, w) not in allowedge: 

986 kslack = slack(v, w) 

987 if kslack <= 0: 

988 # edge k has zero slack => it is allowable 

989 allowedge[(v, w)] = allowedge[(w, v)] = True 

990 if (v, w) in allowedge: 

991 if label.get(bw) is None: 

992 # (C1) w is a free vertex; 

993 # label w with T and label its mate with S (R12). 

994 assignLabel(w, 2, v) 

995 elif label.get(bw) == 1: 

996 # (C2) w is an S-vertex (not in the same blossom); 

997 # follow back-links to discover either an 

998 # augmenting path or a new blossom. 

999 base = scanBlossom(v, w) 

1000 if base is not NoNode: 

1001 # Found a new blossom; add it to the blossom 

1002 # bookkeeping and turn it into an S-blossom. 

1003 addBlossom(base, v, w) 

1004 else: 

1005 # Found an augmenting path; augment the 

1006 # matching and end this stage. 

1007 augmentMatching(v, w) 

1008 augmented = 1 

1009 break 

1010 elif label.get(w) is None: 

1011 # w is inside a T-blossom, but w itself has not 

1012 # yet been reached from outside the blossom; 

1013 # mark it as reached (we need this to relabel 

1014 # during T-blossom expansion). 

1015 assert label[bw] == 2 

1016 label[w] = 2 

1017 labeledge[w] = (v, w) 

1018 elif label.get(bw) == 1: 

1019 # keep track of the least-slack non-allowable edge to 

1020 # a different S-blossom. 

1021 if bestedge.get(bv) is None or kslack < slack(*bestedge[bv]): 

1022 bestedge[bv] = (v, w) 

1023 elif label.get(w) is None: 

1024 # w is a free vertex (or an unreached vertex inside 

1025 # a T-blossom) but we can not reach it yet; 

1026 # keep track of the least-slack edge that reaches w. 

1027 if bestedge.get(w) is None or kslack < slack(*bestedge[w]): 

1028 bestedge[w] = (v, w) 

1029 

1030 if augmented: 

1031 break 

1032 

1033 # There is no augmenting path under these constraints; 

1034 # compute delta and reduce slack in the optimization problem. 

1035 # (Note that our vertex dual variables, edge slacks and delta's 

1036 # are pre-multiplied by two.) 

1037 deltatype = -1 

1038 delta = deltaedge = deltablossom = None 

1039 

1040 # Compute delta1: the minimum value of any vertex dual. 

1041 if not maxcardinality: 

1042 deltatype = 1 

1043 delta = min(dualvar.values()) 

1044 

1045 # Compute delta2: the minimum slack on any edge between 

1046 # an S-vertex and a free vertex. 

1047 for v in G.nodes(): 

1048 if label.get(inblossom[v]) is None and bestedge.get(v) is not None: 

1049 d = slack(*bestedge[v]) 

1050 if deltatype == -1 or d < delta: 

1051 delta = d 

1052 deltatype = 2 

1053 deltaedge = bestedge[v] 

1054 

1055 # Compute delta3: half the minimum slack on any edge between 

1056 # a pair of S-blossoms. 

1057 for b in blossomparent: 

1058 if ( 

1059 blossomparent[b] is None 

1060 and label.get(b) == 1 

1061 and bestedge.get(b) is not None 

1062 ): 

1063 kslack = slack(*bestedge[b]) 

1064 if allinteger: 

1065 assert (kslack % 2) == 0 

1066 d = kslack // 2 

1067 else: 

1068 d = kslack / 2.0 

1069 if deltatype == -1 or d < delta: 

1070 delta = d 

1071 deltatype = 3 

1072 deltaedge = bestedge[b] 

1073 

1074 # Compute delta4: minimum z variable of any T-blossom. 

1075 for b in blossomdual: 

1076 if ( 

1077 blossomparent[b] is None 

1078 and label.get(b) == 2 

1079 and (deltatype == -1 or blossomdual[b] < delta) 

1080 ): 

1081 delta = blossomdual[b] 

1082 deltatype = 4 

1083 deltablossom = b 

1084 

1085 if deltatype == -1: 

1086 # No further improvement possible; max-cardinality optimum 

1087 # reached. Do a final delta update to make the optimum 

1088 # verifiable. 

1089 assert maxcardinality 

1090 deltatype = 1 

1091 delta = max(0, min(dualvar.values())) 

1092 

1093 # Update dual variables according to delta. 

1094 for v in gnodes: 

1095 if label.get(inblossom[v]) == 1: 

1096 # S-vertex: 2*u = 2*u - 2*delta 

1097 dualvar[v] -= delta 

1098 elif label.get(inblossom[v]) == 2: 

1099 # T-vertex: 2*u = 2*u + 2*delta 

1100 dualvar[v] += delta 

1101 for b in blossomdual: 

1102 if blossomparent[b] is None: 

1103 if label.get(b) == 1: 

1104 # top-level S-blossom: z = z + 2*delta 

1105 blossomdual[b] += delta 

1106 elif label.get(b) == 2: 

1107 # top-level T-blossom: z = z - 2*delta 

1108 blossomdual[b] -= delta 

1109 

1110 # Take action at the point where minimum delta occurred. 

1111 if deltatype == 1: 

1112 # No further improvement possible; optimum reached. 

1113 break 

1114 elif deltatype == 2: 

1115 # Use the least-slack edge to continue the search. 

1116 (v, w) = deltaedge 

1117 assert label[inblossom[v]] == 1 

1118 allowedge[(v, w)] = allowedge[(w, v)] = True 

1119 queue.append(v) 

1120 elif deltatype == 3: 

1121 # Use the least-slack edge to continue the search. 

1122 (v, w) = deltaedge 

1123 allowedge[(v, w)] = allowedge[(w, v)] = True 

1124 assert label[inblossom[v]] == 1 

1125 queue.append(v) 

1126 elif deltatype == 4: 

1127 # Expand the least-z blossom. 

1128 expandBlossom(deltablossom, False) 

1129 

1130 # End of a this substage. 

1131 

1132 # Paranoia check that the matching is symmetric. 

1133 for v in mate: 

1134 assert mate[mate[v]] == v 

1135 

1136 # Stop when no more augmenting path can be found. 

1137 if not augmented: 

1138 break 

1139 

1140 # End of a stage; expand all S-blossoms which have zero dual. 

1141 for b in list(blossomdual.keys()): 

1142 if b not in blossomdual: 

1143 continue # already expanded 

1144 if blossomparent[b] is None and label.get(b) == 1 and blossomdual[b] == 0: 

1145 expandBlossom(b, True) 

1146 

1147 # Verify that we reached the optimum solution (only for integer weights). 

1148 if allinteger: 

1149 verifyOptimum() 

1150 

1151 return matching_dict_to_set(mate)