Expand zero-dual blossom before assigning label T
This commit is contained in:
parent
8d69a3316c
commit
caac6825a6
|
@ -1071,83 +1071,28 @@ class _MatchingContext:
|
||||||
# Delete the expanded blossom.
|
# Delete the expanded blossom.
|
||||||
self.nontrivial_blossom.remove(blossom)
|
self.nontrivial_blossom.remove(blossom)
|
||||||
|
|
||||||
def expand_blossom_rec(
|
def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None:
|
||||||
self,
|
"""Expand the specified unlabeled blossom.
|
||||||
blossom: _NonTrivialBlossom,
|
|
||||||
stack: list[_NonTrivialBlossom]
|
|
||||||
) -> None:
|
|
||||||
"""Expand the specified blossom and recursively expand any
|
|
||||||
sub-blossoms that have dual variable zero.
|
|
||||||
|
|
||||||
Use the stack object instead of making direct recursive calls.
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert blossom.parent is None
|
|
||||||
|
|
||||||
# Examine sub-blossoms.
|
|
||||||
for sub in blossom.subblossoms:
|
|
||||||
|
|
||||||
# Mark the sub-blossom as a top-level blossom.
|
|
||||||
sub.parent = None
|
|
||||||
|
|
||||||
if isinstance(sub, _NonTrivialBlossom):
|
|
||||||
# Non-trivial sub-blossom.
|
|
||||||
# If its dual variable is zero, expand it recursively.
|
|
||||||
if sub.dual_var == 0:
|
|
||||||
stack.append(sub)
|
|
||||||
else:
|
|
||||||
# This sub-blossom will not be expanded;
|
|
||||||
# it now becomes top-level. Update its vertices
|
|
||||||
# to point to this sub-blossom.
|
|
||||||
for x in sub.vertices():
|
|
||||||
self.vertex_top_blossom[x] = sub
|
|
||||||
else:
|
|
||||||
# Trivial sub-blossom. Mark it as top-level vertex.
|
|
||||||
self.vertex_top_blossom[sub.base_vertex] = sub
|
|
||||||
|
|
||||||
# Deletion of the expanded blossom will be handled in
|
|
||||||
# the function "expand_zero_dual_blossoms()".
|
|
||||||
|
|
||||||
def expand_zero_dual_blossoms(self) -> None:
|
|
||||||
"""Expand all blossoms with zero dual variable (recursively).
|
|
||||||
|
|
||||||
Note that this function runs at the end of a stage.
|
|
||||||
Blossoms are not labeled. Least-slack edges are not tracked.
|
|
||||||
|
|
||||||
This function takes time O(n).
|
This function takes time O(n).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Use an explicit stack to avoid deep recursion.
|
assert blossom.parent is None
|
||||||
# The stack contains a list of blossoms to be expanded.
|
assert blossom.label == _LABEL_NONE
|
||||||
stack: list[_NonTrivialBlossom] = []
|
|
||||||
|
|
||||||
# Find top-level blossoms with zero slack.
|
# Convert sub-blossoms into top-level blossoms.
|
||||||
for blossom in self.nontrivial_blossom:
|
for sub in blossom.subblossoms:
|
||||||
if blossom.parent is None:
|
assert sub.label == _LABEL_NONE
|
||||||
# We typically expand only S-blossoms that were created after
|
sub.parent = None
|
||||||
# the most recent delta step. Those blossoms have _exactly_
|
|
||||||
# zero dual. So this comparison is reliable, even in case
|
|
||||||
# of floating point edge weights.
|
|
||||||
if blossom.dual_var == 0:
|
|
||||||
stack.append(blossom)
|
|
||||||
|
|
||||||
# Skip the rest of this function if there are no blossoms to delete.
|
if isinstance(sub, _NonTrivialBlossom):
|
||||||
if not stack:
|
for x in sub.vertices():
|
||||||
return
|
self.vertex_top_blossom[x] = sub
|
||||||
|
else:
|
||||||
|
self.vertex_top_blossom[sub.base_vertex] = sub
|
||||||
|
|
||||||
# Expand blossoms.
|
# Delete the expanded blossom.
|
||||||
while stack:
|
self.nontrivial_blossom.remove(blossom)
|
||||||
blossom = stack.pop()
|
|
||||||
self.expand_blossom_rec(blossom, stack)
|
|
||||||
|
|
||||||
# Mark the blossom for deletion.
|
|
||||||
blossom.marker = True
|
|
||||||
|
|
||||||
# Delete the expanded blossoms.
|
|
||||||
# We do this in one pass over the array to ensure O(n) time.
|
|
||||||
self.nontrivial_blossom = [blossom
|
|
||||||
for blossom in self.nontrivial_blossom
|
|
||||||
if not blossom.marker]
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Augmenting:
|
# Augmenting:
|
||||||
|
@ -1350,8 +1295,14 @@ class _MatchingContext:
|
||||||
"""
|
"""
|
||||||
assert self.vertex_top_blossom[x].label == _LABEL_S
|
assert self.vertex_top_blossom[x].label == _LABEL_S
|
||||||
|
|
||||||
# Assign label T to the unlabeled blossom.
|
|
||||||
by = self.vertex_top_blossom[y]
|
by = self.vertex_top_blossom[y]
|
||||||
|
|
||||||
|
# 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_top_blossom[y]
|
||||||
|
|
||||||
|
# Assign label T to the unlabeled blossom.
|
||||||
assert by.label == _LABEL_NONE
|
assert by.label == _LABEL_NONE
|
||||||
by.label = _LABEL_T
|
by.label = _LABEL_T
|
||||||
by.tree_edge = (x, y)
|
by.tree_edge = (x, y)
|
||||||
|
@ -1646,11 +1597,6 @@ class _MatchingContext:
|
||||||
if augmenting_path is not None:
|
if augmenting_path is not None:
|
||||||
self.augment_matching(augmenting_path)
|
self.augment_matching(augmenting_path)
|
||||||
|
|
||||||
# Expand all blossoms with dual variable zero.
|
|
||||||
# These are typically S-blossoms, since T-blossoms normally
|
|
||||||
# get expanded as soon as their dual variable hits zero.
|
|
||||||
self.expand_zero_dual_blossoms()
|
|
||||||
|
|
||||||
# Remove all labels, clear queue.
|
# Remove all labels, clear queue.
|
||||||
self.reset_stage()
|
self.reset_stage()
|
||||||
|
|
||||||
|
|
|
@ -191,6 +191,32 @@ class TestMaximumWeightMatching(unittest.TestCase):
|
||||||
edges = [(0,2,4), (0,3,4), (0,4,1), (1,2,8), (1,5,3), (2,3,9), (3,4,7), (4,5,2)]
|
edges = [(0,2,4), (0,3,4), (0,4,1), (1,2,8), (1,5,3), (2,3,9), (3,4,7), (4,5,2)]
|
||||||
self.assertEqual(mwm(edges), [(1,2), (3,4)])
|
self.assertEqual(mwm(edges), [(1,2), (3,4)])
|
||||||
|
|
||||||
|
def test46_expand_unlabeled_blossom(self):
|
||||||
|
"""expand blossom before assigning label T"""
|
||||||
|
#
|
||||||
|
# 5--[2]--3--[4]
|
||||||
|
# / |
|
||||||
|
# [0]--5--[1] 5
|
||||||
|
# \ |
|
||||||
|
# 5--[3]--3--[5]
|
||||||
|
#
|
||||||
|
self.assertEqual(
|
||||||
|
mwm([(0,1,5), (1,2,5), (1,3,5), (2,3,5), (2,4,3), (3,5,3)]),
|
||||||
|
[(0,1), (2,4), (3,5)])
|
||||||
|
|
||||||
|
def test47_expand_unlabeled_outer(self):
|
||||||
|
"""expand outer blossom before assigning label T"""
|
||||||
|
#
|
||||||
|
# [3]--10--[1]--15--[2]--12--[5]
|
||||||
|
# _/ \_ | |
|
||||||
|
# 11 16_ 8 15
|
||||||
|
# / \ | |
|
||||||
|
# [4] [6]---7--[7]
|
||||||
|
#
|
||||||
|
self.assertEqual(
|
||||||
|
mwm([(1,2,15), (1,3,10), (1,4,11), (1,6,17), (2,5,12), (2,6,8), (5,7,15), (6,7,7)]),
|
||||||
|
[(1,4), (2,6), (5,7)])
|
||||||
|
|
||||||
def test_fail_bad_input(self):
|
def test_fail_bad_input(self):
|
||||||
"""bad input values"""
|
"""bad input values"""
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
|
Loading…
Reference in New Issue