Clean up least-slack edge tracking
This commit is contained in:
parent
1a98624f2b
commit
f8c6b99842
|
@ -78,7 +78,7 @@ def maximum_weight_matching(
|
||||||
# of matched edges by 1.
|
# of matched edges by 1.
|
||||||
#
|
#
|
||||||
# This loop runs through at most (n/2 + 1) iterations.
|
# This loop runs through at most (n/2 + 1) iterations.
|
||||||
# Each iteration takes time O(n**2).
|
# Each iteration takes time O((n + m) * log(n)).
|
||||||
while ctx.run_stage():
|
while ctx.run_stage():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -554,6 +554,8 @@ class _MatchingContext:
|
||||||
|
|
||||||
# "vertex_set_node[x]" represents the vertex "x" inside the
|
# "vertex_set_node[x]" represents the vertex "x" inside the
|
||||||
# union-find datastructure of its top-level blossom.
|
# union-find datastructure of its top-level blossom.
|
||||||
|
#
|
||||||
|
# Initially, each vertex belongs to its owwn trivial top-level blossom.
|
||||||
self.vertex_set_node = [b.vertex_set.insert(i, math.inf)
|
self.vertex_set_node = [b.vertex_set.insert(i, math.inf)
|
||||||
for (i, b) in enumerate(self.trivial_blossom)]
|
for (i, b) in enumerate(self.trivial_blossom)]
|
||||||
|
|
||||||
|
@ -587,7 +589,7 @@ class _MatchingContext:
|
||||||
self.delta_sum_2x: float = 0
|
self.delta_sum_2x: float = 0
|
||||||
|
|
||||||
# Queue containing unlabeled top-level blossoms that have an edge to
|
# Queue containing unlabeled top-level blossoms that have an edge to
|
||||||
# an S-blossom. The priority of a blossom is 2 times the least slack
|
# an S-blossom. The priority of a blossom is 2 times its least slack
|
||||||
# to an S blossom, plus 2 times the running sum of delta steps.
|
# to an S blossom, plus 2 times the running sum of delta steps.
|
||||||
self.delta2_queue: PriorityQueue[_Blossom] = PriorityQueue()
|
self.delta2_queue: PriorityQueue[_Blossom] = PriorityQueue()
|
||||||
|
|
||||||
|
@ -623,11 +625,15 @@ class _MatchingContext:
|
||||||
del blossom.vertex_set
|
del blossom.vertex_set
|
||||||
blossom.tree_blossoms = None
|
blossom.tree_blossoms = None
|
||||||
|
|
||||||
|
#
|
||||||
|
# Least-slack edge tracking:
|
||||||
|
#
|
||||||
|
|
||||||
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 is related to its true slack, but
|
The pseudo-slack of an edge is related to its true slack, but
|
||||||
distorted in a way that makes it invariant under delta steps.
|
adjusted in a way that makes it invariant under delta steps.
|
||||||
|
|
||||||
If the edge connects 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
|
||||||
|
@ -641,47 +647,33 @@ 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 delta2_add_edge(self, e: int, y: int, by: _Blossom) -> None:
|
||||||
# Least-slack edge tracking:
|
"""Add edge "e" for delta2 tracking.
|
||||||
#
|
|
||||||
# To calculate delta steps, the matching algorithm needs to find
|
|
||||||
# - the least-slack edge between any S-vertex and an unlabeled vertex;
|
|
||||||
# - the least-slack edge between any pair of top-level S-blossoms.
|
|
||||||
#
|
|
||||||
# For each unlabeled vertex and each T-vertex, we keep track of the
|
|
||||||
# least-slack edge to any S-vertex. Tracking for unlabeled vertices
|
|
||||||
# serves to provide the least-slack edge for the delta step.
|
|
||||||
# Tracking for T-vertices is done because such vertices can turn into
|
|
||||||
# unlabeled vertices if they are part of a T-blossom that gets expanded.
|
|
||||||
#
|
|
||||||
# Note: For a given vertex or blossom, the identity of the least-slack
|
|
||||||
# edge to any S-blossom remains unchanged during a delta step.
|
|
||||||
# Although the delta step changes edge slacks, it changes the slack
|
|
||||||
# of every edge to an S-vertex by the same amount. Therefore the edge
|
|
||||||
# that had least slack before the delta step, will still have least slack
|
|
||||||
# after the delta step.
|
|
||||||
#
|
|
||||||
|
|
||||||
# TODO -- rename function, maybe refactor
|
Edge "e" connects an S-vertex to a T-vertex or unlabeled vertex "y".
|
||||||
def lset_add_vertex_edge(self, y: int, by: _Blossom, e: int) -> None:
|
|
||||||
"""Add edge "e" from an S-vertex to unlabeled vertex or T-vertex "y".
|
|
||||||
|
|
||||||
This function takes time O(log(n)).
|
This function takes time O(log(n)).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prio = self.edge_pseudo_slack_2x(e)
|
prio = self.edge_pseudo_slack_2x(e)
|
||||||
|
|
||||||
improved = (self.vertex_sedge_queue[y].empty()
|
improved = (self.vertex_sedge_queue[y].empty()
|
||||||
or (self.vertex_sedge_queue[y].find_min().prio > prio))
|
or (self.vertex_sedge_queue[y].find_min().prio > prio))
|
||||||
|
|
||||||
|
# Insert edge in the S-edge queue of vertex "y".
|
||||||
assert self.vertex_sedge_node[e] is None
|
assert self.vertex_sedge_node[e] is None
|
||||||
self.vertex_sedge_node[e] = self.vertex_sedge_queue[y].insert(prio, e)
|
self.vertex_sedge_node[e] = self.vertex_sedge_queue[y].insert(prio, e)
|
||||||
|
|
||||||
|
# Continue if the new edge becomes the least-slack S-edge for "y".
|
||||||
if not improved:
|
if not improved:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Update the priority of "y" in its UnionFindQueue.
|
||||||
prev_min = by.vertex_set.min_prio()
|
prev_min = by.vertex_set.min_prio()
|
||||||
self.vertex_set_node[y].set_prio(prio)
|
self.vertex_set_node[y].set_prio(prio)
|
||||||
|
|
||||||
|
# If the blossom is unlabeled and the new edge becomes its least-slack
|
||||||
|
# S-edge, insert or update the blossom in the global delta2 queue.
|
||||||
if (by.label == _LABEL_NONE) and (prio < prev_min):
|
if (by.label == _LABEL_NONE) and (prio < prev_min):
|
||||||
prio += by.vertex_dual_offset
|
prio += by.vertex_dual_offset
|
||||||
if by.delta2_node is None:
|
if by.delta2_node is None:
|
||||||
|
@ -689,20 +681,99 @@ class _MatchingContext:
|
||||||
elif prio < by.delta2_node.prio:
|
elif prio < by.delta2_node.prio:
|
||||||
self.delta2_queue.decrease_prio(by.delta2_node, prio)
|
self.delta2_queue.decrease_prio(by.delta2_node, prio)
|
||||||
|
|
||||||
# TODO -- rename function, maybe refactor
|
def delta2_remove_edge(self, e: int, y: int, by: _Blossom) -> None:
|
||||||
def lset_get_best_vertex_edge(self) -> tuple[int, float]:
|
"""Remove edge "e" from delta2 tracking.
|
||||||
"""Return the index and slack of the least-slack edge between
|
|
||||||
any S-vertex and unlabeled vertex.
|
This function is called if an S-vertex becomes unlabeled,
|
||||||
|
and edge "e" connects that vertex to vertex "y" which is a T-vertex
|
||||||
|
or unlabeled vertex.
|
||||||
|
|
||||||
|
This function takes time O(log(n)).
|
||||||
|
"""
|
||||||
|
vertex_sedge_node = self.vertex_sedge_node[e]
|
||||||
|
if vertex_sedge_node is not None:
|
||||||
|
# Delete edge from the S-edge queue of vertex "y".
|
||||||
|
vertex_sedge_queue = self.vertex_sedge_queue[y]
|
||||||
|
vertex_sedge_queue.delete(vertex_sedge_node)
|
||||||
|
self.vertex_sedge_node[e] = None
|
||||||
|
|
||||||
|
if vertex_sedge_queue.empty():
|
||||||
|
prio = math.inf
|
||||||
|
else:
|
||||||
|
prio = vertex_sedge_queue.find_min().prio
|
||||||
|
|
||||||
|
# If necessary, update the priority of "y" in its UnionFindQueue.
|
||||||
|
if prio > self.vertex_set_node[y].prio:
|
||||||
|
self.vertex_set_node[y].set_prio(prio)
|
||||||
|
if by.label == _LABEL_NONE:
|
||||||
|
# Update or delete the blossom in the global delta2 queue.
|
||||||
|
assert by.delta2_node is not None
|
||||||
|
prio = by.vertex_set.min_prio()
|
||||||
|
if prio < math.inf:
|
||||||
|
prio += by.vertex_dual_offset
|
||||||
|
if prio > by.delta2_node.prio:
|
||||||
|
self.delta2_queue.increase_prio(
|
||||||
|
by.delta2_node, prio)
|
||||||
|
else:
|
||||||
|
self.delta2_queue.delete(by.delta2_node)
|
||||||
|
by.delta2_node = None
|
||||||
|
|
||||||
|
def delta2_enable_blossom(self, blossom: _Blossom) -> None:
|
||||||
|
"""Enable delta2 tracking for "blossom".
|
||||||
|
|
||||||
|
This function is called when a blossom becomes an unlabeled top-level
|
||||||
|
blossom. If the blossom has at least one edge to an S-vertex,
|
||||||
|
the blossom will be inserted in the global delta2 queue.
|
||||||
|
|
||||||
|
This function takes time O(log(n)).
|
||||||
|
"""
|
||||||
|
assert blossom.delta2_node is None
|
||||||
|
prio = blossom.vertex_set.min_prio()
|
||||||
|
if prio < math.inf:
|
||||||
|
prio += blossom.vertex_dual_offset
|
||||||
|
blossom.delta2_node = self.delta2_queue.insert(prio, blossom)
|
||||||
|
|
||||||
|
def delta2_disable_blossom(self, blossom: _Blossom) -> None:
|
||||||
|
"""Disable delta2 tracking for "blossom".
|
||||||
|
|
||||||
|
The blossom will be removed from the global delta2 queue.
|
||||||
|
This function is called when a blossom stops being an unlabeled
|
||||||
|
top-level blossom.
|
||||||
|
|
||||||
|
This function takes time O(log(n)).
|
||||||
|
"""
|
||||||
|
if blossom.delta2_node is not None:
|
||||||
|
self.delta2_queue.delete(blossom.delta2_node)
|
||||||
|
blossom.delta2_node = None
|
||||||
|
|
||||||
|
def delta2_clear_vertex(self, x: int) -> None:
|
||||||
|
"""Clear delta2 tracking for vertex "x".
|
||||||
|
|
||||||
|
This function is called when "x" becomes an S-vertex.
|
||||||
|
It is assumed that the blossom containing "x" has already been
|
||||||
|
disabled for delta2 tracking.
|
||||||
|
|
||||||
|
This function takes time O(k * log(n)),
|
||||||
|
where "k" is the number of edges incident on "x".
|
||||||
|
"""
|
||||||
|
self.vertex_sedge_queue[x].clear()
|
||||||
|
for e in self.graph.adjacent_edges[x]:
|
||||||
|
self.vertex_sedge_node[e] = None
|
||||||
|
self.vertex_set_node[x].set_prio(math.inf)
|
||||||
|
|
||||||
|
def delta2_get_min_edge(self) -> tuple[int, float]:
|
||||||
|
"""Find the least-slack edge between any S-vertex and any unlabeled
|
||||||
|
vertex.
|
||||||
|
|
||||||
This function takes time O(log(n)).
|
This function takes time O(log(n)).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple (edge_index, slack_2x) if there is a least-slack edge,
|
Tuple (edge_index, slack_2x) if there is an S-to-unlabeled edge,
|
||||||
or (-1, 0) if there is no suitable edge.
|
or (-1, Inf) if there is no such edge.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.delta2_queue.empty():
|
if self.delta2_queue.empty():
|
||||||
return (-1, 0)
|
return (-1, math.inf)
|
||||||
|
|
||||||
delta2_node = self.delta2_queue.find_min()
|
delta2_node = self.delta2_queue.find_min()
|
||||||
blossom = delta2_node.data
|
blossom = delta2_node.data
|
||||||
|
@ -716,6 +787,74 @@ class _MatchingContext:
|
||||||
|
|
||||||
return (e, slack_2x)
|
return (e, slack_2x)
|
||||||
|
|
||||||
|
def delta3_add_edge(self, e: int) -> None:
|
||||||
|
"""Add edge "e" for delta3 tracking.
|
||||||
|
|
||||||
|
This function is called if a vertex becomes an S-vertex and edge "e"
|
||||||
|
connects it to an S-vertex in a different top-level blossom.
|
||||||
|
|
||||||
|
This function takes time O(log(n)).
|
||||||
|
"""
|
||||||
|
# The edge may already be in the delta3 queue, if it was previously
|
||||||
|
# discovered in the opposite direction.
|
||||||
|
if self.delta3_node[e] is None:
|
||||||
|
# Priority is edge slack plus 2 times the running sum of
|
||||||
|
# delta steps.
|
||||||
|
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 prio_2x % 2 == 0
|
||||||
|
prio = prio_2x // 2
|
||||||
|
else:
|
||||||
|
prio = prio_2x / 2
|
||||||
|
self.delta3_node[e] = self.delta3_queue.insert(prio, e)
|
||||||
|
|
||||||
|
def delta3_remove_edge(self, e: int) -> None:
|
||||||
|
"""Remove edge "e" from delta3 tracking.
|
||||||
|
|
||||||
|
This function is called if a former S-vertex becomes unlabeled,
|
||||||
|
and edge "e" connects it to another S-vertex.
|
||||||
|
|
||||||
|
This function takes time O(log(n)).
|
||||||
|
"""
|
||||||
|
delta3_node = self.delta3_node[e]
|
||||||
|
if delta3_node is not None:
|
||||||
|
self.delta3_queue.delete(delta3_node)
|
||||||
|
self.delta3_node[e] = None
|
||||||
|
|
||||||
|
def delta3_get_min_edge(self) -> tuple[int, float]:
|
||||||
|
"""Find the least-slack edge between any pair of S-vertices in
|
||||||
|
different top-level blossoms.
|
||||||
|
|
||||||
|
This function takes time O(1 + k * log(n)),
|
||||||
|
where "k" is the number of intra-blossom edges removed from the queue.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple (edge_index, slack) if there is an S-to-S edge,
|
||||||
|
or (-1, Inf) if there is no suitable edge.
|
||||||
|
"""
|
||||||
|
while not self.delta3_queue.empty():
|
||||||
|
delta3_node = self.delta3_queue.find_min()
|
||||||
|
e = delta3_node.data
|
||||||
|
(x, y, _w) = self.graph.edges[e]
|
||||||
|
bx = self.vertex_set_node[x].find()
|
||||||
|
by = self.vertex_set_node[y].find()
|
||||||
|
assert (bx.label == _LABEL_S) and (by.label == _LABEL_S)
|
||||||
|
if bx is not by:
|
||||||
|
slack = delta3_node.prio - self.delta_sum_2x
|
||||||
|
return (e, slack)
|
||||||
|
|
||||||
|
# Reject edges between vertices within the same top-level blossom.
|
||||||
|
# Although intra-blossom edges are never inserted into the queue,
|
||||||
|
# existing edges in the queue may become intra-blossom when
|
||||||
|
# a new blossom is formed.
|
||||||
|
self.delta3_queue.delete(delta3_node)
|
||||||
|
self.delta3_node[e] = None
|
||||||
|
|
||||||
|
# If the queue is empty, no suitable edge exists.
|
||||||
|
return (-1, math.inf)
|
||||||
|
|
||||||
#
|
#
|
||||||
# General support routines:
|
# General support routines:
|
||||||
#
|
#
|
||||||
|
@ -734,10 +873,7 @@ class _MatchingContext:
|
||||||
assert blossom.label == _LABEL_NONE
|
assert blossom.label == _LABEL_NONE
|
||||||
blossom.label = _LABEL_S
|
blossom.label = _LABEL_S
|
||||||
|
|
||||||
# Delete blossom from delta2 queue.
|
self.delta2_disable_blossom(blossom)
|
||||||
if blossom.delta2_node is not None:
|
|
||||||
self.delta2_queue.delete(blossom.delta2_node)
|
|
||||||
blossom.delta2_node = None
|
|
||||||
|
|
||||||
# Prepare for lazy updating of S-blossom dual variable.
|
# Prepare for lazy updating of S-blossom dual variable.
|
||||||
if isinstance(blossom, _NonTrivialBlossom):
|
if isinstance(blossom, _NonTrivialBlossom):
|
||||||
|
@ -765,14 +901,7 @@ class _MatchingContext:
|
||||||
blossom.vertex_dual_offset = 0
|
blossom.vertex_dual_offset = 0
|
||||||
for x in vertices:
|
for x in vertices:
|
||||||
self.vertex_dual_2x[x] += vertex_dual_fixup
|
self.vertex_dual_2x[x] += vertex_dual_fixup
|
||||||
|
self.delta2_clear_vertex(x)
|
||||||
# Clean up tracking of edges from vertex "x" to S-vertices.
|
|
||||||
# We maintain that tracking only for unlabeled vertices and
|
|
||||||
# T-vertices.
|
|
||||||
self.vertex_sedge_queue[x].clear()
|
|
||||||
for e in self.graph.adjacent_edges[x]:
|
|
||||||
self.vertex_sedge_node[e] = None
|
|
||||||
self.vertex_set_node[x].set_prio(math.inf)
|
|
||||||
|
|
||||||
def assign_blossom_label_t(self, blossom: _Blossom) -> None:
|
def assign_blossom_label_t(self, blossom: _Blossom) -> None:
|
||||||
"""Assign label T to an unlabeled top-level blossom."""
|
"""Assign label T to an unlabeled top-level blossom."""
|
||||||
|
@ -781,10 +910,7 @@ class _MatchingContext:
|
||||||
assert blossom.label == _LABEL_NONE
|
assert blossom.label == _LABEL_NONE
|
||||||
blossom.label = _LABEL_T
|
blossom.label = _LABEL_T
|
||||||
|
|
||||||
# Delete blossom from delta2 queue.
|
self.delta2_disable_blossom(blossom)
|
||||||
if blossom.delta2_node is not None:
|
|
||||||
self.delta2_queue.delete(blossom.delta2_node)
|
|
||||||
blossom.delta2_node = None
|
|
||||||
|
|
||||||
if isinstance(blossom, _NonTrivialBlossom):
|
if isinstance(blossom, _NonTrivialBlossom):
|
||||||
|
|
||||||
|
@ -838,47 +964,17 @@ class _MatchingContext:
|
||||||
(p, q, _w) = edges[e]
|
(p, q, _w) = edges[e]
|
||||||
y = p if p != x else q
|
y = p if p != x else q
|
||||||
|
|
||||||
# If this edge was in the delta3_queue, remove it since
|
self.delta3_remove_edge(e)
|
||||||
# this is no longer an edge between S-vertices.
|
|
||||||
delta3_node = self.delta3_node[e]
|
|
||||||
if delta3_node is not None:
|
|
||||||
self.delta3_queue.delete(delta3_node)
|
|
||||||
self.delta3_node[e] = None
|
|
||||||
|
|
||||||
by = self.vertex_set_node[y].find()
|
by = self.vertex_set_node[y].find()
|
||||||
if by.label == _LABEL_S:
|
if by.label == _LABEL_S:
|
||||||
# This is an edge between "x" and an S-vertex.
|
# This is an edge between "x" and an S-vertex.
|
||||||
# Add this edge to "vertex_sedge_queue[x]".
|
# Add this edge to "vertex_sedge_queue[x]".
|
||||||
# Update delta2 tracking accordingly.
|
# Update delta2 tracking accordingly.
|
||||||
self.lset_add_vertex_edge(x, bx, e)
|
self.delta2_add_edge(e, x, bx)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# This is no longer an edge between "y" and an S-vertex.
|
self.delta2_remove_edge(e, y, by)
|
||||||
# Remove this edge from "vertex_sedge_queue[y]".
|
|
||||||
# Update delta2 tracking accordingly.
|
|
||||||
# TODO -- untangle this mess
|
|
||||||
vertex_sedge_node = self.vertex_sedge_node[e]
|
|
||||||
if vertex_sedge_node is not None:
|
|
||||||
vertex_sedge_queue = self.vertex_sedge_queue[y]
|
|
||||||
vertex_sedge_queue.delete(vertex_sedge_node)
|
|
||||||
self.vertex_sedge_node[e] = None
|
|
||||||
if vertex_sedge_queue.empty():
|
|
||||||
prio = math.inf
|
|
||||||
else:
|
|
||||||
prio = vertex_sedge_queue.find_min().prio
|
|
||||||
if prio > self.vertex_set_node[y].prio:
|
|
||||||
self.vertex_set_node[y].set_prio(prio)
|
|
||||||
if by.label == _LABEL_NONE:
|
|
||||||
assert by.delta2_node is not None
|
|
||||||
prio = by.vertex_set.min_prio()
|
|
||||||
if prio < math.inf:
|
|
||||||
prio += by.vertex_dual_offset
|
|
||||||
if prio > by.delta2_node.prio:
|
|
||||||
self.delta2_queue.increase_prio(
|
|
||||||
by.delta2_node, prio)
|
|
||||||
else:
|
|
||||||
self.delta2_queue.delete(by.delta2_node)
|
|
||||||
by.delta2_node = None
|
|
||||||
|
|
||||||
def reset_blossom_label(self, blossom: _Blossom) -> None:
|
def reset_blossom_label(self, blossom: _Blossom) -> None:
|
||||||
"""Remove blossom label."""
|
"""Remove blossom label."""
|
||||||
|
@ -907,14 +1003,7 @@ class _MatchingContext:
|
||||||
elif blossom.label == _LABEL_T:
|
elif blossom.label == _LABEL_T:
|
||||||
|
|
||||||
self.remove_blossom_label_t(blossom)
|
self.remove_blossom_label_t(blossom)
|
||||||
|
self.delta2_enable_blossom(blossom)
|
||||||
# Since the blossom is now unlabeled, insert it in delta2_queue
|
|
||||||
# if it has at least one edge to an S-vertex.
|
|
||||||
assert blossom.delta2_node is None
|
|
||||||
prio = blossom.vertex_set.min_prio()
|
|
||||||
if prio < math.inf:
|
|
||||||
prio += blossom.vertex_dual_offset
|
|
||||||
blossom.delta2_node = self.delta2_queue.insert(prio, blossom)
|
|
||||||
|
|
||||||
def _check_alternating_tree_consistency(self) -> None:
|
def _check_alternating_tree_consistency(self) -> None:
|
||||||
"""TODO -- remove this function, only for debugging"""
|
"""TODO -- remove this function, only for debugging"""
|
||||||
|
@ -1161,12 +1250,7 @@ class _MatchingContext:
|
||||||
assert sub.vertex_dual_offset == 0
|
assert sub.vertex_dual_offset == 0
|
||||||
sub.vertex_dual_offset = vertex_dual_fixup
|
sub.vertex_dual_offset = vertex_dual_fixup
|
||||||
|
|
||||||
# Insert blossom in delta2_queue if necessary.
|
self.delta2_enable_blossom(sub)
|
||||||
prio = sub.vertex_set.min_prio()
|
|
||||||
if prio < math.inf:
|
|
||||||
assert sub.delta2_node is None
|
|
||||||
prio += sub.vertex_dual_offset
|
|
||||||
sub.delta2_node = self.delta2_queue.insert(prio, sub)
|
|
||||||
|
|
||||||
# The expanding blossom was part of an alternating tree, linked to
|
# The expanding blossom was part of an alternating tree, linked to
|
||||||
# a parent node in the tree via one of its subblossoms, and linked to
|
# a parent node in the tree via one of its subblossoms, and linked to
|
||||||
|
@ -1224,10 +1308,7 @@ class _MatchingContext:
|
||||||
assert blossom.parent is None
|
assert blossom.parent is None
|
||||||
assert blossom.label == _LABEL_NONE
|
assert blossom.label == _LABEL_NONE
|
||||||
|
|
||||||
# Remove blossom from delta2 heap.
|
self.delta2_disable_blossom(blossom)
|
||||||
assert blossom.delta2_node is not None
|
|
||||||
self.delta2_queue.delete(blossom.delta2_node)
|
|
||||||
blossom.delta2_node = None
|
|
||||||
|
|
||||||
# Split union-find structure.
|
# Split union-find structure.
|
||||||
blossom.vertex_set.split()
|
blossom.vertex_set.split()
|
||||||
|
@ -1244,12 +1325,7 @@ class _MatchingContext:
|
||||||
assert sub.vertex_dual_offset == 0
|
assert sub.vertex_dual_offset == 0
|
||||||
sub.vertex_dual_offset = vertex_dual_offset
|
sub.vertex_dual_offset = vertex_dual_offset
|
||||||
|
|
||||||
# Insert blossom in delta2_queue if necessary.
|
self.delta2_enable_blossom(sub)
|
||||||
prio = sub.vertex_set.min_prio()
|
|
||||||
if prio < math.inf:
|
|
||||||
assert sub.delta2_node is None
|
|
||||||
prio += sub.vertex_dual_offset
|
|
||||||
sub.delta2_node = self.delta2_queue.insert(prio, sub)
|
|
||||||
|
|
||||||
# Delete the expanded blossom.
|
# Delete the expanded blossom.
|
||||||
self.nontrivial_blossom.remove(blossom)
|
self.nontrivial_blossom.remove(blossom)
|
||||||
|
@ -1546,25 +1622,9 @@ class _MatchingContext:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if by.label == _LABEL_S:
|
if by.label == _LABEL_S:
|
||||||
# Update tracking of least-slack edges between S-blossoms.
|
self.delta3_add_edge(e)
|
||||||
# Priority is edge slack plus 2 times the running sum of
|
|
||||||
# delta steps.
|
|
||||||
if self.delta3_node[e] is None:
|
|
||||||
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 prio_2x % 2 == 0
|
|
||||||
prio = prio_2x // 2
|
|
||||||
else:
|
|
||||||
prio = prio_2x / 2
|
|
||||||
self.delta3_node[e] = self.delta3_queue.insert(prio, e)
|
|
||||||
else:
|
else:
|
||||||
# Update tracking of least-slack edges from vertex "y" to
|
self.delta2_add_edge(e, y, by)
|
||||||
# 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)
|
|
||||||
|
|
||||||
self.scan_queue.clear()
|
self.scan_queue.clear()
|
||||||
|
|
||||||
|
@ -1604,7 +1664,7 @@ class _MatchingContext:
|
||||||
# Compute delta2: minimum slack of any edge between an S-vertex and
|
# Compute delta2: minimum slack of any edge between an S-vertex and
|
||||||
# an unlabeled vertex.
|
# an unlabeled vertex.
|
||||||
# This takes time O(log(n)).
|
# This takes time O(log(n)).
|
||||||
(e, slack) = self.lset_get_best_vertex_edge()
|
(e, slack) = self.delta2_get_min_edge()
|
||||||
if (e != -1) and (slack <= delta_2x):
|
if (e != -1) and (slack <= delta_2x):
|
||||||
delta_type = 2
|
delta_type = 2
|
||||||
delta_2x = slack
|
delta_2x = slack
|
||||||
|
@ -1612,31 +1672,12 @@ class _MatchingContext:
|
||||||
|
|
||||||
# Compute delta3: half minimum slack of any edge between two top-level
|
# Compute delta3: half minimum slack of any edge between two top-level
|
||||||
# S-blossoms.
|
# S-blossoms.
|
||||||
#
|
# This takes total time O(m * log(n)) per stage.
|
||||||
# This loop iterates O(m) times per stage.
|
(e, slack) = self.delta3_get_min_edge()
|
||||||
# Each iteration takes time O(log(n)).
|
if (e != -1) and (slack <= delta_2x):
|
||||||
while not self.delta3_queue.empty():
|
delta_type = 3
|
||||||
delta3_node = self.delta3_queue.find_min()
|
delta_2x = slack
|
||||||
e = delta3_node.data
|
delta_edge = e
|
||||||
(x, y, _w) = self.graph.edges[e]
|
|
||||||
bx = self.vertex_set_node[x].find()
|
|
||||||
by = self.vertex_set_node[y].find()
|
|
||||||
assert (bx.label == _LABEL_S) and (by.label == _LABEL_S)
|
|
||||||
if bx is not by:
|
|
||||||
# Found edge between different top-level S-blossoms.
|
|
||||||
slack = delta3_node.prio - self.delta_sum_2x
|
|
||||||
if slack <= delta_2x:
|
|
||||||
delta_type = 3
|
|
||||||
delta_2x = slack
|
|
||||||
delta_edge = e
|
|
||||||
break
|
|
||||||
|
|
||||||
# Reject edges between vertices within the same top-level blossom.
|
|
||||||
# Although intra-blossom edges are never inserted into the queue,
|
|
||||||
# existing edges in the queue may become intra-blossom when
|
|
||||||
# a new blossom is formed.
|
|
||||||
self.delta3_queue.delete(delta3_node)
|
|
||||||
self.delta3_node[e] = None
|
|
||||||
|
|
||||||
# Compute delta4: half minimum dual variable of a top-level T-blossom.
|
# Compute delta4: half minimum dual variable of a top-level T-blossom.
|
||||||
# This takes time O(log(n)).
|
# This takes time O(log(n)).
|
||||||
|
|
Loading…
Reference in New Issue