1
0
Fork 0

Use (x, y) instead of (i, j)

This commit is contained in:
Joris van Rantwijk 2023-02-07 11:23:42 +01:00
parent d063bbd45a
commit 0ad3020425
2 changed files with 265 additions and 265 deletions

View File

@ -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

View File

@ -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