Clean up magagement of blossom labels
This commit is contained in:
parent
f8c6b99842
commit
b960a85b6c
|
@ -386,7 +386,7 @@ class _Blossom:
|
|||
self.tree_edge: Optional[tuple[int, int]] = None
|
||||
|
||||
# For a labeled top-level blossom,
|
||||
# "alternating_tree" is the set of all top-level blossoms that belong
|
||||
# "tree_blossoms" is the set of all top-level blossoms that belong
|
||||
# to the same alternating tree. The same set instance is shared by
|
||||
# all top-level blossoms in the tree.
|
||||
self.tree_blossoms: "Optional[set[_Blossom]]" = None
|
||||
|
@ -856,77 +856,155 @@ class _MatchingContext:
|
|||
return (-1, math.inf)
|
||||
|
||||
#
|
||||
# General support routines:
|
||||
# Managing blossom labels:
|
||||
#
|
||||
|
||||
# TODO -- Although this code is correct, there is a conceptual problem
|
||||
# that the division of responsibilities is asymmetric.
|
||||
# For example, assign_blossom_label_s is not the exact
|
||||
# opposite of remove_blossom_label_s, and remove_blossom_label_s
|
||||
# has different responsibilities from remove_blossom_label_t.
|
||||
# This must be fixed by shifting responsibilities or renaming
|
||||
# functions, otherwise this stuff is impossible to understand.
|
||||
|
||||
def assign_blossom_label_s(self, blossom: _Blossom) -> None:
|
||||
"""Assign label S to an unlabeled top-level blossom."""
|
||||
"""Change an unlabeled top-level blossom into an S-blossom."""
|
||||
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_NONE
|
||||
blossom.label = _LABEL_S
|
||||
|
||||
# Labeled blossoms must not be in the delta2 queue.
|
||||
self.delta2_disable_blossom(blossom)
|
||||
|
||||
# Prepare for lazy updating of S-blossom dual variable.
|
||||
# Adjust for lazy updating of S-blossom dual variables.
|
||||
#
|
||||
# The true dual value of an unlabeled top-level blossom is
|
||||
# blossom.dual_var
|
||||
#
|
||||
# while the true dual value of a top-level S-blossom is
|
||||
# blossom.dual_var + ctx.delta_sum_2x
|
||||
#
|
||||
# The value of blossom.dual_var must be adjusted accordingly
|
||||
# when the blossom changes from unlabeled to S-blossom.
|
||||
#
|
||||
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
|
||||
|
||||
# Catch up with lazy updates to S-blossom dual variable.
|
||||
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."""
|
||||
|
||||
# Add the new S-vertices to the scan queue.
|
||||
vertices = blossom.vertices()
|
||||
self.scan_queue.extend(vertices)
|
||||
|
||||
# Prepare for lazy updating of S-vertex dual variables.
|
||||
# Apply pending updates to vertex dual variables and prepare
|
||||
# for lazy updating of S-vertex dual variables.
|
||||
#
|
||||
# For S-blossoms, blossom.vertex_dual_offset is always 0.
|
||||
#
|
||||
# Furthermore, the true dual value of an unlabeled vertex is
|
||||
# (vertex_dual_2x[x] + blossom.vertex_dual_offset) / 2
|
||||
#
|
||||
# while the true dual value of an S-vertex is
|
||||
# (vertex_dual_2x[x] - delta_sum_2x) / 2
|
||||
#
|
||||
# The value of vertex_dual_2x must be adjusted accordingly
|
||||
# when vertices change from unlabeled to S-vertex.
|
||||
#
|
||||
vertex_dual_fixup = self.delta_sum_2x + blossom.vertex_dual_offset
|
||||
blossom.vertex_dual_offset = 0
|
||||
vertices = blossom.vertices()
|
||||
for x in vertices:
|
||||
self.vertex_dual_2x[x] += vertex_dual_fixup
|
||||
|
||||
# S-vertices do not keep track of potential delta2 edges.
|
||||
self.delta2_clear_vertex(x)
|
||||
|
||||
# Add the new S-vertices to the scan queue.
|
||||
self.scan_queue.extend(vertices)
|
||||
|
||||
def assign_blossom_label_t(self, blossom: _Blossom) -> None:
|
||||
"""Assign label T to an unlabeled top-level blossom."""
|
||||
"""Change an unlabeled top-level blossom into a T-blossom."""
|
||||
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_NONE
|
||||
blossom.label = _LABEL_T
|
||||
|
||||
# Labeled blossoms must not be in the delta2 queue.
|
||||
self.delta2_disable_blossom(blossom)
|
||||
|
||||
if isinstance(blossom, _NonTrivialBlossom):
|
||||
|
||||
# Prepare for lazy updating of T-blossom dual variables.
|
||||
# Adjust for lazy updating of T-blossom dual variables.
|
||||
#
|
||||
# The true dual value of an unlabeled top-level blossom is
|
||||
# blossom.dual_var
|
||||
#
|
||||
# while the true dual value of a top-level T-blossom is
|
||||
# blossom.dual_var - ctx.delta_sum_2x
|
||||
#
|
||||
# The value of blossom.dual_var must be adjusted accordingly
|
||||
# when the blossom changes from unlabeled to S-blossom.
|
||||
#
|
||||
blossom.dual_var += self.delta_sum_2x
|
||||
|
||||
# Insert blossom into the delta4 queue.
|
||||
# Top-level T-blossoms are tracked in the delta4 queue.
|
||||
assert blossom.delta4_node is None
|
||||
blossom.delta4_node = self.delta4_queue.insert(blossom.dual_var,
|
||||
blossom)
|
||||
|
||||
# Prepare for lazy updating of T-vertex dual variables.
|
||||
#
|
||||
# The true dual value of an unlabeled vertex is
|
||||
# (vertex_dual_2x[x] + blossom.vertex_dual_offset) / 2
|
||||
#
|
||||
# while the true dual value of a T-vertex is
|
||||
# (vertex_dual_2x[x] + blossom.vertex_dual_offset + delta_sum_2x) / 2
|
||||
#
|
||||
# The value of blossom.vertex_dual_offset must be adjusted accordingly
|
||||
# when the blossom changes from unlabeled to T-blossom.
|
||||
#
|
||||
blossom.vertex_dual_offset -= self.delta_sum_2x
|
||||
|
||||
def remove_blossom_label_s(self, blossom: _Blossom) -> None:
|
||||
"""Change a top-level S-blossom into an unlabeled blossom.
|
||||
|
||||
For a blossom with "j" vertices and "k" incident edges,
|
||||
this function takes time O((j + k) * log(n)).
|
||||
|
||||
This function is called at most once per blossom per stage.
|
||||
It therefore takes total time O((n + m) * log(n)) per stage.
|
||||
"""
|
||||
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_S
|
||||
blossom.label = _LABEL_NONE
|
||||
|
||||
# Unwind lazy delta updates to the S-blossom dual variable.
|
||||
if isinstance(blossom, _NonTrivialBlossom):
|
||||
blossom.dual_var += self.delta_sum_2x
|
||||
|
||||
assert blossom.vertex_dual_offset == 0
|
||||
vertex_dual_fixup = -self.delta_sum_2x
|
||||
|
||||
edges = self.graph.edges
|
||||
adjacent_edges = self.graph.adjacent_edges
|
||||
|
||||
for x in blossom.vertices():
|
||||
|
||||
# Unwind lazy delta updates to S-vertex dual variables.
|
||||
self.vertex_dual_2x[x] += vertex_dual_fixup
|
||||
|
||||
# Scan the incident edges of all vertices in the blossom.
|
||||
for e in adjacent_edges[x]:
|
||||
(p, q, _w) = edges[e]
|
||||
y = p if p != x else q
|
||||
|
||||
# If this edge is in the delta3 queue, remove it.
|
||||
# Only edges between S-vertices are tracked for delta3,
|
||||
# and vertex "x" is no longer an S-vertex.
|
||||
self.delta3_remove_edge(e)
|
||||
|
||||
by = self.vertex_set_node[y].find()
|
||||
if by.label == _LABEL_S:
|
||||
# Edge "e" connects unlabeled vertex "x" to S-vertex "y".
|
||||
# It must be tracked for delta2 via vertex "x".
|
||||
self.delta2_add_edge(e, x, blossom)
|
||||
else:
|
||||
# Edge "e" connects former S-vertex "x" to T-vertex
|
||||
# or unlabeled vertex "y". That implies this edge was
|
||||
# tracked for delta2 via vertex "y", but it must be
|
||||
# removed now.
|
||||
self.delta2_remove_edge(e, y, by)
|
||||
|
||||
def remove_blossom_label_t(self, blossom: _Blossom) -> None:
|
||||
"""Remove label T from a top-level T-blossom."""
|
||||
"""Change a top-level T-blossom into an unlabeled blossom."""
|
||||
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_T
|
||||
|
@ -934,47 +1012,34 @@ class _MatchingContext:
|
|||
|
||||
if isinstance(blossom, _NonTrivialBlossom):
|
||||
|
||||
# Remove blossom from delta4 queue.
|
||||
# Unlabeled blossoms are not tracked in the delta4 queue.
|
||||
assert blossom.delta4_node is not None
|
||||
self.delta4_queue.delete(blossom.delta4_node)
|
||||
blossom.delta4_node = None
|
||||
|
||||
# Unwind lazy updates to T-blossom dual variable.
|
||||
# Unwind lazy updates to the T-blossom dual variable.
|
||||
blossom.dual_var -= self.delta_sum_2x
|
||||
|
||||
# Unwind lazy updates of T-vertex dual variables.
|
||||
blossom.vertex_dual_offset += self.delta_sum_2x
|
||||
|
||||
def remove_vertex_label_s(self, x: int, bx: _Blossom) -> None:
|
||||
"""Adjust delta tracking for S-vertex losings its label.
|
||||
# Enable unlabeled top-level blossom for delta2 tracking.
|
||||
self.delta2_enable_blossom(blossom)
|
||||
|
||||
This function is called when vertex "x" was an S-vertex but
|
||||
has just lost its label. This requires adjustments in the tracking
|
||||
of delta2 and delta3.
|
||||
def change_s_blossom_to_subblossom(self, blossom: _Blossom) -> None:
|
||||
"""Change a top-level S-blossom into an S-subblossom."""
|
||||
|
||||
This function is takes time O(q * log(n)),
|
||||
where q is the number of incident edges of vertex "x".
|
||||
This function is called at most once per vertex per stage.
|
||||
This function therefore takes ammortized time O(m * log(n)) per stage.
|
||||
"""
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_S
|
||||
blossom.label = _LABEL_NONE
|
||||
|
||||
# Scan the edges that are incident on "x".
|
||||
edges = self.graph.edges
|
||||
for e in self.graph.adjacent_edges[x]:
|
||||
(p, q, _w) = edges[e]
|
||||
y = p if p != x else q
|
||||
# Unwind lazy delta updates to the S-blossom dual variable.
|
||||
if isinstance(blossom, _NonTrivialBlossom):
|
||||
blossom.dual_var += self.delta_sum_2x
|
||||
|
||||
self.delta3_remove_edge(e)
|
||||
|
||||
by = self.vertex_set_node[y].find()
|
||||
if by.label == _LABEL_S:
|
||||
# This is an edge between "x" and an S-vertex.
|
||||
# Add this edge to "vertex_sedge_queue[x]".
|
||||
# Update delta2 tracking accordingly.
|
||||
self.delta2_add_edge(e, x, bx)
|
||||
|
||||
else:
|
||||
self.delta2_remove_edge(e, y, by)
|
||||
#
|
||||
# General support routines:
|
||||
#
|
||||
|
||||
def reset_blossom_label(self, blossom: _Blossom) -> None:
|
||||
"""Remove blossom label."""
|
||||
|
@ -983,27 +1048,9 @@ class _MatchingContext:
|
|||
assert blossom.label != _LABEL_NONE
|
||||
|
||||
if blossom.label == _LABEL_S:
|
||||
|
||||
# Remove label.
|
||||
blossom.label = _LABEL_NONE
|
||||
|
||||
# Unwind lazy delta updates to S-blossom dual variable.
|
||||
if isinstance(blossom, _NonTrivialBlossom):
|
||||
blossom.dual_var += self.delta_sum_2x
|
||||
|
||||
# Unwind lazy delta updates to S-vertex dual variables.
|
||||
assert blossom.vertex_dual_offset == 0
|
||||
vertex_dual_fixup = -self.delta_sum_2x
|
||||
for x in blossom.vertices():
|
||||
self.vertex_dual_2x[x] += vertex_dual_fixup
|
||||
|
||||
# Adjust delta tracking for S-vertex losing its label.
|
||||
self.remove_vertex_label_s(x, blossom)
|
||||
|
||||
self.remove_blossom_label_s(blossom)
|
||||
elif blossom.label == _LABEL_T:
|
||||
|
||||
self.remove_blossom_label_t(blossom)
|
||||
self.delta2_enable_blossom(blossom)
|
||||
|
||||
def _check_alternating_tree_consistency(self) -> None:
|
||||
"""TODO -- remove this function, only for debugging"""
|
||||
|
@ -1155,25 +1202,28 @@ class _MatchingContext:
|
|||
# Remove blossom labels.
|
||||
# Mark vertices inside former T-blossoms as S-vertices.
|
||||
for sub in subblossoms:
|
||||
if sub.label == _LABEL_S:
|
||||
self.remove_blossom_label_s(sub)
|
||||
elif sub.label == _LABEL_T:
|
||||
if sub.label == _LABEL_T:
|
||||
self.remove_blossom_label_t(sub)
|
||||
self.assign_vertex_label_s(sub)
|
||||
self.assign_blossom_label_s(sub)
|
||||
self.change_s_blossom_to_subblossom(sub)
|
||||
|
||||
# Create the new blossom object.
|
||||
blossom = _NonTrivialBlossom(subblossoms, path.edges)
|
||||
|
||||
# Assign label S to the new blossom and link it to the tree.
|
||||
self.assign_blossom_label_s(blossom)
|
||||
# Assign label S to the new blossom.
|
||||
blossom.label = _LABEL_S
|
||||
|
||||
# Prepare for lazy updating of S-blossom dual variable.
|
||||
blossom.dual_var = -self.delta_sum_2x
|
||||
|
||||
# Link the new blossom to the alternating tree.
|
||||
tree_blossoms = subblossoms[0].tree_blossoms
|
||||
assert tree_blossoms is not None
|
||||
blossom.tree_edge = subblossoms[0].tree_edge
|
||||
blossom.tree_blossoms = tree_blossoms
|
||||
tree_blossoms.add(blossom)
|
||||
|
||||
# Insert into the blossom array.
|
||||
# Add to the list of blossoms.
|
||||
self.nontrivial_blossom.add(blossom)
|
||||
|
||||
# Link the subblossoms to the their new parent.
|
||||
|
@ -1215,6 +1265,38 @@ class _MatchingContext:
|
|||
|
||||
return (nodes, edges)
|
||||
|
||||
def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None:
|
||||
"""Expand the specified unlabeled blossom.
|
||||
|
||||
This function takes total time O(n*log(n)) per stage.
|
||||
"""
|
||||
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_NONE
|
||||
|
||||
# Remove blossom from the delta2 queue.
|
||||
self.delta2_disable_blossom(blossom)
|
||||
|
||||
# Split union-find structure.
|
||||
blossom.vertex_set.split()
|
||||
|
||||
# Prepare to push lazy delta updates down to the sub-blossoms.
|
||||
vertex_dual_offset = blossom.vertex_dual_offset
|
||||
blossom.vertex_dual_offset = 0
|
||||
|
||||
# Convert sub-blossoms into top-level blossoms.
|
||||
for sub in blossom.subblossoms:
|
||||
assert sub.label == _LABEL_NONE
|
||||
sub.parent = None
|
||||
|
||||
assert sub.vertex_dual_offset == 0
|
||||
sub.vertex_dual_offset = vertex_dual_offset
|
||||
|
||||
self.delta2_enable_blossom(sub)
|
||||
|
||||
# Delete the expanded blossom.
|
||||
self.nontrivial_blossom.remove(blossom)
|
||||
|
||||
def expand_t_blossom(self, blossom: _NonTrivialBlossom) -> None:
|
||||
"""Expand the specified T-blossom.
|
||||
|
||||
|
@ -1225,34 +1307,18 @@ class _MatchingContext:
|
|||
assert blossom.label == _LABEL_T
|
||||
assert blossom.delta2_node is None
|
||||
|
||||
# Remove expanded blossom from the delta4 queue.
|
||||
assert blossom.delta4_node is not None
|
||||
self.delta4_queue.delete(blossom.delta4_node)
|
||||
blossom.delta4_node = None
|
||||
|
||||
# Remove blossom from its alternating tree.
|
||||
tree_blossoms = blossom.tree_blossoms
|
||||
assert tree_blossoms is not None
|
||||
tree_blossoms.remove(blossom)
|
||||
|
||||
# Split union-find structure.
|
||||
blossom.vertex_set.split()
|
||||
# Remove label T.
|
||||
self.remove_blossom_label_t(blossom)
|
||||
|
||||
# Prepare to push lazy delta updates down to the sub-blossoms.
|
||||
vertex_dual_fixup = self.delta_sum_2x + blossom.vertex_dual_offset
|
||||
blossom.vertex_dual_offset = 0
|
||||
# Expand the now-unlabeled blossom.
|
||||
self.expand_unlabeled_blossom(blossom)
|
||||
|
||||
# Convert sub-blossoms into top-level blossoms.
|
||||
for sub in blossom.subblossoms:
|
||||
assert sub.label == _LABEL_NONE
|
||||
sub.parent = None
|
||||
|
||||
assert sub.vertex_dual_offset == 0
|
||||
sub.vertex_dual_offset = vertex_dual_fixup
|
||||
|
||||
self.delta2_enable_blossom(sub)
|
||||
|
||||
# The expanding blossom was part of an alternating tree, linked to
|
||||
# The expanded blossom was part of an alternating tree, linked to
|
||||
# a parent node in the tree via one of its subblossoms, and linked to
|
||||
# a child node of the tree via the base vertex.
|
||||
# We must reconstruct this part of the alternating tree, which will
|
||||
|
@ -1296,40 +1362,6 @@ class _MatchingContext:
|
|||
sub.tree_blossoms = tree_blossoms
|
||||
tree_blossoms.add(sub)
|
||||
|
||||
# Delete the expanded blossom.
|
||||
self.nontrivial_blossom.remove(blossom)
|
||||
|
||||
def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None:
|
||||
"""Expand the specified unlabeled blossom.
|
||||
|
||||
This function takes total time O(n*log(n)) per stage.
|
||||
"""
|
||||
|
||||
assert blossom.parent is None
|
||||
assert blossom.label == _LABEL_NONE
|
||||
|
||||
self.delta2_disable_blossom(blossom)
|
||||
|
||||
# Split union-find structure.
|
||||
blossom.vertex_set.split()
|
||||
|
||||
# Prepare to push lazy delta updates down to the sub-blossoms.
|
||||
vertex_dual_offset = blossom.vertex_dual_offset
|
||||
blossom.vertex_dual_offset = 0
|
||||
|
||||
# Convert sub-blossoms into top-level blossoms.
|
||||
for sub in blossom.subblossoms:
|
||||
assert sub.label == _LABEL_NONE
|
||||
sub.parent = None
|
||||
|
||||
assert sub.vertex_dual_offset == 0
|
||||
sub.vertex_dual_offset = vertex_dual_offset
|
||||
|
||||
self.delta2_enable_blossom(sub)
|
||||
|
||||
# Delete the expanded blossom.
|
||||
self.nontrivial_blossom.remove(blossom)
|
||||
|
||||
#
|
||||
# Augmenting:
|
||||
#
|
||||
|
@ -1492,7 +1524,6 @@ class _MatchingContext:
|
|||
# Assign label S to the blossom that contains vertex "x".
|
||||
bx = self.vertex_set_node[x].find()
|
||||
self.assign_blossom_label_s(bx)
|
||||
self.assign_vertex_label_s(bx)
|
||||
|
||||
y = self.vertex_mate[x]
|
||||
if y == -1:
|
||||
|
@ -1712,7 +1743,6 @@ class _MatchingContext:
|
|||
|
||||
# Assign label S.
|
||||
self.assign_blossom_label_s(bx)
|
||||
self.assign_vertex_label_s(bx)
|
||||
|
||||
# Mark blossom as the root of an alternating tree.
|
||||
bx.tree_edge = None
|
||||
|
|
Loading…
Reference in New Issue