diff --git a/python/mwmatching.py b/python/mwmatching.py index 254845e..733cc29 100644 --- a/python/mwmatching.py +++ b/python/mwmatching.py @@ -508,6 +508,7 @@ class _AlternatingPath(NamedTuple): """Represents a list of edges forming an alternating path or an alternating cycle.""" edges: list[tuple[int, int]] + is_cycle: bool class _MatchingContext: @@ -1166,7 +1167,8 @@ class _MatchingContext: # Any S-to-S alternating path must have odd length. assert len(path_edges) % 2 == 1 - return _AlternatingPath(path_edges) + return _AlternatingPath(edges=path_edges, + is_cycle=(first_common is not None)) # # Merge and expand blossoms: @@ -1243,8 +1245,8 @@ class _MatchingContext: blossom: _NonTrivialBlossom, sub: _Blossom ) -> tuple[list[_Blossom], list[tuple[int, int]]]: - """Construct a path through the specified blossom, - from sub-blossom "sub" to the base of the blossom. + """Construct a path with an even number of edges through the + specified blossom, from sub-blossom "sub" to the base of "blossom". Return: Tuple (nodes, edges). @@ -1263,6 +1265,9 @@ class _MatchingContext: nodes = blossom.subblossoms[p:] + blossom.subblossoms[0:1] edges = blossom.edges[p:] + assert len(edges) % 2 == 0 + assert len(nodes) % 2 == 1 + return (nodes, edges) def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None: @@ -1352,7 +1357,7 @@ class _MatchingContext: # Assign label S to path_nodes[p+1]. (y, x) = path_edges[p] - self.extend_tree_s(x) + self.extend_tree_t_to_s(x) # Assign label T to path_nodes[i+2] and attach it # to path_nodes[p+1]. @@ -1504,59 +1509,49 @@ class _MatchingContext: self.vertex_mate[y] = x # - # Labeling and alternating tree expansion: + # Alternating tree: # - def extend_tree_s(self, x: int) -> None: + def extend_tree_t_to_s(self, x: int) -> None: """Assign label S to the unlabeled blossom that contains vertex "x". - If vertex "x" is matched, it is attached to the alternating tree - via its matched edge. If vertex "x" is unmatched, it becomes the root - of an alternating tree. + The newly labeled S-blossom is added to the alternating tree + via its matched edge. All vertices in the newly labeled S-blossom + are added to the scan queue. - All vertices in the newly labeled blossom are added to the scan queue. - - Precondition: - "x" is an unlabeled vertex, either unmatched or matched to - a T-vertex via a tight edge. + Preconditions: + - "x" is a vertex in an unlabeled blossom. + - "x" is matched to a T-vertex via a tight edge. """ # Assign label S to the blossom that contains vertex "x". bx = self.vertex_set_node[x].find() self.assign_blossom_label_s(bx) + # Vertex "x" is matched to T-vertex "y". y = self.vertex_mate[x] - if y == -1: - # Vertex "x" is unmatched. - # It must be either a top-level vertex or the base vertex of - # a top-level blossom. - assert bx.base_vertex == x + assert y != -1 - # Mark the blossom as root of an alternating tree. - bx.tree_edge = None - bx.tree_blossoms = {bx} + by = self.vertex_set_node[y].find() + assert by.label == _LABEL_T + assert by.tree_blossoms is not None - else: - # Vertex "x" is matched to T-vertex "y". - by = self.vertex_set_node[y].find() - assert by.label == _LABEL_T + # Attach the blossom that contains "x" to the alternating tree. + bx.tree_edge = (y, x) + bx.tree_blossoms = by.tree_blossoms + bx.tree_blossoms.add(bx) - # Attach the blossom that contains "x" to the alternating tree. - bx.tree_edge = (y, x) - - bx.tree_blossoms = by.tree_blossoms - assert bx.tree_blossoms is not None - bx.tree_blossoms.add(bx) - - def extend_tree_t(self, x: int, y: int) -> None: + def extend_tree_s_to_t(self, x: int, y: int) -> None: """Assign label T to the unlabeled blossom that contains vertex "y". - Attach it to the alternating tree via edge (x, y). - Then immediately assign label S to the mate of vertex "y". + The newly labeled T-blossom is added to the alternating tree. + Directly afterwards, label S is assigned to the blossom that has + a matched edge to the base of the newly labeled T-blossom, and + that newly labeled S-blossom is also added to the alternating tree. Preconditions: - "x" is an S-vertex. - - "y" is an unlabeled, matched vertex. + - "y" is a vertex in an unlabeled blossom with a matched base vertex. - There is a tight edge between vertices "x" and "y". """ @@ -1579,36 +1574,66 @@ class _MatchingContext: # Assign label S to the blossom that is mated to the T-blossom. z = self.vertex_mate[by.base_vertex] assert z != -1 - self.extend_tree_s(z) + self.extend_tree_t_to_s(z) - def add_s_to_s_edge(self, x: int, y: int) -> Optional[_AlternatingPath]: + def add_s_to_s_edge(self, x: int, y: int) -> bool: """Add the edge between S-vertices "x" and "y". If the edge connects blossoms that are part of the same alternating - tree, this function creates a new S-blossom and returns None. + tree, this function creates a new S-blossom and returns False. If the edge connects two different alternating trees, an augmenting - path has been discovered. In this case the function changes nothing - and returns the augmenting path. + path has been discovered. This function then augments the matching + and returns True. Labels are removed from blossoms that belonged + to the two alternating trees involved in the matching. All other + alternating trees and labels are preserved. + + Preconditions: + - "x" and "y" are S-vertices in different top-level blossoms. + - There is a tight edge between vertices "x" and "y". Returns: - Augmenting path if found; otherwise None. + True if the matching was augmented; otherwise False. """ + bx = self.vertex_set_node[x].find() + by = self.vertex_set_node[y].find() + + assert bx.label == _LABEL_S + assert by.label == _LABEL_S + assert bx is not by + # Trace back through the alternating trees from "x" and "y". path = self.trace_alternating_paths(x, y) - # If the path is a cycle, create a new blossom. - # Otherwise the path is an augmenting path. - # Note that an alternating starts and ends in the same blossom, - # but not necessarily in the same vertex within that blossom. - p = path.edges[0][0] - q = path.edges[-1][1] - if self.vertex_set_node[p].find() is self.vertex_set_node[q].find(): + assert bx.tree_blossoms is not None + assert by.tree_blossoms is not None + + if bx.tree_blossoms is by.tree_blossoms: + # Both blossoms belong to the same alternating tree. + # This implies that the alternating path is a cycle. + # The path will be used to create a new blossom. + assert path.is_cycle self.make_blossom(path) - return None + + return False + else: - return path + # The blossoms belong to different alternating trees. + # This implies that the alternating path is an augmenting + # path between two unlabeled vertices. + # The path will be used to augment the matching. + + # Delete the two alternating trees on the augmenting path. + # The blossoms in those trees become unlabeled. + self.remove_alternating_tree(bx.tree_blossoms) + self.remove_alternating_tree(by.tree_blossoms) + + # Augment the matching. + assert not path.is_cycle + self.augment_matching(path) + + return True def substage_scan(self) -> None: """Scan queued S-vertices and consider their incident edges. @@ -1799,25 +1824,14 @@ class _MatchingContext: (x, y, _w) = self.graph.edges[delta_edge] if self.vertex_set_node[x].find().label != _LABEL_S: (x, y) = (y, x) - self.extend_tree_t(x, y) + self.extend_tree_s_to_t(x, y) elif delta_type == 3: # Use the S-to-S edge that got unlocked by the delta update. # This reveals either a new blossom or an augmenting path. (x, y, _w) = self.graph.edges[delta_edge] - augmenting_path = self.add_s_to_s_edge(x, y) - if augmenting_path is not None: - # Found augmenting path. - # Delete the two alternating trees on the augmenting path. - bx = self.vertex_set_node[x].find() - by = self.vertex_set_node[y].find() - assert bx.tree_blossoms is not None - assert by.tree_blossoms is not None - self.remove_alternating_tree(bx.tree_blossoms) - self.remove_alternating_tree(by.tree_blossoms) - # Augment the matching. - self.augment_matching(augmenting_path) - # End the stage. + if self.add_s_to_s_edge(x, y): + # Matching was augmented. End the stage. return True elif delta_type == 4: