Raise MatchingFailed when verify fails
This commit is contained in:
parent
f1a60febe7
commit
be0f5c3881
|
@ -45,6 +45,8 @@ def maximum_weight_matching(
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the input does not satisfy the constraints.
|
ValueError: If the input does not satisfy the constraints.
|
||||||
TypeError: If the input contains invalid data types.
|
TypeError: If the input contains invalid data types.
|
||||||
|
MatchingError: If the matching algorithm fails.
|
||||||
|
This can only happen if there is a bug in the algorithm.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check that the input meets all constraints.
|
# Check that the input meets all constraints.
|
||||||
|
@ -171,6 +173,14 @@ def adjust_weights_for_maximum_cardinality_matching(
|
||||||
return [(x, y, w + delta) for (x, y, w) in edges]
|
return [(x, y, w + delta) for (x, y, w) in edges]
|
||||||
|
|
||||||
|
|
||||||
|
class MatchingError(Exception):
|
||||||
|
"""Raised when verification of the matching fails.
|
||||||
|
|
||||||
|
This can only happen if there is a bug in the algorithm.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _check_input_types(edges: list[tuple[int, int, int|float]]) -> None:
|
def _check_input_types(edges: list[tuple[int, int, int|float]]) -> None:
|
||||||
"""Check that the input consists of valid data types and valid
|
"""Check that the input consists of valid data types and valid
|
||||||
numerical ranges.
|
numerical ranges.
|
||||||
|
@ -1669,7 +1679,7 @@ def _verify_blossom_edges(
|
||||||
dual variable are "full".
|
dual variable are "full".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: If a blossom with non-zero dual is not full.
|
MatchingError: If a blossom with non-zero dual is not full.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
num_vertex = ctx.graph.num_vertex
|
num_vertex = ctx.graph.num_vertex
|
||||||
|
@ -1753,7 +1763,12 @@ def _verify_blossom_edges(
|
||||||
# matched to another vertex in the blossom.
|
# matched to another vertex in the blossom.
|
||||||
if blossom.dual_var > 0:
|
if blossom.dual_var > 0:
|
||||||
blossom_num_matched = path_num_matched[depth]
|
blossom_num_matched = path_num_matched[depth]
|
||||||
assert blossom_num_vertex == 2 * blossom_num_matched + 1
|
if blossom_num_vertex != 2 * blossom_num_matched + 1:
|
||||||
|
raise MatchingError(
|
||||||
|
"Verification failed:"
|
||||||
|
f" blossom with dual={blossom.dual_var}"
|
||||||
|
f" nvertex={blossom_num_vertex}"
|
||||||
|
f" nmatched={blossom_num_matched}")
|
||||||
|
|
||||||
# Update the number of matched edges in the parent blossom to
|
# Update the number of matched edges in the parent blossom to
|
||||||
# take into account the matched edges in this blossom.
|
# take into account the matched edges in this blossom.
|
||||||
|
@ -1778,17 +1793,21 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
This function takes time O(n**2).
|
This function takes time O(n**2).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: If the solution is not optimal.
|
MatchingError: If the solution is not optimal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
num_vertex = ctx.graph.num_vertex
|
num_vertex = ctx.graph.num_vertex
|
||||||
num_edge = len(ctx.graph.edges)
|
num_edge = len(ctx.graph.edges)
|
||||||
|
|
||||||
# Double-check that each matched edge actually exists in the graph.
|
# Check that each matched edge actually exists in the graph.
|
||||||
num_matched_vertex = 0
|
num_matched_vertex = 0
|
||||||
for x in range(num_vertex):
|
for x in range(num_vertex):
|
||||||
if ctx.vertex_mate[x] != -1:
|
y = ctx.vertex_mate[x]
|
||||||
assert ctx.vertex_mate[ctx.vertex_mate[x]] == x
|
if y != -1:
|
||||||
|
if ctx.vertex_mate[y] != x:
|
||||||
|
raise MatchingError(
|
||||||
|
"Verification failed:"
|
||||||
|
f" asymmetric match of vertex {x} and {y}")
|
||||||
num_matched_vertex += 1
|
num_matched_vertex += 1
|
||||||
|
|
||||||
num_matched_edge = 0
|
num_matched_edge = 0
|
||||||
|
@ -1796,12 +1815,17 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
if ctx.vertex_mate[x] == y:
|
if ctx.vertex_mate[x] == y:
|
||||||
num_matched_edge += 1
|
num_matched_edge += 1
|
||||||
|
|
||||||
assert num_matched_vertex == 2 * num_matched_edge
|
if num_matched_vertex != 2 * num_matched_edge:
|
||||||
|
raise MatchingError(
|
||||||
|
f"Verification failed: {num_matched_vertex} matched vertices"
|
||||||
|
f" inconsistent with {num_matched_edge} matched edges")
|
||||||
|
|
||||||
# Check that all dual variables are non-negative.
|
# Check that all dual variables are non-negative.
|
||||||
assert min(ctx.vertex_dual_2x) >= 0
|
assert min(ctx.vertex_dual_2x) >= 0
|
||||||
for blossom in ctx.nontrivial_blossom:
|
for blossom in ctx.nontrivial_blossom:
|
||||||
assert blossom.dual_var >= 0
|
if blossom.dual_var < 0:
|
||||||
|
raise MatchingError("Verification failed:"
|
||||||
|
f" negative blossom dual {blossom.dual_var}")
|
||||||
|
|
||||||
# Calculate the slack of each edge.
|
# Calculate the slack of each edge.
|
||||||
# A correction will be needed for edges inside blossoms.
|
# A correction will be needed for edges inside blossoms.
|
||||||
|
@ -1819,12 +1843,17 @@ def _verify_optimum(ctx: _MatchingContext) -> None:
|
||||||
|
|
||||||
# We now know the correct slack of each edge.
|
# We now know the correct slack of each edge.
|
||||||
# Check that all edges have non-negative slack.
|
# Check that all edges have non-negative slack.
|
||||||
assert min(edge_slack_2x) >= 0
|
min_edge_slack = min(edge_slack_2x)
|
||||||
|
if min_edge_slack < 0:
|
||||||
|
raise MatchingError(
|
||||||
|
f"Verification failed: negative edge slack {min_edge_slack/2}")
|
||||||
|
|
||||||
# Check that all matched edges have zero slack.
|
# Check that all matched edges have zero slack.
|
||||||
for e in range(num_edge):
|
for e in range(num_edge):
|
||||||
(x, y, _w) = ctx.graph.edges[e]
|
(x, y, _w) = ctx.graph.edges[e]
|
||||||
if ctx.vertex_mate[x] == y:
|
if ctx.vertex_mate[x] == y and edge_slack_2x[e] != 0:
|
||||||
assert edge_slack_2x[e] == 0
|
raise MatchingError(
|
||||||
|
"Verification failed:"
|
||||||
|
f" matched edge ({x}, {y}) has slack {edge_slack_2x[e]/2}")
|
||||||
|
|
||||||
# Optimum solution confirmed.
|
# Optimum solution confirmed.
|
||||||
|
|
Loading…
Reference in New Issue