From b2e055b35782c852d331b4f457e67efe050b1a18 Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Sun, 26 May 2024 00:07:16 +0200 Subject: [PATCH] Lazy delta updates of S-blossom duals --- python/mwmatching.py | 79 ++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/python/mwmatching.py b/python/mwmatching.py index d23782b..af986ac 100644 --- a/python/mwmatching.py +++ b/python/mwmatching.py @@ -444,7 +444,13 @@ class _NonTrivialBlossom(_Blossom): # Every non-trivial blossom has a variable in the dual LPP. # + # If this is a top-level S-blossom, + # "dual_var" is the value of the dual variable minus 2 times + # the running sum of delta steps. + # + # In all other cases, # "dual_var" is the current value of the dual variable. + # # New blossoms start with dual variable 0. self.dual_var: float = 0 @@ -542,7 +548,7 @@ class _MatchingContext: # # For an S-vertex "x", # "vertex_dual_2x[x]" is 2 times the dual variable of vertex "x" - # plus two times the running sum of delta updates. + # plus two times the running sum of delta steps. # # For a T-vertex "x", ... TODO self.vertex_dual_2x: list[float] @@ -553,13 +559,13 @@ class _MatchingContext: # Queue containing edges between S-vertices in different top-level # blossoms. The priority of an edge is its slack plus two times the - # running sum of delta updates. + # running sum of delta steps. self.delta3_queue: PriorityQueue[int] = PriorityQueue() self.delta3_set: set[int] = set() # Queue containing top-level non-trivial T-blossoms. # The priority of a blossom is its dual plus two times the running - # sum of delta updates. + # sum of delta steps. self.delta4_queue: PriorityQueue[_NonTrivialBlossom] = PriorityQueue() # For each T-vertex or unlabeled vertex "x", @@ -677,34 +683,36 @@ class _MatchingContext: # def assign_blossom_label_s(self, blossom: _Blossom) -> None: - """Assign label S to an unlabeled top-level blossom. - - All vertices in the newly labeled blossom are added to the scan queue. - Dual variables are adjusted. - """ - + """Assign label S to an unlabeled top-level blossom.""" assert blossom.parent is None assert blossom.label == _LABEL_NONE - - # Assign label S. blossom.label = _LABEL_S + if isinstance(blossom, _NonTrivialBlossom): + blossom.dual_var -= self.delta_sum_2x + + def remove_blossom_label_s(self, blossom: _Blossom) -> None: + """Remove label S from a top-level S-blossom.""" + assert blossom.parent is None + assert blossom.label == _LABEL_S + blossom.label = _LABEL_NONE + + if isinstance(blossom, _NonTrivialBlossom): + blossom.dual_var += self.delta_sum_2x + + def assign_vertex_label_s(self, blossom: _Blossom) -> None: + """Adjust after assigning label S to previously unlabeled vertices.""" + # Adjust the vertex dual variables of the new S-vertices. vertices = blossom.vertices() for x in vertices: self.vertex_dual_2x[x] += self.delta_sum_2x - # Add all vertices inside the newly labeled S-blossom to the queue. + # Add the new S-vertices to the scan queue. self.scan_queue.extend(vertices) - def remove_blossom_label_s(self, blossom: _Blossom) -> None: - """Remove label S from a top-level S-blossom.""" - - assert blossom.parent is None - assert blossom.label == _LABEL_S - - # Remove label. - blossom.label = _LABEL_NONE + def remove_vertex_label_s(self, blossom: _Blossom) -> None: + """Adjust after removing labels from S-vertices.""" # Adjust the vertex dual variables of the former S-vertices. for x in blossom.vertices(): @@ -751,12 +759,11 @@ class _MatchingContext: # Remove blossom labels. for blossom in self.trivial_blossom + self.nontrivial_blossom: - if blossom.parent is None: - if blossom.label == _LABEL_S: - self.remove_blossom_label_s(blossom) - elif blossom.label == _LABEL_T: - self.remove_blossom_label_t(blossom) - blossom.label = _LABEL_NONE + if blossom.label == _LABEL_S: + self.remove_blossom_label_s(blossom) + self.remove_vertex_label_s(blossom) + elif blossom.label == _LABEL_T: + self.remove_blossom_label_t(blossom) blossom.tree_edge = None # Clear the scan queue. @@ -881,18 +888,20 @@ class _MatchingContext: # Blossom must start and end with an S-sub-blossom. assert subblossoms[0].label == _LABEL_S - # Relabel the T-sub-blossoms to S-sub-blossoms. - # This also adds new S-vertices to the scan queue. + # Remove blossom labels. + # Mark vertices inside former T-blossoms as S-vertices. for sub in subblossoms: - if sub.label == _LABEL_T: + if sub.label == _LABEL_S: + self.remove_blossom_label_s(sub) + elif sub.label == _LABEL_T: self.remove_blossom_label_t(sub) - self.assign_blossom_label_s(sub) + self.assign_vertex_label_s(sub) # Create the new blossom object. blossom = _NonTrivialBlossom(subblossoms, path.edges) # Assign label S to the new blossom and link it to the tree. - blossom.label = _LABEL_S + self.assign_blossom_label_s(blossom) blossom.tree_edge = subblossoms[0].tree_edge # Insert into the blossom array. @@ -1185,6 +1194,7 @@ class _MatchingContext: # Assign label S to the blossom that contains vertex "x". bx = self.vertex_top_blossom[x] self.assign_blossom_label_s(bx) + self.assign_vertex_label_s(bx) y = self.vertex_mate[x] if y == -1: @@ -1325,7 +1335,7 @@ class _MatchingContext: elif ylabel == _LABEL_S: # Update tracking of least-slack edges between S-blossoms. # Priority is edge slack plus 2 times the running sum of - # delta updates. + # delta steps. if e not in self.delta3_set: if self.graph.integer_weights: # If all edge weights are integers, the slack of @@ -1443,10 +1453,7 @@ class _MatchingContext: for blossom in self.nontrivial_blossom: if blossom.parent is None: blabel = blossom.label - if blabel == _LABEL_S: - # S-blossom: add 2*delta to dual variable. - blossom.dual_var += delta_2x - elif blabel == _LABEL_T: + if blabel == _LABEL_T: # T-blossom: subtract 2*delta from dual variable. blossom.dual_var -= delta_2x