Use (x, y) instead of (i, j)
This commit is contained in:
parent
d063bbd45a
commit
0ad3020425
|
@ -34,13 +34,13 @@ def maximum_weight_matching(
|
||||||
This function uses O(n + m) memory, where "m" is the number of edges.
|
This function uses O(n + m) memory, where "m" is the number of edges.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
edges: List of edges, each edge specified as a tuple "(i, j, w)"
|
edges: List of edges, each edge specified as a tuple "(x, y, w)"
|
||||||
where "i" and "j" are vertex indices and "w" is the edge weight.
|
where "x" and "y" are vertex indices and "w" is the edge weight.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of pairs of matched vertex indices.
|
List of pairs of matched vertex indices.
|
||||||
This is a subset of the list of edges in the graph.
|
This is a subset of the edges in the graph.
|
||||||
It contains a tuple "(i, j)" if vertex "i" is matched to vertex "j".
|
It contains a tuple "(x, y)" if vertex "x" is matched to vertex "y".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the input does not satisfy the constraints.
|
ValueError: If the input does not satisfy the constraints.
|
||||||
|
@ -76,7 +76,7 @@ def maximum_weight_matching(
|
||||||
|
|
||||||
# Extract the final solution.
|
# Extract the final solution.
|
||||||
pairs: list[tuple[int, int]] = [
|
pairs: list[tuple[int, int]] = [
|
||||||
(i, j) for (i, j, _w) in edges if ctx.vertex_mate[i] == j]
|
(x, y) for (x, y, _w) in edges if ctx.vertex_mate[x] == y]
|
||||||
|
|
||||||
# Verify that the matching is optimal.
|
# Verify that the matching is optimal.
|
||||||
# This only works reliably for integer weights.
|
# This only works reliably for integer weights.
|
||||||
|
@ -130,8 +130,8 @@ def adjust_weights_for_maximum_cardinality_matching(
|
||||||
This function takes time O(m), where "m" is the number of edges.
|
This function takes time O(m), where "m" is the number of edges.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
edges: List of edges, each edge specified as a tuple "(i, j, w)"
|
edges: List of edges, each edge specified as a tuple "(x, y, w)"
|
||||||
where "i" and "j" are vertex indices and "w" is the edge weight.
|
where "x" and "y" are vertex indices and "w" is the edge weight.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of edges with adjusted weights. If no adjustments are necessary,
|
List of edges with adjusted weights. If no adjustments are necessary,
|
||||||
|
@ -148,10 +148,10 @@ def adjust_weights_for_maximum_cardinality_matching(
|
||||||
if not edges:
|
if not edges:
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
num_vertex = 1 + max(max(i, j) for (i, j, _w) in edges)
|
num_vertex = 1 + max(max(x, y) for (x, y, _w) in edges)
|
||||||
|
|
||||||
min_weight = min(w for (_i, _j, w) in edges)
|
min_weight = min(w for (_x, _y, w) in edges)
|
||||||
max_weight = max(w for (_i, _j, w) in edges)
|
max_weight = max(w for (_x, _y, w) in edges)
|
||||||
weight_range = max_weight - min_weight
|
weight_range = max_weight - min_weight
|
||||||
|
|
||||||
# Do nothing if the weights already ensure a maximum-cardinality matching.
|
# Do nothing if the weights already ensure a maximum-cardinality matching.
|
||||||
|
@ -170,7 +170,7 @@ def adjust_weights_for_maximum_cardinality_matching(
|
||||||
assert delta >= 0
|
assert delta >= 0
|
||||||
|
|
||||||
# Increase all edge weights by "delta".
|
# Increase all edge weights by "delta".
|
||||||
return [(i, j, w + delta) for (i, j, w) in edges]
|
return [(x, y, w + delta) for (x, y, w) in edges]
|
||||||
|
|
||||||
|
|
||||||
def _check_input_types(edges: list[tuple[int, int, int|float]]) -> None:
|
def _check_input_types(edges: list[tuple[int, int, int|float]]) -> None:
|
||||||
|
@ -180,8 +180,8 @@ def _check_input_types(edges: list[tuple[int, int, int|float]]) -> None:
|
||||||
This function takes time O(m).
|
This function takes time O(m).
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
edges: List of edges, each edge specified as a tuple "(i, j, w)"
|
edges: List of edges, each edge specified as a tuple "(x, y, w)"
|
||||||
where "i" and "j" are edge indices and "w" is the edge weight.
|
where "x" and "y" are edge indices and "w" is the edge weight.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the input does not satisfy the constraints.
|
ValueError: If the input does not satisfy the constraints.
|
||||||
|
@ -197,12 +197,12 @@ def _check_input_types(edges: list[tuple[int, int, int|float]]) -> None:
|
||||||
if (not isinstance(e, tuple)) or (len(e) != 3):
|
if (not isinstance(e, tuple)) or (len(e) != 3):
|
||||||
raise TypeError("Each edge must be specified as a 3-tuple")
|
raise TypeError("Each edge must be specified as a 3-tuple")
|
||||||
|
|
||||||
(i, j, w) = e
|
(x, y, w) = e
|
||||||
|
|
||||||
if (not isinstance(i, int)) or (not isinstance(j, int)):
|
if (not isinstance(x, int)) or (not isinstance(y, int)):
|
||||||
raise TypeError("Edge endpoints must be integers")
|
raise TypeError("Edge endpoints must be integers")
|
||||||
|
|
||||||
if (i < 0) or (j < 0):
|
if (x < 0) or (y < 0):
|
||||||
raise ValueError("Edge endpoints must be non-negative integers")
|
raise ValueError("Edge endpoints must be non-negative integers")
|
||||||
|
|
||||||
if not isinstance(w, (int, float)):
|
if not isinstance(w, (int, float)):
|
||||||
|
@ -227,23 +227,23 @@ def _check_input_graph(edges: list[tuple[int, int, int|float]]) -> None:
|
||||||
This function takes time O(m * log(m)).
|
This function takes time O(m * log(m)).
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
edges: List of edges, each edge specified as a tuple "(i, j, w)"
|
edges: List of edges, each edge specified as a tuple "(x, y, w)"
|
||||||
where "i" and "j" are edge indices and "w" is the edge weight.
|
where "x" and "y" are edge indices and "w" is the edge weight.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the input does not satisfy the constraints.
|
ValueError: If the input does not satisfy the constraints.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check that the graph has no self-edges.
|
# Check that the graph has no self-edges.
|
||||||
for (i, j, _w) in edges:
|
for (x, y, _w) in edges:
|
||||||
if i == j:
|
if x == y:
|
||||||
raise ValueError("Self-edges are not supported")
|
raise ValueError("Self-edges are not supported")
|
||||||
|
|
||||||
# Check that the graph does not have multi-edges.
|
# Check that the graph does not have multi-edges.
|
||||||
# Using a set() would be more straightforward, but the runtime bounds
|
# Using a set() would be more straightforward, but the runtime bounds
|
||||||
# of the Python set type are not clearly specified.
|
# of the Python set type are not clearly specified.
|
||||||
# Sorting provides guaranteed O(m * log(m)) run time.
|
# Sorting provides guaranteed O(m * log(m)) run time.
|
||||||
edge_endpoints = [((i, j) if (i < j) else (j, i)) for (i, j, _w) in edges]
|
edge_endpoints = [((x, y) if (x < y) else (y, x)) for (x, y, _w) in edges]
|
||||||
edge_endpoints.sort()
|
edge_endpoints.sort()
|
||||||
|
|
||||||
for i in range(len(edge_endpoints) - 1):
|
for i in range(len(edge_endpoints) - 1):
|
||||||
|
@ -283,9 +283,9 @@ class _GraphInfo:
|
||||||
# Each edge is incident on two vertices.
|
# Each edge is incident on two vertices.
|
||||||
# Each edge also has a weight.
|
# Each edge also has a weight.
|
||||||
#
|
#
|
||||||
# "edges[e] = (i, j, w)" where
|
# "edges[e] = (x, y, w)" where
|
||||||
# "e" is an edge index;
|
# "e" is an edge index;
|
||||||
# "i" and "j" are vertex indices of the incident vertices;
|
# "x" and "y" are vertex indices of the incident vertices;
|
||||||
# "w" is the edge weight.
|
# "w" is the edge weight.
|
||||||
#
|
#
|
||||||
# These data remain unchanged while the algorithm runs.
|
# These data remain unchanged while the algorithm runs.
|
||||||
|
@ -293,7 +293,7 @@ class _GraphInfo:
|
||||||
|
|
||||||
# num_vertex = the number of vertices.
|
# num_vertex = the number of vertices.
|
||||||
if edges:
|
if edges:
|
||||||
self.num_vertex = 1 + max(max(i, j) for (i, j, _w) in edges)
|
self.num_vertex = 1 + max(max(x, y) for (x, y, _w) in edges)
|
||||||
else:
|
else:
|
||||||
self.num_vertex = 0
|
self.num_vertex = 0
|
||||||
|
|
||||||
|
@ -305,14 +305,14 @@ class _GraphInfo:
|
||||||
# These data remain unchanged while the algorithm runs.
|
# These data remain unchanged while the algorithm runs.
|
||||||
self.adjacent_edges: list[list[int]] = [
|
self.adjacent_edges: list[list[int]] = [
|
||||||
[] for v in range(self.num_vertex)]
|
[] for v in range(self.num_vertex)]
|
||||||
for (e, (i, j, _w)) in enumerate(edges):
|
for (e, (x, y, _w)) in enumerate(edges):
|
||||||
self.adjacent_edges[i].append(e)
|
self.adjacent_edges[x].append(e)
|
||||||
self.adjacent_edges[j].append(e)
|
self.adjacent_edges[y].append(e)
|
||||||
|
|
||||||
# Determine whether _all_ weights are integers.
|
# Determine whether _all_ weights are integers.
|
||||||
# In this case we can avoid floating point computations entirely.
|
# In this case we can avoid floating point computations entirely.
|
||||||
self.integer_weights: bool = all(isinstance(w, int)
|
self.integer_weights: bool = all(isinstance(w, int)
|
||||||
for (_i, _j, w) in edges)
|
for (_x, _y, w) in edges)
|
||||||
|
|
||||||
|
|
||||||
# Each vertex may be labeled "S" (outer) or "T" (inner) or be unlabeled.
|
# Each vertex may be labeled "S" (outer) or "T" (inner) or be unlabeled.
|
||||||
|
@ -371,11 +371,11 @@ class _Blossom:
|
||||||
self.subblossoms: list[int] = subblossoms
|
self.subblossoms: list[int] = subblossoms
|
||||||
|
|
||||||
# "edges" is a list of edges linking the sub-blossoms.
|
# "edges" is a list of edges linking the sub-blossoms.
|
||||||
# Each edge is represented as an ordered pair "(i, j)" where "i"
|
# Each edge is represented as an ordered pair "(x, y)" where "x"
|
||||||
# and "j" are vertex indices.
|
# and "y" are vertex indices.
|
||||||
#
|
#
|
||||||
# "edges[0] = (i, j)" where vertex "i" in "subblossoms[0]" is
|
# "edges[0] = (x, y)" where vertex "x" in "subblossoms[0]" is
|
||||||
# adjacent to vertex "j" in "subblossoms[1]", etc.
|
# adjacent to vertex "y" in "subblossoms[1]", etc.
|
||||||
self.edges: list[tuple[int, int]] = edges
|
self.edges: list[tuple[int, int]] = edges
|
||||||
|
|
||||||
# "base_vertex" is the vertex index of the base of the blossom.
|
# "base_vertex" is the vertex index of the base of the blossom.
|
||||||
|
@ -416,9 +416,9 @@ class _MatchingContext:
|
||||||
# Each vertex is either single (unmatched) or matched to
|
# Each vertex is either single (unmatched) or matched to
|
||||||
# another vertex.
|
# another vertex.
|
||||||
#
|
#
|
||||||
# If vertex "i" is matched to vertex "j",
|
# If vertex "x" is matched to vertex "y",
|
||||||
# "vertex_mate[i] == j" and "vertex_mate[j] == i".
|
# "vertex_mate[x] == y" and "vertex_mate[y] == x".
|
||||||
# If vertex "i" is unmatched, "vertex_mate[i] == -1".
|
# If vertex "x" is unmatched, "vertex_mate[x] == -1".
|
||||||
#
|
#
|
||||||
# Initially all vertices are unmatched.
|
# Initially all vertices are unmatched.
|
||||||
self.vertex_mate: list[int] = num_vertex * [-1]
|
self.vertex_mate: list[int] = num_vertex * [-1]
|
||||||
|
@ -447,9 +447,9 @@ class _MatchingContext:
|
||||||
# Every vertex is part of exactly one top-level blossom,
|
# Every vertex is part of exactly one top-level blossom,
|
||||||
# possibly a trivial blossom consisting of just that vertex.
|
# possibly a trivial blossom consisting of just that vertex.
|
||||||
#
|
#
|
||||||
# "vertex_blossom[i]" is the index of the top-level blossom that
|
# "vertex_blossom[x]" is the index of the top-level blossom that
|
||||||
# contains vertex "i".
|
# contains vertex "x".
|
||||||
# "vertex_blossom[i] == i" if the "i" is a trivial top-level blossom.
|
# "vertex_blossom[x] == x" if the "x" is a trivial top-level blossom.
|
||||||
#
|
#
|
||||||
# Initially all vertices are top-level trivial blossoms.
|
# Initially all vertices are top-level trivial blossoms.
|
||||||
self.vertex_blossom: list[int] = list(range(num_vertex))
|
self.vertex_blossom: list[int] = list(range(num_vertex))
|
||||||
|
@ -463,14 +463,13 @@ class _MatchingContext:
|
||||||
|
|
||||||
# Every vertex has a variable in the dual LPP.
|
# Every vertex has a variable in the dual LPP.
|
||||||
#
|
#
|
||||||
# "dual_var_2x[i]" is 2 times the dual variable of vertex "i".
|
# "vertex_dual_2x[x]" is 2 times the dual variable of vertex "x".
|
||||||
# Multiplication by 2 ensures that the values are integers
|
# Multiplication by 2 ensures that the values are integers
|
||||||
# if all edge weights are integers.
|
# if all edge weights are integers.
|
||||||
#
|
#
|
||||||
# Vertex duals are initialized to half the maximum edge weight.
|
# Vertex duals are initialized to half the maximum edge weight.
|
||||||
max_weight = max(w for (_i, _j, w) in graph.edges)
|
max_weight = max(w for (_x, _y, w) in graph.edges)
|
||||||
# TODO : rename to "vertex_dual_2x"
|
self.vertex_dual_2x: list[int|float] = num_vertex * [max_weight]
|
||||||
self.dual_var_2x: list[int|float] = num_vertex * [max_weight]
|
|
||||||
|
|
||||||
# Top-level blossoms that are part of an alternating tree are
|
# Top-level blossoms that are part of an alternating tree are
|
||||||
# labeled S or T. Unlabeled top-level blossoms are not (yet)
|
# labeled S or T. Unlabeled top-level blossoms are not (yet)
|
||||||
|
@ -484,17 +483,17 @@ class _MatchingContext:
|
||||||
# For each labeled blossom, we keep track of the edge that attaches
|
# For each labeled blossom, we keep track of the edge that attaches
|
||||||
# it to its alternating tree.
|
# it to its alternating tree.
|
||||||
#
|
#
|
||||||
# "blossom_link[b] = (i, j)" denotes the edge through which
|
# "blossom_link[b] = (x, y)" denotes the edge through which
|
||||||
# blossom "b" is attached to the alternating tree, where "i" and "j"
|
# blossom "b" is attached to the alternating tree, where "x" and "y"
|
||||||
# are vertex indices and vertex "j" is contained in blossom "b".
|
# are vertex indices and vertex "y" is contained in blossom "b".
|
||||||
#
|
#
|
||||||
# "blossom_link[b] = None" if "b" is the root of an alternating tree,
|
# "blossom_link[b] = None" if "b" is the root of an alternating tree,
|
||||||
# or if "b" is not a labeled, top-level blossom.
|
# or if "b" is not a labeled, top-level blossom.
|
||||||
self.blossom_link: list[Optional[tuple[int, int]]] = [
|
self.blossom_link: list[Optional[tuple[int, int]]] = [
|
||||||
None for b in range(2 * num_vertex)]
|
None for b in range(2 * num_vertex)]
|
||||||
|
|
||||||
# "vertex_best_edge[i]" is the edge index of the least-slack edge
|
# "vertex_best_edge[x]" is the edge index of the least-slack edge
|
||||||
# between "i" and any S-vertex, or -1 if no such edge has been found.
|
# between "x" and any S-vertex, or -1 if no such edge has been found.
|
||||||
self.vertex_best_edge: list[int] = num_vertex * [-1]
|
self.vertex_best_edge: list[int] = num_vertex * [-1]
|
||||||
|
|
||||||
# For non-trivial top-level S-blossom "b",
|
# For non-trivial top-level S-blossom "b",
|
||||||
|
@ -524,9 +523,9 @@ class _MatchingContext:
|
||||||
Multiplication by 2 ensures that the return value is an integer
|
Multiplication by 2 ensures that the return value is an integer
|
||||||
if all edge weights are integers.
|
if all edge weights are integers.
|
||||||
"""
|
"""
|
||||||
(i, j, w) = self.graph.edges[e]
|
(x, y, w) = self.graph.edges[e]
|
||||||
assert self.vertex_blossom[i] != self.vertex_blossom[j]
|
assert self.vertex_blossom[x] != self.vertex_blossom[y]
|
||||||
return self.dual_var_2x[i] + self.dual_var_2x[j] - 2 * w
|
return self.vertex_dual_2x[x] + self.vertex_dual_2x[y] - 2 * w
|
||||||
|
|
||||||
def get_blossom(self, b: int) -> _Blossom:
|
def get_blossom(self, b: int) -> _Blossom:
|
||||||
"""Return the Blossom instance for blossom index "b"."""
|
"""Return the Blossom instance for blossom index "b"."""
|
||||||
|
@ -570,15 +569,15 @@ class _MatchingContext:
|
||||||
self.queue.clear()
|
self.queue.clear()
|
||||||
|
|
||||||
# Reset least-slack edge tracking.
|
# Reset least-slack edge tracking.
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
self.vertex_best_edge[i] = -1
|
self.vertex_best_edge[x] = -1
|
||||||
|
|
||||||
for b in range(2 * num_vertex):
|
for b in range(2 * num_vertex):
|
||||||
self.blossom_best_edge_set[b] = None
|
self.blossom_best_edge_set[b] = None
|
||||||
self.blossom_best_edge[b] = -1
|
self.blossom_best_edge[b] = -1
|
||||||
|
|
||||||
def trace_alternating_paths(self, i: int, j: int) -> _AlternatingPath:
|
def trace_alternating_paths(self, x: int, y: int) -> _AlternatingPath:
|
||||||
"""Trace back through the alternating trees from vertices "i" and "j".
|
"""Trace back through the alternating trees from vertices "x" and "y".
|
||||||
|
|
||||||
If both vertices are part of the same alternating tree, this function
|
If both vertices are part of the same alternating tree, this function
|
||||||
discovers a new blossom. In this case it returns an alternating path
|
discovers a new blossom. In this case it returns an alternating path
|
||||||
|
@ -599,45 +598,45 @@ class _MatchingContext:
|
||||||
blossom_marker = self.blossom_marker
|
blossom_marker = self.blossom_marker
|
||||||
marked_blossoms: list[int] = []
|
marked_blossoms: list[int] = []
|
||||||
|
|
||||||
# "iedges" is a list of edges used while tracing from "i".
|
# "xedges" is a list of edges used while tracing from "x".
|
||||||
# "jedges" is a list of edges used while tracing from "j".
|
# "yedges" is a list of edges used while tracing from "y".
|
||||||
iedges: list[tuple[int, int]] = []
|
xedges: list[tuple[int, int]] = []
|
||||||
jedges: list[tuple[int, int]] = []
|
yedges: list[tuple[int, int]] = []
|
||||||
|
|
||||||
# Pre-load the edge between "i" and "j" so it will end up in the right
|
# Pre-load the edge between "x" and "y" so it will end up in the right
|
||||||
# place in the final path.
|
# place in the final path.
|
||||||
iedges.append((i, j))
|
xedges.append((x, y))
|
||||||
|
|
||||||
# Alternate between tracing the path from "i" and the path from "j".
|
# Alternate between tracing the path from "x" and the path from "y".
|
||||||
# This ensures that the search time is bounded by the size of the
|
# This ensures that the search time is bounded by the size of the
|
||||||
# newly found blossom.
|
# newly found blossom.
|
||||||
# TODO : this code is a bit shady; maybe reconsider the swapping trick
|
# TODO : this code is a bit shady; maybe reconsider the swapping trick
|
||||||
first_common = -1
|
first_common = -1
|
||||||
while i != -1 or j != -1:
|
while x != -1 or y != -1:
|
||||||
|
|
||||||
# Check if we found a common ancestor.
|
# Check if we found a common ancestor.
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
if blossom_marker[bi]:
|
if blossom_marker[bx]:
|
||||||
first_common = bi
|
first_common = bx
|
||||||
break
|
break
|
||||||
|
|
||||||
# Mark blossom as a potential common ancestor.
|
# Mark blossom as a potential common ancestor.
|
||||||
blossom_marker[bi] = True
|
blossom_marker[bx] = True
|
||||||
marked_blossoms.append(bi)
|
marked_blossoms.append(bx)
|
||||||
|
|
||||||
# Track back through the link in the alternating tree.
|
# Track back through the link in the alternating tree.
|
||||||
link = self.blossom_link[bi]
|
link = self.blossom_link[bx]
|
||||||
if link is None:
|
if link is None:
|
||||||
# Reached the root of this alternating tree.
|
# Reached the root of this alternating tree.
|
||||||
i = -1
|
x = -1
|
||||||
else:
|
else:
|
||||||
iedges.append(link)
|
xedges.append(link)
|
||||||
i = link[0]
|
x = link[0]
|
||||||
|
|
||||||
# Swap "i" and "j" to alternate between paths.
|
# Swap "x" and "y" to alternate between paths.
|
||||||
if j != -1:
|
if y != -1:
|
||||||
i, j = j, i
|
(x, y) = (y, x)
|
||||||
iedges, jedges = jedges, iedges
|
(xedges, yedges) = (yedges, xedges)
|
||||||
|
|
||||||
# Remove all markers we placed.
|
# Remove all markers we placed.
|
||||||
for b in marked_blossoms:
|
for b in marked_blossoms:
|
||||||
|
@ -646,15 +645,15 @@ class _MatchingContext:
|
||||||
# If we found a common ancestor, trim the paths so they end there.
|
# If we found a common ancestor, trim the paths so they end there.
|
||||||
# TODO : also this is just plain ugly - try to rework
|
# TODO : also this is just plain ugly - try to rework
|
||||||
if first_common != -1:
|
if first_common != -1:
|
||||||
assert self.vertex_blossom[iedges[-1][0]] == first_common
|
assert self.vertex_blossom[xedges[-1][0]] == first_common
|
||||||
while (jedges
|
while (yedges
|
||||||
and (self.vertex_blossom[jedges[-1][0]] != first_common)):
|
and (self.vertex_blossom[yedges[-1][0]] != first_common)):
|
||||||
jedges.pop()
|
yedges.pop()
|
||||||
|
|
||||||
# Fuse the two paths.
|
# Fuse the two paths.
|
||||||
# Flip the order of one path and the edge tuples in the other path
|
# Flip the order of one path and the edge tuples in the other path
|
||||||
# to obtain a continuous path with correctly ordered edge tuples.
|
# to obtain a continuous path with correctly ordered edge tuples.
|
||||||
path_edges = iedges[::-1] + [(j, i) for (i, j) in jedges]
|
path_edges = xedges[::-1] + [(y, x) for (x, y) in yedges]
|
||||||
|
|
||||||
# Any S-to-S alternating path must have odd length.
|
# Any S-to-S alternating path must have odd length.
|
||||||
assert len(path_edges) % 2 == 1
|
assert len(path_edges) % 2 == 1
|
||||||
|
@ -677,12 +676,12 @@ class _MatchingContext:
|
||||||
assert len(path.edges) >= 3
|
assert len(path.edges) >= 3
|
||||||
|
|
||||||
# Construct the list of sub-blossoms (current top-level blossoms).
|
# Construct the list of sub-blossoms (current top-level blossoms).
|
||||||
subblossoms = [self.vertex_blossom[i] for (i, j) in path.edges]
|
subblossoms = [self.vertex_blossom[x] for (x, y) in path.edges]
|
||||||
|
|
||||||
# Check that the path is cyclic.
|
# Check that the path is cyclic.
|
||||||
# Note the path may not start and end with the same _vertex_,
|
# Note the path may not start and end with the same _vertex_,
|
||||||
# but it must start and end in the same _blossom_.
|
# but it must start and end in the same _blossom_.
|
||||||
subblossoms_next = [self.vertex_blossom[j] for (i, j) in path.edges]
|
subblossoms_next = [self.vertex_blossom[y] for (x, y) in path.edges]
|
||||||
assert subblossoms[0] == subblossoms_next[-1]
|
assert subblossoms[0] == subblossoms_next[-1]
|
||||||
assert subblossoms[1:] == subblossoms_next[:-1]
|
assert subblossoms[1:] == subblossoms_next[:-1]
|
||||||
|
|
||||||
|
@ -710,8 +709,8 @@ class _MatchingContext:
|
||||||
# to O(n**2) total time per stage.
|
# to O(n**2) total time per stage.
|
||||||
# This could be improved through a union-find datastructure, or
|
# This could be improved through a union-find datastructure, or
|
||||||
# by re-using the blossom index of the largest sub-blossom.
|
# by re-using the blossom index of the largest sub-blossom.
|
||||||
for i in self.blossom_vertices(b):
|
for x in self.blossom_vertices(b):
|
||||||
self.vertex_blossom[i] = b
|
self.vertex_blossom[x] = b
|
||||||
|
|
||||||
# Assign label S to the new blossom.
|
# Assign label S to the new blossom.
|
||||||
assert self.blossom_label[base_blossom] == _LABEL_S
|
assert self.blossom_label[base_blossom] == _LABEL_S
|
||||||
|
@ -767,28 +766,29 @@ class _MatchingContext:
|
||||||
|
|
||||||
# Add edges to the temporary array.
|
# Add edges to the temporary array.
|
||||||
for e in sub_edge_set:
|
for e in sub_edge_set:
|
||||||
(i, j, _w) = self.graph.edges[e]
|
(x, y, _w) = self.graph.edges[e]
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
bj = self.vertex_blossom[j]
|
by = self.vertex_blossom[y]
|
||||||
assert (bi == b) or (bj == b)
|
assert (bx == b) or (by == b)
|
||||||
|
|
||||||
# Reject internal edges in this blossom.
|
# Reject internal edges in this blossom.
|
||||||
if bi == bj:
|
if bx == by:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Set bi = other blossom which is reachable through this edge.
|
# Set bi = other blossom which is reachable through this edge.
|
||||||
bi = bj if (bi == b) else bi
|
# TODO : generalize over this pattern
|
||||||
|
bx = by if (bx == b) else bx
|
||||||
|
|
||||||
# Reject edges that don't link to an S-blossom.
|
# Reject edges that don't link to an S-blossom.
|
||||||
if self.blossom_label[bi] != _LABEL_S:
|
if self.blossom_label[bx] != _LABEL_S:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Keep only the least-slack edge to "vblossom".
|
# Keep only the least-slack edge to "vblossom".
|
||||||
slack = self.edge_slack_2x(e)
|
slack = self.edge_slack_2x(e)
|
||||||
if ((best_edge_to_blossom[bi] == -1)
|
if ((best_edge_to_blossom[bx] == -1)
|
||||||
or (slack < best_slack_to_blossom[bi])):
|
or (slack < best_slack_to_blossom[bx])):
|
||||||
best_edge_to_blossom[bi] = e
|
best_edge_to_blossom[bx] = e
|
||||||
best_slack_to_blossom[bi] = slack
|
best_slack_to_blossom[bx] = slack
|
||||||
|
|
||||||
# Extract a compact list of least-slack edge indices.
|
# Extract a compact list of least-slack edge indices.
|
||||||
# We can not keep the temporary array because that would blow up
|
# We can not keep the temporary array because that would blow up
|
||||||
|
@ -878,8 +878,8 @@ class _MatchingContext:
|
||||||
if sub < num_vertex:
|
if sub < num_vertex:
|
||||||
self.vertex_blossom[sub] = sub
|
self.vertex_blossom[sub] = sub
|
||||||
else:
|
else:
|
||||||
for i in self.blossom_vertices(sub):
|
for x in self.blossom_vertices(sub):
|
||||||
self.vertex_blossom[i] = sub
|
self.vertex_blossom[x] = sub
|
||||||
assert self.blossom_label[sub] == _LABEL_NONE
|
assert self.blossom_label[sub] == _LABEL_NONE
|
||||||
|
|
||||||
# The expanding blossom was part of an alternating tree, linked to
|
# The expanding blossom was part of an alternating tree, linked to
|
||||||
|
@ -893,12 +893,12 @@ class _MatchingContext:
|
||||||
# TODO : uglyness with the assertion
|
# TODO : uglyness with the assertion
|
||||||
entry_link = self.blossom_link[b]
|
entry_link = self.blossom_link[b]
|
||||||
assert entry_link is not None
|
assert entry_link is not None
|
||||||
(i, j) = entry_link
|
(x, y) = entry_link
|
||||||
sub = self.vertex_blossom[j]
|
sub = self.vertex_blossom[y]
|
||||||
|
|
||||||
# Assign label T to that sub-blossom.
|
# Assign label T to that sub-blossom.
|
||||||
self.blossom_label[sub] = _LABEL_T
|
self.blossom_label[sub] = _LABEL_T
|
||||||
self.blossom_link[sub] = (i, j)
|
self.blossom_link[sub] = (x, y)
|
||||||
|
|
||||||
# Walk through the expanded blossom from "sub" to the base vertex.
|
# Walk through the expanded blossom from "sub" to the base vertex.
|
||||||
# Assign alternating S and T labels to the sub-blossoms and attach
|
# Assign alternating S and T labels to the sub-blossoms and attach
|
||||||
|
@ -907,15 +907,15 @@ class _MatchingContext:
|
||||||
|
|
||||||
for p in range(0, len(path_edges), 2):
|
for p in range(0, len(path_edges), 2):
|
||||||
#
|
#
|
||||||
# (p) ==(j,i)== (p+1) ----- (p+2)
|
# (p) ==(y,x)== (p+1) ----- (p+2)
|
||||||
# T S T
|
# T S T
|
||||||
#
|
#
|
||||||
# path_nodes[p] has already been labeled T.
|
# path_nodes[p] has already been labeled T.
|
||||||
# We now assign labels to path_nodes[p+1] and path_nodes[p+2].
|
# We now assign labels to path_nodes[p+1] and path_nodes[p+2].
|
||||||
|
|
||||||
# Assign label S to path_nodes[p+1].
|
# Assign label S to path_nodes[p+1].
|
||||||
(j, i) = path_edges[p]
|
(y, x) = path_edges[p]
|
||||||
self.assign_label_s(i)
|
self.assign_label_s(x)
|
||||||
|
|
||||||
# Assign label T to path_nodes[i+2] and attach it to path_nodes[p+1].
|
# Assign label T to path_nodes[i+2] and attach it to path_nodes[p+1].
|
||||||
sub = path_nodes[p+2]
|
sub = path_nodes[p+2]
|
||||||
|
@ -981,16 +981,16 @@ class _MatchingContext:
|
||||||
# This sub-blossom will not be expanded;
|
# This sub-blossom will not be expanded;
|
||||||
# it now becomes top-level. Update its vertices
|
# it now becomes top-level. Update its vertices
|
||||||
# to point to this sub-blossom.
|
# to point to this sub-blossom.
|
||||||
for i in self.blossom_vertices(sub):
|
for x in self.blossom_vertices(sub):
|
||||||
self.vertex_blossom[i] = sub
|
self.vertex_blossom[x] = sub
|
||||||
|
|
||||||
# Delete the expanded blossom. Recycle its blossom index.
|
# Delete the expanded blossom. Recycle its blossom index.
|
||||||
self.blossom[b] = None
|
self.blossom[b] = None
|
||||||
self.unused_blossoms.append(b)
|
self.unused_blossoms.append(b)
|
||||||
|
|
||||||
def augment_blossom(self, b: int, i: int) -> None:
|
def augment_blossom(self, b: int, sub: int) -> None:
|
||||||
"""Augment along an alternating path through blossom "b",
|
"""Augment along an alternating path through blossom "b",
|
||||||
from vertex "i" to the base vertex of the blossom.
|
from sub-blossom "sub" to the base vertex of the blossom.
|
||||||
|
|
||||||
This function takes time O(n).
|
This function takes time O(n).
|
||||||
"""
|
"""
|
||||||
|
@ -999,7 +999,7 @@ class _MatchingContext:
|
||||||
# TODO : cleanup explicit stack
|
# TODO : cleanup explicit stack
|
||||||
|
|
||||||
# Use an explicit stack to avoid deep recursion.
|
# Use an explicit stack to avoid deep recursion.
|
||||||
stack = [(b, i)]
|
stack = [(b, sub)]
|
||||||
|
|
||||||
while stack:
|
while stack:
|
||||||
(top_blossom, sub) = stack.pop()
|
(top_blossom, sub) = stack.pop()
|
||||||
|
@ -1031,19 +1031,19 @@ class _MatchingContext:
|
||||||
#
|
#
|
||||||
|
|
||||||
# Pull the edge (i, j) into the matching.
|
# Pull the edge (i, j) into the matching.
|
||||||
(i, j) = path_edges[p+1]
|
(x, y) = path_edges[p+1]
|
||||||
self.vertex_mate[i] = j
|
self.vertex_mate[x] = y
|
||||||
self.vertex_mate[j] = i
|
self.vertex_mate[y] = x
|
||||||
|
|
||||||
# Augment through the subblossoms touching the edge (i, j).
|
# Augment through the subblossoms touching the edge (i, j).
|
||||||
# Nothing needs to be done for trivial subblossoms.
|
# Nothing needs to be done for trivial subblossoms.
|
||||||
bi = path_nodes[p+1]
|
bx = path_nodes[p+1]
|
||||||
if bi >= num_vertex:
|
if bx >= num_vertex:
|
||||||
stack.append((bi, i))
|
stack.append((bx, x))
|
||||||
|
|
||||||
bj = path_nodes[p+2]
|
by = path_nodes[p+2]
|
||||||
if bj >= num_vertex:
|
if by >= num_vertex:
|
||||||
stack.append((bj, j))
|
stack.append((by, y))
|
||||||
|
|
||||||
# Rotate the subblossom list so the new base ends up in position 0.
|
# Rotate the subblossom list so the new base ends up in position 0.
|
||||||
p = blossom.subblossoms.index(sub)
|
p = blossom.subblossoms.index(sub)
|
||||||
|
@ -1068,11 +1068,11 @@ class _MatchingContext:
|
||||||
# Check that the augmenting path starts and ends in
|
# Check that the augmenting path starts and ends in
|
||||||
# an unmatched vertex or a blossom with unmatched base.
|
# an unmatched vertex or a blossom with unmatched base.
|
||||||
assert len(path.edges) % 2 == 1
|
assert len(path.edges) % 2 == 1
|
||||||
for i in (path.edges[0][0], path.edges[-1][1]):
|
for x in (path.edges[0][0], path.edges[-1][1]):
|
||||||
b = self.vertex_blossom[i]
|
b = self.vertex_blossom[x]
|
||||||
if b != i:
|
if b != x:
|
||||||
i = self.get_blossom(b).base_vertex
|
x = self.get_blossom(b).base_vertex
|
||||||
assert self.vertex_mate[i] == -1
|
assert self.vertex_mate[x] == -1
|
||||||
|
|
||||||
# The augmenting path looks like this:
|
# The augmenting path looks like this:
|
||||||
#
|
#
|
||||||
|
@ -1085,96 +1085,96 @@ class _MatchingContext:
|
||||||
#
|
#
|
||||||
# This loop walks along the edges of this path that were not matched
|
# This loop walks along the edges of this path that were not matched
|
||||||
# before augmenting.
|
# before augmenting.
|
||||||
for (i, j) in path.edges[0::2]:
|
for (x, y) in path.edges[0::2]:
|
||||||
|
|
||||||
# Augment the non-trivial blossoms on either side of this edge.
|
# Augment the non-trivial blossoms on either side of this edge.
|
||||||
# No action is necessary for trivial blossoms.
|
# No action is necessary for trivial blossoms.
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
if bi != i:
|
if bx != x:
|
||||||
self.augment_blossom(bi, i)
|
self.augment_blossom(bx, x)
|
||||||
|
|
||||||
bj = self.vertex_blossom[j]
|
by = self.vertex_blossom[y]
|
||||||
if bj != j:
|
if by != y:
|
||||||
self.augment_blossom(bj, j)
|
self.augment_blossom(by, y)
|
||||||
|
|
||||||
# Pull the edge into the matching.
|
# Pull the edge into the matching.
|
||||||
self.vertex_mate[i] = j
|
self.vertex_mate[x] = y
|
||||||
self.vertex_mate[j] = i
|
self.vertex_mate[y] = x
|
||||||
|
|
||||||
def assign_label_s(self, i: int) -> None:
|
def assign_label_s(self, x: int) -> None:
|
||||||
"""Assign label S to the unlabeled blossom that contains vertex "i".
|
"""Assign label S to the unlabeled blossom that contains vertex "x".
|
||||||
|
|
||||||
If vertex "i" is matched, it is attached to the alternating tree
|
If vertex "x" is matched, it is attached to the alternating tree
|
||||||
via its matched edge. If vertex "i" is unmatched, it becomes the root
|
via its matched edge. If vertex "x" is unmatched, it becomes the root
|
||||||
of an alternating tree.
|
of an alternating tree.
|
||||||
|
|
||||||
All vertices in the newly labeled blossom are added to the scan queue.
|
All vertices in the newly labeled blossom are added to the scan queue.
|
||||||
|
|
||||||
Precondition:
|
Precondition:
|
||||||
"i" is an unlabeled vertex, either unmatched or matched to
|
"x" is an unlabeled vertex, either unmatched or matched to
|
||||||
a T-vertex via a tight edge.
|
a T-vertex via a tight edge.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Assign label S to the blossom that contains vertex "v".
|
# Assign label S to the blossom that contains vertex "v".
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
assert self.blossom_label[bi] == _LABEL_NONE
|
assert self.blossom_label[bx] == _LABEL_NONE
|
||||||
self.blossom_label[bi] = _LABEL_S
|
self.blossom_label[bx] = _LABEL_S
|
||||||
|
|
||||||
j = self.vertex_mate[i]
|
y = self.vertex_mate[x]
|
||||||
if j == -1:
|
if y == -1:
|
||||||
# Vertex "i" is unmatched.
|
# Vertex "x" is unmatched.
|
||||||
# It must be either a top-level vertex or the base vertex of
|
# It must be either a top-level vertex or the base vertex of
|
||||||
# a top-level blossom.
|
# a top-level blossom.
|
||||||
assert (bi == i) or (self.get_blossom(bi).base_vertex == i)
|
assert (bx == x) or (self.get_blossom(bx).base_vertex == x)
|
||||||
|
|
||||||
# Mark the blossom that contains "v" as root of an alternating tree.
|
# Mark the blossom that contains "v" as root of an alternating tree.
|
||||||
self.blossom_link[bi] = None
|
self.blossom_link[bx] = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Vertex "i" is matched to T-vertex "j".
|
# Vertex "x" is matched to T-vertex "y".
|
||||||
bj = self.vertex_blossom[j]
|
by = self.vertex_blossom[y]
|
||||||
assert self.blossom_label[bj] == _LABEL_T
|
assert self.blossom_label[by] == _LABEL_T
|
||||||
|
|
||||||
# Attach the blossom that contains "i" to the alternating tree.
|
# Attach the blossom that contains "x" to the alternating tree.
|
||||||
self.blossom_link[bi] = (j, i)
|
self.blossom_link[bx] = (y, x)
|
||||||
|
|
||||||
# Initialize the least-slack edge list of the newly labeled blossom.
|
# Initialize the least-slack edge list of the newly labeled blossom.
|
||||||
# This list will be filled by scanning the vertices of the blossom.
|
# This list will be filled by scanning the vertices of the blossom.
|
||||||
self.blossom_best_edge_set[bi] = []
|
self.blossom_best_edge_set[bx] = []
|
||||||
|
|
||||||
# Add all vertices inside the newly labeled S-blossom to the queue.
|
# Add all vertices inside the newly labeled S-blossom to the queue.
|
||||||
if bi == i:
|
if bx == x:
|
||||||
self.queue.append(i)
|
self.queue.append(x)
|
||||||
else:
|
else:
|
||||||
self.queue.extend(self.blossom_vertices(bi))
|
self.queue.extend(self.blossom_vertices(bx))
|
||||||
|
|
||||||
def assign_label_t(self, i: int, j: int) -> None:
|
def assign_label_t(self, x: int, y: int) -> None:
|
||||||
"""Assign label T to the unlabeled blossom that contains vertex "j".
|
"""Assign label T to the unlabeled blossom that contains vertex "y".
|
||||||
|
|
||||||
Attach it to the alternating tree via edge (i, j).
|
Attach it to the alternating tree via edge (x, y).
|
||||||
Then immediately assign label S to the mate of vertex "j".
|
Then immediately assign label S to the mate of vertex "y".
|
||||||
|
|
||||||
Preconditions:
|
Preconditions:
|
||||||
- "i" is an S-vertex.
|
- "x" is an S-vertex.
|
||||||
- "j" is an unlabeled, matched vertex.
|
- "y" is an unlabeled, matched vertex.
|
||||||
- There is a tight edge between vertices "i" and "j".
|
- There is a tight edge between vertices "x" and "y".
|
||||||
"""
|
"""
|
||||||
assert self.blossom_label[self.vertex_blossom[i]] == _LABEL_S
|
assert self.blossom_label[self.vertex_blossom[x]] == _LABEL_S
|
||||||
|
|
||||||
# Assign label T to the unlabeled blossom.
|
# Assign label T to the unlabeled blossom.
|
||||||
bj = self.vertex_blossom[j]
|
by = self.vertex_blossom[y]
|
||||||
assert self.blossom_label[bj] == _LABEL_NONE
|
assert self.blossom_label[by] == _LABEL_NONE
|
||||||
self.blossom_label[bj] = _LABEL_T
|
self.blossom_label[by] = _LABEL_T
|
||||||
self.blossom_link[bj] = (i, j)
|
self.blossom_link[by] = (x, y)
|
||||||
|
|
||||||
# Assign label S to the blossom that contains the mate of vertex "j".
|
# Assign label S to the blossom that contains the mate of vertex "y".
|
||||||
jbase = j if bj == j else self.get_blossom(bj).base_vertex
|
ybase = y if by == y else self.get_blossom(by).base_vertex
|
||||||
k = self.vertex_mate[jbase]
|
z = self.vertex_mate[ybase]
|
||||||
assert k != -1
|
assert z != -1
|
||||||
self.assign_label_s(k)
|
self.assign_label_s(z)
|
||||||
|
|
||||||
def add_s_to_s_edge(self, i: int, j: int) -> Optional[_AlternatingPath]:
|
def add_s_to_s_edge(self, x: int, y: int) -> Optional[_AlternatingPath]:
|
||||||
"""Add the edge between S-vertices "i" and "j".
|
"""Add the edge between S-vertices "x" and "y".
|
||||||
|
|
||||||
If the edge connects blossoms that are part of the same alternating
|
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 None.
|
||||||
|
@ -1187,8 +1187,8 @@ class _MatchingContext:
|
||||||
Augmenting path if found; otherwise None.
|
Augmenting path if found; otherwise None.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Trace back through the alternating trees from "i" and "j".
|
# Trace back through the alternating trees from "x" and "y".
|
||||||
path = self.trace_alternating_paths(i, j)
|
path = self.trace_alternating_paths(x, y)
|
||||||
|
|
||||||
# If the path is a cycle, create a new blossom.
|
# If the path is a cycle, create a new blossom.
|
||||||
# Otherwise the path is an augmenting path.
|
# Otherwise the path is an augmenting path.
|
||||||
|
@ -1221,68 +1221,68 @@ class _MatchingContext:
|
||||||
while self.queue:
|
while self.queue:
|
||||||
|
|
||||||
# Take a vertex from the queue.
|
# Take a vertex from the queue.
|
||||||
i = self.queue.pop()
|
x = self.queue.pop()
|
||||||
|
|
||||||
# Double-check that "v" is an S-vertex.
|
# Double-check that "x" is an S-vertex.
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
assert self.blossom_label[bi] == _LABEL_S
|
assert self.blossom_label[bx] == _LABEL_S
|
||||||
|
|
||||||
# Scan the edges that are incident on "v".
|
# Scan the edges that are incident on "x".
|
||||||
for e in adjacent_edges[i]:
|
for e in adjacent_edges[x]:
|
||||||
(p, q, _w) = edges[e]
|
(p, q, _w) = edges[e]
|
||||||
j = p if p != i else q # TODO : consider abstracting this
|
y = p if p != x else q # TODO : consider abstracting this
|
||||||
|
|
||||||
# Consider the edge between vertices "i" and "j".
|
# Consider the edge between vertices "x" and "y".
|
||||||
# Try to pull this edge into an alternating tree.
|
# Try to pull this edge into an alternating tree.
|
||||||
|
|
||||||
# Note: blossom index of vertex "i" may change during
|
# Note: blossom index of vertex "x" may change during
|
||||||
# this loop, so we need to refresh it here.
|
# this loop, so we need to refresh it here.
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
bj = self.vertex_blossom[j]
|
by = self.vertex_blossom[y]
|
||||||
|
|
||||||
# Ignore edges that are internal to a blossom.
|
# Ignore edges that are internal to a blossom.
|
||||||
if bi == bj:
|
if bx == by:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
jlabel = self.blossom_label[bj]
|
ylabel = self.blossom_label[by]
|
||||||
|
|
||||||
# Check whether this edge is tight (has zero slack).
|
# Check whether this edge is tight (has zero slack).
|
||||||
# Only tight edges may be part of an alternating tree.
|
# Only tight edges may be part of an alternating tree.
|
||||||
slack = self.edge_slack_2x(e)
|
slack = self.edge_slack_2x(e)
|
||||||
if slack <= 0:
|
if slack <= 0:
|
||||||
if jlabel == _LABEL_NONE:
|
if ylabel == _LABEL_NONE:
|
||||||
# Assign label T to the blossom that contains "j".
|
# Assign label T to the blossom that contains "y".
|
||||||
self.assign_label_t(i, j)
|
self.assign_label_t(x, y)
|
||||||
elif jlabel == _LABEL_S:
|
elif ylabel == _LABEL_S:
|
||||||
# This edge connects two S-blossoms. Use it to find
|
# This edge connects two S-blossoms. Use it to find
|
||||||
# either a new blossom or an augmenting path.
|
# either a new blossom or an augmenting path.
|
||||||
alternating_path = self.add_s_to_s_edge(i, j)
|
alternating_path = self.add_s_to_s_edge(x, y)
|
||||||
if alternating_path is not None:
|
if alternating_path is not None:
|
||||||
return alternating_path
|
return alternating_path
|
||||||
|
|
||||||
elif jlabel == _LABEL_S:
|
elif ylabel == _LABEL_S:
|
||||||
# Update tracking of least-slack edges between S-blossoms.
|
# Update tracking of least-slack edges between S-blossoms.
|
||||||
best_edge = self.blossom_best_edge[bi]
|
best_edge = self.blossom_best_edge[bx]
|
||||||
if ((best_edge < 0)
|
if ((best_edge < 0)
|
||||||
or (slack < self.edge_slack_2x(best_edge))):
|
or (slack < self.edge_slack_2x(best_edge))):
|
||||||
self.blossom_best_edge[bi] = e
|
self.blossom_best_edge[bx] = e
|
||||||
|
|
||||||
# Update the list of least-slack edges to S-blossoms for
|
# Update the list of least-slack edges to S-blossoms for
|
||||||
# the blossom that contains "i".
|
# the blossom that contains "x".
|
||||||
# We only do this for non-trivial blossoms.
|
# We only do this for non-trivial blossoms.
|
||||||
if bi != i:
|
if bx != x:
|
||||||
best_edge_set = self.blossom_best_edge_set[bi]
|
best_edge_set = self.blossom_best_edge_set[bx]
|
||||||
assert best_edge_set is not None
|
assert best_edge_set is not None
|
||||||
best_edge_set.append(e)
|
best_edge_set.append(e)
|
||||||
|
|
||||||
if jlabel != _LABEL_S:
|
if ylabel != _LABEL_S:
|
||||||
# Update tracking of least-slack edges from vertex "j" 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.
|
||||||
best_edge = self.vertex_best_edge[j]
|
best_edge = self.vertex_best_edge[y]
|
||||||
if best_edge < 0 or slack < self.edge_slack_2x(best_edge):
|
if best_edge < 0 or slack < self.edge_slack_2x(best_edge):
|
||||||
self.vertex_best_edge[j] = e
|
self.vertex_best_edge[y] = e
|
||||||
|
|
||||||
# No further S vertices to scan, and no augmenting path found.
|
# No further S vertices to scan, and no augmenting path found.
|
||||||
return None
|
return None
|
||||||
|
@ -1312,16 +1312,16 @@ class _MatchingContext:
|
||||||
# Compute delta1: minimum dual variable of any S-vertex.
|
# Compute delta1: minimum dual variable of any S-vertex.
|
||||||
delta_type = 1
|
delta_type = 1
|
||||||
delta_2x = min(
|
delta_2x = min(
|
||||||
self.dual_var_2x[i]
|
self.vertex_dual_2x[x]
|
||||||
for i in range(num_vertex)
|
for x in range(num_vertex)
|
||||||
if self.blossom_label[self.vertex_blossom[i]] == _LABEL_S)
|
if self.blossom_label[self.vertex_blossom[x]] == _LABEL_S)
|
||||||
|
|
||||||
# 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.
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
bi = self.vertex_blossom[i]
|
bx = self.vertex_blossom[x]
|
||||||
if self.blossom_label[bi] == _LABEL_NONE:
|
if self.blossom_label[bx] == _LABEL_NONE:
|
||||||
e = self.vertex_best_edge[i]
|
e = self.vertex_best_edge[x]
|
||||||
if e != -1:
|
if e != -1:
|
||||||
slack = self.edge_slack_2x(e)
|
slack = self.edge_slack_2x(e)
|
||||||
if slack <= delta_2x:
|
if slack <= delta_2x:
|
||||||
|
@ -1368,14 +1368,14 @@ class _MatchingContext:
|
||||||
num_vertex = self.graph.num_vertex
|
num_vertex = self.graph.num_vertex
|
||||||
|
|
||||||
# Apply delta to dual variables of all vertices.
|
# Apply delta to dual variables of all vertices.
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
ilabel = self.blossom_label[self.vertex_blossom[i]]
|
xlabel = self.blossom_label[self.vertex_blossom[x]]
|
||||||
if ilabel == _LABEL_S:
|
if xlabel == _LABEL_S:
|
||||||
# S-vertex: subtract delta from dual variable.
|
# S-vertex: subtract delta from dual variable.
|
||||||
self.dual_var_2x[i] -= delta_2x
|
self.vertex_dual_2x[x] -= delta_2x
|
||||||
elif ilabel == _LABEL_T:
|
elif xlabel == _LABEL_T:
|
||||||
# T-vertex: add delta to dual variable.
|
# T-vertex: add delta to dual variable.
|
||||||
self.dual_var_2x[i] += delta_2x
|
self.vertex_dual_2x[x] += delta_2x
|
||||||
|
|
||||||
# Apply delta to dual variables of top-level non-trivial blossoms.
|
# Apply delta to dual variables of top-level non-trivial blossoms.
|
||||||
for b in range(num_vertex, 2 * num_vertex):
|
for b in range(num_vertex, 2 * num_vertex):
|
||||||
|
@ -1406,9 +1406,9 @@ class _MatchingContext:
|
||||||
num_vertex = self.graph.num_vertex
|
num_vertex = self.graph.num_vertex
|
||||||
|
|
||||||
# Assign label S to all unmatched vertices and put them in the queue.
|
# Assign label S to all unmatched vertices and put them in the queue.
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
if self.vertex_mate[i] == -1:
|
if self.vertex_mate[x] == -1:
|
||||||
self.assign_label_s(i)
|
self.assign_label_s(x)
|
||||||
|
|
||||||
# Stop if all vertices are matched.
|
# Stop if all vertices are matched.
|
||||||
# No further improvement is possible in that case.
|
# No further improvement is possible in that case.
|
||||||
|
@ -1442,16 +1442,16 @@ class _MatchingContext:
|
||||||
if delta_type == 2:
|
if delta_type == 2:
|
||||||
# Use the edge from S-vertex to unlabeled vertex that got
|
# Use the edge from S-vertex to unlabeled vertex that got
|
||||||
# unlocked through the delta update.
|
# unlocked through the delta update.
|
||||||
(i, j, _w) = self.graph.edges[delta_edge]
|
(x, y, _w) = self.graph.edges[delta_edge]
|
||||||
if self.blossom_label[self.vertex_blossom[i]] != _LABEL_S:
|
if self.blossom_label[self.vertex_blossom[x]] != _LABEL_S:
|
||||||
(i, j) = (j, i)
|
(x, y) = (y, x)
|
||||||
self.assign_label_t(i, j)
|
self.assign_label_t(x, y)
|
||||||
|
|
||||||
elif delta_type == 3:
|
elif delta_type == 3:
|
||||||
# Use the S-to-S edge that got unlocked through the delta update.
|
# Use the S-to-S edge that got unlocked through the delta update.
|
||||||
# This may reveal an augmenting path.
|
# This may reveal an augmenting path.
|
||||||
(i, j, _w) = self.graph.edges[delta_edge]
|
(x, y, _w) = self.graph.edges[delta_edge]
|
||||||
augmenting_path = self.add_s_to_s_edge(i, j)
|
augmenting_path = self.add_s_to_s_edge(x, y)
|
||||||
if augmenting_path is not None:
|
if augmenting_path is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -1493,7 +1493,7 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
num_vertex = ctx.graph.num_vertex
|
num_vertex = ctx.graph.num_vertex
|
||||||
|
|
||||||
vertex_mate = ctx.vertex_mate
|
vertex_mate = ctx.vertex_mate
|
||||||
vertex_dual_var_2x = ctx.dual_var_2x
|
vertex_dual_var_2x = ctx.vertex_dual_2x
|
||||||
|
|
||||||
# Extract dual values of blossoms
|
# Extract dual values of blossoms
|
||||||
blossom_dual_var = [
|
blossom_dual_var = [
|
||||||
|
@ -1502,13 +1502,13 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
|
|
||||||
# Double-check that each matching edge actually exists in the graph.
|
# Double-check that each matching edge actually exists in the graph.
|
||||||
num_matched_vertex = 0
|
num_matched_vertex = 0
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
if vertex_mate[i] != -1:
|
if vertex_mate[x] != -1:
|
||||||
num_matched_vertex += 1
|
num_matched_vertex += 1
|
||||||
|
|
||||||
num_matched_edge = 0
|
num_matched_edge = 0
|
||||||
for (i, j, _w) in ctx.graph.edges:
|
for (x, y, _w) in ctx.graph.edges:
|
||||||
if vertex_mate[i] == j:
|
if vertex_mate[x] == y:
|
||||||
num_matched_edge += 1
|
num_matched_edge += 1
|
||||||
|
|
||||||
assert num_matched_vertex == 2 * num_matched_edge
|
assert num_matched_vertex == 2 * num_matched_edge
|
||||||
|
@ -1519,8 +1519,8 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
|
|
||||||
# Count the number of vertices in each blossom.
|
# Count the number of vertices in each blossom.
|
||||||
blossom_nvertex = (2 * num_vertex) * [0]
|
blossom_nvertex = (2 * num_vertex) * [0]
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
b = ctx.blossom_parent[i]
|
b = ctx.blossom_parent[x]
|
||||||
while b != -1:
|
while b != -1:
|
||||||
blossom_nvertex[b] += 1
|
blossom_nvertex[b] += 1
|
||||||
b = ctx.blossom_parent[b]
|
b = ctx.blossom_parent[b]
|
||||||
|
@ -1529,53 +1529,53 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
# Also count the number of matched edges in each blossom.
|
# Also count the number of matched edges in each blossom.
|
||||||
blossom_nmatched = (2 * num_vertex) * [0]
|
blossom_nmatched = (2 * num_vertex) * [0]
|
||||||
|
|
||||||
for (i, j, w) in ctx.graph.edges:
|
for (x, y, w) in ctx.graph.edges:
|
||||||
|
|
||||||
# List blossoms that contain vertex "i".
|
# List blossoms that contain vertex "x".
|
||||||
iblossoms = []
|
xblossoms = []
|
||||||
bi = ctx.blossom_parent[i]
|
bx = ctx.blossom_parent[x]
|
||||||
while bi != -1:
|
while bx != -1:
|
||||||
iblossoms.append(bi)
|
xblossoms.append(bx)
|
||||||
bi = ctx.blossom_parent[bi]
|
bx = ctx.blossom_parent[bx]
|
||||||
|
|
||||||
# List blossoms that contain vertex "j".
|
# List blossoms that contain vertex "y".
|
||||||
jblossoms = []
|
yblossoms = []
|
||||||
bj = ctx.blossom_parent[j]
|
by = ctx.blossom_parent[y]
|
||||||
while bj != -1:
|
while by != -1:
|
||||||
jblossoms.append(bj)
|
yblossoms.append(by)
|
||||||
bj = ctx.blossom_parent[bj]
|
by = ctx.blossom_parent[by]
|
||||||
|
|
||||||
# List blossoms that contain the edge (i, j).
|
# List blossoms that contain the edge (x, y).
|
||||||
edge_blossoms = []
|
edge_blossoms = []
|
||||||
for (bi, bj) in zip(reversed(iblossoms), reversed(jblossoms)):
|
for (bx, by) in zip(reversed(xblossoms), reversed(yblossoms)):
|
||||||
if bi != bj:
|
if bx != by:
|
||||||
break
|
break
|
||||||
edge_blossoms.append(bi)
|
edge_blossoms.append(bx)
|
||||||
|
|
||||||
# Calculate edge slack =
|
# Calculate edge slack =
|
||||||
# dual[i] + dual[j] - weight
|
# dual[x] + dual[y] - weight
|
||||||
# + sum(dual[b] for blossoms "b" containing the edge)
|
# + sum(dual[b] for blossoms "b" containing the edge)
|
||||||
#
|
#
|
||||||
# Multiply weights by 2 to ensure integer values.
|
# Multiply weights by 2 to ensure integer values.
|
||||||
slack = vertex_dual_var_2x[i] + vertex_dual_var_2x[j] - 2 * w
|
slack = vertex_dual_var_2x[x] + vertex_dual_var_2x[y] - 2 * w
|
||||||
slack += 2 * sum(blossom_dual_var[b] for b in edge_blossoms)
|
slack += 2 * sum(blossom_dual_var[b] for b in edge_blossoms)
|
||||||
|
|
||||||
# Check that all edges have non-negative slack.
|
# Check that all edges have non-negative slack.
|
||||||
assert slack >= 0
|
assert slack >= 0
|
||||||
|
|
||||||
# Check that all matched edges have zero slack.
|
# Check that all matched edges have zero slack.
|
||||||
if vertex_mate[i] == j:
|
if vertex_mate[x] == y:
|
||||||
assert slack == 0
|
assert slack == 0
|
||||||
|
|
||||||
# Update number of matched edges in each blossom.
|
# Update number of matched edges in each blossom.
|
||||||
if vertex_mate[i] == j:
|
if vertex_mate[x] == y:
|
||||||
for b in edge_blossoms:
|
for b in edge_blossoms:
|
||||||
blossom_nmatched[b] += 1
|
blossom_nmatched[b] += 1
|
||||||
|
|
||||||
# Check that all unmatched vertices have zero dual.
|
# Check that all unmatched vertices have zero dual.
|
||||||
for i in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
if vertex_mate[i] == -1:
|
if vertex_mate[x] == -1:
|
||||||
assert vertex_dual_var_2x[i] == 0
|
assert vertex_dual_var_2x[x] == 0
|
||||||
|
|
||||||
# Check that all blossoms with positive dual are "full".
|
# Check that all blossoms with positive dual are "full".
|
||||||
# A blossom is full if all except one of its vertices are matched
|
# A blossom is full if all except one of its vertices are matched
|
||||||
|
|
|
@ -55,10 +55,10 @@ def read_dimacs_graph(f: TextIO) -> list[tuple[int, int, int|float]]:
|
||||||
# Handle "edge" line.
|
# Handle "edge" line.
|
||||||
if len(words) != 4:
|
if len(words) != 4:
|
||||||
raise ValueError(f"Expecting edge but got {s.strip()!r}")
|
raise ValueError(f"Expecting edge but got {s.strip()!r}")
|
||||||
i = int(words[1])
|
x = int(words[1])
|
||||||
j = int(words[2])
|
y = int(words[2])
|
||||||
w = parse_int_or_float(words[3])
|
w = parse_int_or_float(words[3])
|
||||||
edges.append((i, j, w))
|
edges.append((x, y, w))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown line type {words[0]!r}")
|
raise ValueError(f"Unknown line type {words[0]!r}")
|
||||||
|
@ -116,9 +116,9 @@ def read_dimacs_matching(
|
||||||
if len(words) != 3:
|
if len(words) != 3:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Expecting matched edge but got {s.strip()}")
|
f"Expecting matched edge but got {s.strip()}")
|
||||||
i = int(words[1])
|
x = int(words[1])
|
||||||
j = int(words[2])
|
y = int(words[2])
|
||||||
pairs.append((i, j))
|
pairs.append((x, y))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown line type {words[0]!r}")
|
raise ValueError(f"Unknown line type {words[0]!r}")
|
||||||
|
@ -152,8 +152,8 @@ def write_dimacs_matching(
|
||||||
else:
|
else:
|
||||||
print("s", f"{weight:.12g}", file=f)
|
print("s", f"{weight:.12g}", file=f)
|
||||||
|
|
||||||
for (i, j) in pairs:
|
for (x, y) in pairs:
|
||||||
print("m", i, j, file=f)
|
print("m", x, y, file=f)
|
||||||
|
|
||||||
|
|
||||||
def write_dimacs_matching_file(
|
def write_dimacs_matching_file(
|
||||||
|
@ -187,8 +187,8 @@ def calc_matching_weight(
|
||||||
break
|
break
|
||||||
edge_pos += 1
|
edge_pos += 1
|
||||||
assert edge_pos <= len(edges)
|
assert edge_pos <= len(edges)
|
||||||
(i, j, w) = edges[edge_pos]
|
(x, y, w) = edges[edge_pos]
|
||||||
assert pair == (i, j)
|
assert pair == (x, y)
|
||||||
weight += w
|
weight += w
|
||||||
edge_pos += 1
|
edge_pos += 1
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue