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:
parent
3a77749425
commit
04b6908449
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue