1
0
Fork 0

Do not check edge slack during scan

Tight edges are not used immediately during the scan.
Just like other edges, tight edges are tracked in priority queues
and are used later through a zero-delta step.

This simplifies slack calculations.
This commit is contained in:
Joris van Rantwijk 2024-06-02 00:31:40 +02:00
parent 3a77749425
commit 04b6908449
1 changed files with 27 additions and 80 deletions

View File

@ -616,13 +616,14 @@ class _MatchingContext:
def edge_pseudo_slack_2x(self, e: int) -> float: def edge_pseudo_slack_2x(self, e: int) -> float:
"""Return 2 times the pseudo-slack of the specified edge. """Return 2 times the pseudo-slack of the specified edge.
The pseudo-slack of an edge ignores pending changes to vertex duals. The pseudo-slack of an edge is related to its true slack, but
distorted in a way that makes it invariant under delta steps.
For an edge between two S-vertices (in different top-level blossoms), If the edge connects two S-vertices in different top-level blossoms,
the true slack is the pseudo-slack minus 2 times the running sum the true slack is the pseudo-slack minus 2 times the running sum
of delta steps. of delta steps.
For an edge between an S-vertex and unlabeled vertex, If the edge connects an S-vertex to an unlabeled vertex,
the true slack is the pseudo-slack minus the running sum of delta the true slack is the pseudo-slack minus the running sum of delta
steps, plus the pending offset of the top-level blossom that contains steps, plus the pending offset of the top-level blossom that contains
the unlabeled vertex. the unlabeled vertex.
@ -630,38 +631,6 @@ class _MatchingContext:
(x, y, w) = self.graph.edges[e] (x, y, w) = self.graph.edges[e]
return self.vertex_dual_2x[x] + self.vertex_dual_2x[y] - 2 * w return self.vertex_dual_2x[x] + self.vertex_dual_2x[y] - 2 * w
def edge_slack_2x(
self,
x: int,
y: int,
bx: _Blossom,
by: _Blossom,
w: float
) -> float:
"""Calculate edge slack.
Return 2 times the slack of an edge from vertex "x" in blossom "bx"
to vertex "y" in blossom "by" with weight "w".
The two vertices must be in different top-level blossoms.
Multiplication by 2 ensures that the return value is an integer
if all edge weights are integers.
"""
assert bx is not by
dual_2x = self.vertex_dual_2x[x] + self.vertex_dual_2x[y]
if bx.label == _LABEL_S:
dual_2x -= self.delta_sum_2x
if by.label == _LABEL_S:
dual_2x -= self.delta_sum_2x
if bx.label == _LABEL_T:
dual_2x += self.delta_sum_2x
if by.label == _LABEL_T:
dual_2x += self.delta_sum_2x
dual_2x += bx.vertex_dual_offset
dual_2x += by.vertex_dual_offset
return dual_2x - 2 * w
# #
# Least-slack edge tracking: # Least-slack edge tracking:
# #
@ -1428,16 +1397,14 @@ class _MatchingContext:
else: else:
return path return path
def substage_scan(self) -> Optional[_AlternatingPath]: def substage_scan(self) -> None:
"""Scan queued S-vertices to expand the alternating trees. """Scan queued S-vertices and consider their incident edges.
The scan proceeds until either an augmenting path is found, Edges are inserted in delta2 and delta3 tracking.
or the queue of S-vertices becomes empty. This function does not yet use the edges to extend the alternating
tree or find blossoms or augmenting paths, even if the edges
New blossoms may be created during the scan. are tight. An edge that is already tight may be used later through
a zero-delta step.
Returns:
Augmenting path if found; otherwise None.
""" """
edges = self.graph.edges edges = self.graph.edges
@ -1461,59 +1428,42 @@ class _MatchingContext:
y = p if p != x else q y = p if p != x else q
# Consider the edge between vertices "x" and "y". # Consider the edge between vertices "x" and "y".
# Update delta2 or delta3 tracking accordingly.
#
# We don't actually use the edge right now to extend
# the alternating tree or create a blossom or alternating path.
# If appropriate, insert this edge into delta2 or delta3
# tracking.
# Insert this edge into delta2 or delta3 tracking
# Try to pull this edge into an alternating tree. # Try to pull this edge into an alternating tree.
# Note: blossom index of vertex "x" may change during
# this loop, so we need to refresh it here.
bx = self.vertex_set_node[x].find()
by = self.vertex_set_node[y].find()
# Ignore edges that are internal to a blossom. # Ignore edges that are internal to a blossom.
by = self.vertex_set_node[y].find()
if bx is by: if bx is by:
continue continue
ylabel = by.label if by.label == _LABEL_S:
# Check whether this edge is tight (has zero slack).
# Only tight edges may be part of an alternating tree.
slack = self.edge_slack_2x(x, y, bx, by, w)
if slack <= 0:
if ylabel == _LABEL_NONE:
# Assign label T to the blossom that contains "y".
self.extend_tree_t(x, y)
elif ylabel == _LABEL_S:
# This edge connects two S-blossoms. Use it to find
# either a new blossom or an augmenting path.
alternating_path = self.add_s_to_s_edge(x, y)
if alternating_path is not None:
return alternating_path
elif ylabel == _LABEL_S:
# Update tracking of least-slack edges between S-blossoms. # Update tracking of least-slack edges between S-blossoms.
# Priority is edge slack plus 2 times the running sum of # Priority is edge slack plus 2 times the running sum of
# delta steps. # delta steps.
if e not in self.delta3_set: if e not in self.delta3_set:
prio_2x = self.edge_pseudo_slack_2x(e)
if self.graph.integer_weights: if self.graph.integer_weights:
# If all edge weights are integers, the slack of # If all edge weights are integers, the slack of
# any edge between S-vertices is also an integer. # any edge between S-vertices is also an integer.
assert slack % 2 == 0 assert prio_2x % 2 == 0
slack_1x = slack // 2 prio = prio_2x // 2
else: else:
slack_1x = slack / 2 prio = prio_2x / 2
prio = slack_1x + self.delta_sum_2x
self.delta3_set.add(e) self.delta3_set.add(e)
self.delta3_queue.insert(prio, e) self.delta3_queue.insert(prio, e)
else:
if ylabel != _LABEL_S:
# Update tracking of least-slack edges from vertex "y" to # Update tracking of least-slack edges from vertex "y" to
# any S-vertex. We do this for T-vertices and unlabeled # any S-vertex. We do this for T-vertices and unlabeled
# vertices. Edges which already have zero slack are still # vertices. Edges which already have zero slack are still
# tracked. # tracked.
self.lset_add_vertex_edge(y, by, e) self.lset_add_vertex_edge(y, by, e)
# No further S vertices to scan, and no augmenting path found.
return None
# #
# Delta steps: # Delta steps:
# #
@ -1640,11 +1590,8 @@ class _MatchingContext:
augmenting_path = None augmenting_path = None
while True: while True:
# Expand alternating trees. # Consider the incident edges of newly labeled S-vertices.
# End the stage if an augmenting path is found. self.substage_scan()
augmenting_path = self.substage_scan()
if augmenting_path is not None:
break
# Calculate delta step in the dual LPP problem. # Calculate delta step in the dual LPP problem.
(delta_type, delta_2x, delta_edge, delta_blossom (delta_type, delta_2x, delta_edge, delta_blossom