diff --git a/python/mwmatching/algorithm.py b/python/mwmatching/algorithm.py index 3b9007e..3293fa2 100644 --- a/python/mwmatching/algorithm.py +++ b/python/mwmatching/algorithm.py @@ -393,8 +393,8 @@ class Blossom: # Each top-level blossom maintains a concatenable queue containing # all vertices in the blossom. - self.vertex_set: ConcatenableQueue[Blossom, int] - self.vertex_set = ConcatenableQueue(self) + self.vertex_queue: ConcatenableQueue[Blossom, int] + self.vertex_queue = ConcatenableQueue(self) # If this is a top-level unlabeled blossom with an edge to an # S-blossom, "delta2_node" is the corresponding node in the delta2 @@ -554,12 +554,13 @@ class MatchingContext: # Initially there are no non-trivial blossoms. self.nontrivial_blossom: set[NonTrivialBlossom] = set() - # "vertex_set_node[x]" represents the vertex "x" inside the + # "vertex_queue_node[x]" represents the vertex "x" inside the # concatenable queue of its top-level blossom. # # Initially, each vertex belongs to its own trivial top-level blossom. - self.vertex_set_node = [b.vertex_set.insert(i, math.inf) - for (i, b) in enumerate(self.trivial_blossom)] + self.vertex_queue_node = [ + b.vertex_queue.insert(i, math.inf) + for (i, b) in enumerate(self.trivial_blossom)] # All vertex duals are initialized to half the maximum edge weight. # @@ -623,8 +624,19 @@ class MatchingContext: for blossom in itertools.chain(self.trivial_blossom, self.nontrivial_blossom): blossom.parent = None - blossom.vertex_set.clear() - del blossom.vertex_set + blossom.vertex_queue.clear() + del blossom.vertex_queue + + # + # Find top-level blossom: + # + + def top_level_blossom(self, x: int) -> Blossom: + """Find the top-level blossom that contains vertex "x". + + This function takes time O(log(n)). + """ + return self.vertex_queue_node[x].find() # # Least-slack edge tracking: @@ -670,7 +682,7 @@ class MatchingContext: return # Update the priority of "y" in its ConcatenableQueue. - self.vertex_set_node[y].set_prio(prio) + self.vertex_queue_node[y].set_prio(prio) # If the blossom is unlabeled and the new edge becomes its least-slack # S-edge, insert or update the blossom in the global delta2 queue. @@ -703,12 +715,12 @@ class MatchingContext: prio = vertex_sedge_queue.find_min().prio # If necessary, update priority of "y" in its ConcatenableQueue. - if prio > self.vertex_set_node[y].prio: - self.vertex_set_node[y].set_prio(prio) + if prio > self.vertex_queue_node[y].prio: + self.vertex_queue_node[y].set_prio(prio) if by.label == LABEL_NONE: # Update or delete the blossom in the global delta2 queue. assert by.delta2_node is not None - prio = by.vertex_set.min_prio() + prio = by.vertex_queue.min_prio() if prio < math.inf: prio += by.vertex_dual_offset if prio > by.delta2_node.prio: @@ -728,7 +740,7 @@ class MatchingContext: This function takes time O(log(n)). """ assert blossom.delta2_node is None - prio = blossom.vertex_set.min_prio() + prio = blossom.vertex_queue.min_prio() if prio < math.inf: prio += blossom.vertex_dual_offset blossom.delta2_node = self.delta2_queue.insert(prio, blossom) @@ -759,7 +771,7 @@ class MatchingContext: self.vertex_sedge_queue[x].clear() for e in self.graph.adjacent_edges[x]: self.vertex_sedge_node[e] = None - self.vertex_set_node[x].set_prio(math.inf) + self.vertex_queue_node[x].set_prio(math.inf) def delta2_get_min_edge(self) -> tuple[int, float]: """Find the least-slack edge between any S-vertex and any unlabeled @@ -782,7 +794,7 @@ class MatchingContext: assert blossom.parent is None assert blossom.label == LABEL_NONE - x = blossom.vertex_set.min_elem() + x = blossom.vertex_queue.min_elem() e = self.vertex_sedge_queue[x].find_min().data return (e, slack_2x) @@ -838,8 +850,8 @@ class MatchingContext: delta3_node = self.delta3_queue.find_min() e = delta3_node.data (x, y, _w) = self.graph.edges[e] - bx = self.vertex_set_node[x].find() - by = self.vertex_set_node[y].find() + bx = self.top_level_blossom(x) + by = self.top_level_blossom(y) assert (bx.label == LABEL_S) and (by.label == LABEL_S) if bx is not by: slack = delta3_node.prio - self.delta_sum_2x @@ -1001,7 +1013,7 @@ class MatchingContext: # and vertex "x" is no longer an S-vertex. self.delta3_remove_edge(e) - by = self.vertex_set_node[y].find() + by = self.top_level_blossom(y) if by.label == LABEL_S: # Edge "e" connects unlabeled vertex "x" to S-vertex "y". # It must be tracked for delta2 via vertex "x". @@ -1121,7 +1133,7 @@ class MatchingContext: while x != -1 or y != -1: # Check if we found a common ancestor. - bx = self.vertex_set_node[x].find() + bx = self.top_level_blossom(x) if bx.marker: first_common = bx break @@ -1149,8 +1161,8 @@ class MatchingContext: # If we found a common ancestor, trim the paths so they end there. if first_common is not None: - assert self.vertex_set_node[xedges[-1][0]].find() is first_common - while (self.vertex_set_node[yedges[-1][0]].find() + assert self.top_level_blossom(xedges[-1][0]) is first_common + while (self.top_level_blossom(yedges[-1][0]) is not first_common): yedges.pop() @@ -1187,12 +1199,12 @@ class MatchingContext: assert len(path.edges) >= 3 # Construct the list of sub-blossoms (current top-level blossoms). - subblossoms = [self.vertex_set_node[x].find() for (x, y) in path.edges] + subblossoms = [self.top_level_blossom(x) for (x, y) in path.edges] # Check that the path is cyclic. # Note the path will not always start and end with the same _vertex_, # but it must start and end in the same _blossom_. - subblossoms_next = [self.vertex_set_node[y].find() + subblossoms_next = [self.top_level_blossom(y) for (x, y) in path.edges] assert subblossoms[0] == subblossoms_next[-1] assert subblossoms[1:] == subblossoms_next[:-1] @@ -1237,7 +1249,7 @@ class MatchingContext: tree_blossoms.remove(sub) # Merge concatenable queues. - blossom.vertex_set.merge([sub.vertex_set for sub in subblossoms]) + blossom.vertex_queue.merge([sub.vertex_queue for sub in subblossoms]) @staticmethod def find_path_through_blossom( @@ -1282,7 +1294,7 @@ class MatchingContext: self.delta2_disable_blossom(blossom) # Split concatenable queue. - blossom.vertex_set.split() + blossom.vertex_queue.split() # Prepare to push lazy delta updates down to the sub-blossoms. vertex_dual_offset = blossom.vertex_dual_offset @@ -1299,7 +1311,7 @@ class MatchingContext: self.delta2_enable_blossom(sub) # Avoid leaking a reference cycle. - del blossom.vertex_set + del blossom.vertex_queue # Delete the expanded blossom. self.nontrivial_blossom.remove(blossom) @@ -1335,7 +1347,7 @@ class MatchingContext: # the alternating tree. assert blossom.tree_edge is not None (x, y) = blossom.tree_edge - sub = self.vertex_set_node[y].find() + sub = self.top_level_blossom(y) # Assign label T to that sub-blossom. self.assign_blossom_label_t(sub) @@ -1480,7 +1492,7 @@ class MatchingContext: # an unmatched vertex or a blossom with unmatched base. assert len(path.edges) % 2 == 1 for x in (path.edges[0][0], path.edges[-1][1]): - b = self.vertex_set_node[x].find() + b = self.top_level_blossom(x) assert self.vertex_mate[b.base_vertex] == -1 # The augmenting path looks like this: @@ -1498,11 +1510,11 @@ class MatchingContext: # Augment the non-trivial blossoms on either side of this edge. # No action is necessary for trivial blossoms. - bx = self.vertex_set_node[x].find() + bx = self.top_level_blossom(x) if isinstance(bx, NonTrivialBlossom): self.augment_blossom(bx, self.trivial_blossom[x]) - by = self.vertex_set_node[y].find() + by = self.top_level_blossom(y) if isinstance(by, NonTrivialBlossom): self.augment_blossom(by, self.trivial_blossom[y]) @@ -1527,14 +1539,14 @@ class MatchingContext: """ # Assign label S to the blossom that contains vertex "x". - bx = self.vertex_set_node[x].find() + bx = self.top_level_blossom(x) self.assign_blossom_label_s(bx) # Vertex "x" is matched to T-vertex "y". y = self.vertex_mate[x] assert y != -1 - by = self.vertex_set_node[y].find() + by = self.top_level_blossom(y) assert by.label == LABEL_T assert by.tree_blossoms is not None @@ -1557,14 +1569,14 @@ class MatchingContext: - There is a tight edge between vertices "x" and "y". """ - bx = self.vertex_set_node[x].find() - by = self.vertex_set_node[y].find() + bx = self.top_level_blossom(x) + by = self.top_level_blossom(y) assert bx.label == LABEL_S # Expand zero-dual blossoms before assigning label T. while isinstance(by, NonTrivialBlossom) and (by.dual_var == 0): self.expand_unlabeled_blossom(by) - by = self.vertex_set_node[y].find() + by = self.top_level_blossom(y) # Assign label T to the unlabeled blossom. self.assign_blossom_label_t(by) @@ -1598,8 +1610,8 @@ class MatchingContext: True if the matching was augmented; otherwise False. """ - bx = self.vertex_set_node[x].find() - by = self.vertex_set_node[y].find() + bx = self.top_level_blossom(x) + by = self.top_level_blossom(y) assert bx.label == LABEL_S assert by.label == LABEL_S @@ -1663,7 +1675,7 @@ class MatchingContext: for x in self.scan_queue: # Double-check that "x" is an S-vertex. - bx = self.vertex_set_node[x].find() + bx = self.top_level_blossom(x) assert bx.label == LABEL_S # Scan the edges that are incident on "x". @@ -1683,7 +1695,7 @@ class MatchingContext: # Try to pull this edge into an alternating tree. # Ignore edges that are internal to a blossom. - by = self.vertex_set_node[y].find() + by = self.top_level_blossom(y) if bx is by: continue @@ -1778,7 +1790,7 @@ class MatchingContext: """ for x in range(self.graph.num_vertex): assert self.vertex_mate[x] == -1 - bx = self.vertex_set_node[x].find() + bx = self.top_level_blossom(x) assert bx.base_vertex == x # Assign label S. @@ -1829,7 +1841,7 @@ class MatchingContext: # Use the edge from S-vertex to unlabeled vertex that got # unlocked through the delta update. (x, y, _w) = self.graph.edges[delta_edge] - if self.vertex_set_node[x].find().label != LABEL_S: + if self.top_level_blossom(x).label != LABEL_S: (x, y) = (y, x) self.extend_tree_s_to_t(x, y)