1
0
Fork 0

Clean up management of the alternating tree

This commit is contained in:
Joris van Rantwijk 2024-06-30 13:48:11 +02:00
parent b960a85b6c
commit f35a640e43
1 changed files with 80 additions and 66 deletions

View File

@ -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: