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:
"""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
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
steps, plus the pending offset of the top-level blossom that contains
the unlabeled vertex.
@ -630,38 +631,6 @@ class _MatchingContext:
(x, y, w) = self.graph.edges[e]
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:
#
@ -1428,16 +1397,14 @@ class _MatchingContext:
else:
return path
def substage_scan(self) -> Optional[_AlternatingPath]:
"""Scan queued S-vertices to expand the alternating trees.
def substage_scan(self) -> None:
"""Scan queued S-vertices and consider their incident edges.
The scan proceeds until either an augmenting path is found,
or the queue of S-vertices becomes empty.
New blossoms may be created during the scan.
Returns:
Augmenting path if found; otherwise None.
Edges are inserted in delta2 and delta3 tracking.
This function does not yet use the edges to extend the alternating
tree or find blossoms or augmenting paths, even if the edges
are tight. An edge that is already tight may be used later through
a zero-delta step.
"""
edges = self.graph.edges
@ -1461,59 +1428,42 @@ class _MatchingContext:
y = p if p != x else q
# 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.
# 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.
by = self.vertex_set_node[y].find()
if bx is by:
continue
ylabel = by.label
# 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:
if by.label == _LABEL_S:
# Update tracking of least-slack edges between S-blossoms.
# Priority is edge slack plus 2 times the running sum of
# delta steps.
if e not in self.delta3_set:
prio_2x = self.edge_pseudo_slack_2x(e)
if self.graph.integer_weights:
# If all edge weights are integers, the slack of
# any edge between S-vertices is also an integer.
assert slack % 2 == 0
slack_1x = slack // 2
assert prio_2x % 2 == 0
prio = prio_2x // 2
else:
slack_1x = slack / 2
prio = slack_1x + self.delta_sum_2x
prio = prio_2x / 2
self.delta3_set.add(e)
self.delta3_queue.insert(prio, e)
if ylabel != _LABEL_S:
else:
# Update tracking of least-slack edges from vertex "y" to
# any S-vertex. We do this for T-vertices and unlabeled
# vertices. Edges which already have zero slack are still
# tracked.
self.lset_add_vertex_edge(y, by, e)
# No further S vertices to scan, and no augmenting path found.
return None
#
# Delta steps:
#
@ -1640,11 +1590,8 @@ class _MatchingContext:
augmenting_path = None
while True:
# Expand alternating trees.
# End the stage if an augmenting path is found.
augmenting_path = self.substage_scan()
if augmenting_path is not None:
break
# Consider the incident edges of newly labeled S-vertices.
self.substage_scan()
# Calculate delta step in the dual LPP problem.
(delta_type, delta_2x, delta_edge, delta_blossom