Nothing
# test-gabow_tarjan_module_g.R
# Tests for Module G: scale_match wrapper for bit-scaling outer loop
library(testthat)
# Helper function to compute total matching cost
total_cost <- function(cost, row_match) {
n <- nrow(cost)
total <- 0
for (i in seq_len(n)) {
j <- row_match[i]
if (j > 0) {
total <- total + cost[i, j]
}
}
total
}
# Helper to check if matching is perfect
is_perfect_matching <- function(row_match) {
all(row_match > 0)
}
# Helper to check matching consistency
check_matching_consistency <- function(row_match, col_match) {
n <- length(row_match)
m <- length(col_match)
for (i in seq_len(n)) {
j <- row_match[i]
if (j > 0) {
if (j > m || col_match[j] != i) {
return(FALSE)
}
}
}
for (j in seq_len(m)) {
i <- col_match[j]
if (i > 0) {
if (i > n || row_match[i] != j) {
return(FALSE)
}
}
}
TRUE
}
# Helper to check 1-feasibility
check_one_feasible <- function(cost, row_match, col_match, y_u, y_v) {
n <- nrow(cost)
m <- ncol(cost)
BIG_INT <- 1e15
for (i in seq_len(n)) {
for (j in seq_len(m)) {
c_ij <- cost[i, j]
# Skip forbidden edges
if (is.infinite(c_ij) || c_ij >= BIG_INT) next
sum_duals <- y_u[i] + y_v[j]
matched <- (row_match[i] == j && col_match[j] == i)
# Condition 1: y_u[i] + y_v[j] <= c(i,j) + 1
if (sum_duals > c_ij + 1 + 1e-9) {
return(FALSE)
}
# Condition 2: for matched edges, y_u[i] + y_v[j] >= c(i,j)
if (matched && sum_duals < c_ij - 1e-9) {
return(FALSE)
}
}
}
TRUE
}
test_that("scale_match finds optimal matching on simple 3x3 matrix", {
# Known optimal solution: matching (0->1, 1->0, 2->2) with cost 5
cost <- matrix(c(
4, 1, 3,
2, 0, 5,
3, 2, 2
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
# Check perfect matching
expect_true(is_perfect_matching(result$row_match))
# Check consistency
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Check optimal cost
expect_equal(total_cost(cost, result$row_match), 5)
# Check specific matching (may vary but cost should be same)
actual_cost <- total_cost(cost, result$row_match)
expect_equal(actual_cost, 5)
# Check 1-feasibility
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match is idempotent on optimal solution", {
cost <- matrix(c(
4, 1, 3,
2, 0, 5,
3, 2, 2
), nrow = 3, byrow = TRUE)
# First call
result1 <- couplr:::scale_match_cpp(cost)
cost1 <- total_cost(cost, result1$row_match)
# Second call starting from result1
result2 <- couplr:::scale_match_cpp(
cost,
row_match = result1$row_match,
col_match = result1$col_match,
y_u = result1$y_u,
y_v = result1$y_v
)
cost2 <- total_cost(cost, result2$row_match)
# Cost should remain optimal
expect_equal(cost2, cost1)
expect_equal(cost2, 5)
# Matching should still be perfect
expect_true(is_perfect_matching(result2$row_match))
# Should still be 1-feasible
expect_true(check_one_feasible(cost, result2$row_match, result2$col_match,
result2$y_u, result2$y_v))
})
test_that("scale_match handles 2x2 matrices correctly", {
# Simple 2x2 case
cost <- matrix(c(
1, 2,
4, 3
), nrow = 2, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Optimal matching is (0->0, 1->1) with cost 4
expect_equal(total_cost(cost, result$row_match), 4)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles identity-like costs", {
# Identity-style matrix (diagonal is cheapest)
cost <- matrix(c(
1, 5, 5,
5, 1, 5,
5, 5, 1
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Optimal cost is 3 (diagonal)
expect_equal(total_cost(cost, result$row_match), 3)
# Matching should be diagonal
expect_equal(result$row_match, c(1, 2, 3))
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles anti-diagonal costs", {
# Anti-diagonal is cheapest
cost <- matrix(c(
5, 5, 1,
5, 1, 5,
1, 5, 5
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Optimal cost is 3 (anti-diagonal)
expect_equal(total_cost(cost, result$row_match), 3)
# Matching should be anti-diagonal
expect_equal(result$row_match, c(3, 2, 1))
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles uniform costs", {
# All edges have same cost
cost <- matrix(5, nrow = 3, ncol = 3)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Any perfect matching has cost 15
expect_equal(total_cost(cost, result$row_match), 15)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles large cost differences", {
# Mix of very different costs
cost <- matrix(c(
1, 1000, 1000,
1000, 1, 1000,
1000, 1000, 1
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Should match diagonal with cost 3
expect_equal(total_cost(cost, result$row_match), 3)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles 4x4 matrix", {
# Note: sum(row minimums) is NOT an upper bound on optimal cost
# because the minimum in each row might map to the same column
cost <- matrix(c(
10, 19, 8, 15,
10, 18, 7, 17,
13, 16, 9, 14,
12, 19, 8, 18
), nrow = 4, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Verify we get a valid optimal matching
# The test checks we get a perfect matching with valid cost
actual_cost <- total_cost(cost, result$row_match)
expect_true(actual_cost > 0)
expect_true(is.finite(actual_cost))
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles 5x5 matrix", {
cost <- matrix(c(
7, 2, 1, 9, 4,
9, 6, 9, 5, 5,
3, 8, 3, 1, 8,
7, 9, 4, 2, 2,
8, 4, 7, 4, 8
), nrow = 5, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Should find some perfect matching
actual_cost <- total_cost(cost, result$row_match)
expect_true(actual_cost > 0)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles negative costs", {
# Matrix with negative costs
cost <- matrix(c(
-1, 5, 3,
4, -2, 6,
2, 1, -3
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Optimal is diagonal: -1 + -2 + -3 = -6
expect_equal(total_cost(cost, result$row_match), -6)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match handles mix of positive and negative costs", {
cost <- matrix(c(
10, -5, 3,
-2, 8, -1,
4, 1, 6
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Should find optimal matching
actual_cost <- total_cost(cost, result$row_match)
# Verify it's a reasonable cost (should include negative costs)
expect_true(actual_cost < 20) # Much less than sum of all positive values
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match with pre-initialized duals converges correctly", {
cost <- matrix(c(
4, 1, 3,
2, 0, 5,
3, 2, 2
), nrow = 3, byrow = TRUE)
# Start with some non-zero duals
y_u <- c(1, 2, 1)
y_v <- c(1, 0, 1)
result <- couplr:::scale_match_cpp(
cost,
y_u = y_u,
y_v = y_v
)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Should still find optimal cost
expect_equal(total_cost(cost, result$row_match), 5)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match with partial matching converges correctly", {
cost <- matrix(c(
4, 1, 3,
2, 0, 5,
3, 2, 2
), nrow = 3, byrow = TRUE)
# Start with partial matching (only first row matched)
row_match <- c(2, 0, 0) # row 0 matched to col 1
col_match <- c(0, 1, 0) # col 1 matched to row 0
result <- couplr:::scale_match_cpp(
cost,
row_match = row_match,
col_match = col_match
)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Should complete to optimal matching
expect_equal(total_cost(cost, result$row_match), 5)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match produces consistent duals across multiple calls", {
cost <- matrix(c(
10, 20, 30,
15, 25, 35,
20, 30, 40
), nrow = 3, byrow = TRUE)
# First call
result1 <- couplr:::scale_match_cpp(cost)
# Second call with first result as input
result2 <- couplr:::scale_match_cpp(
cost,
row_match = result1$row_match,
col_match = result1$col_match,
y_u = result1$y_u,
y_v = result1$y_v
)
# Matching should be identical (or at least same cost)
expect_equal(total_cost(cost, result2$row_match),
total_cost(cost, result1$row_match))
# Both should be 1-feasible
expect_true(check_one_feasible(cost, result1$row_match, result1$col_match,
result1$y_u, result1$y_v))
expect_true(check_one_feasible(cost, result2$row_match, result2$col_match,
result2$y_u, result2$y_v))
})
test_that("scale_match handles zero costs correctly", {
cost <- matrix(c(
0, 1, 2,
1, 0, 1,
2, 1, 0
), nrow = 3, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
# Optimal is diagonal with cost 0
expect_equal(total_cost(cost, result$row_match), 0)
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
test_that("scale_match dual updates are cumulative", {
cost <- matrix(c(
4, 1, 3,
2, 0, 5,
3, 2, 2
), nrow = 3, byrow = TRUE)
# First call with zero duals
result1 <- couplr:::scale_match_cpp(cost)
# Duals should be non-zero after finding optimal matching
expect_true(any(result1$y_u != 0) || any(result1$y_v != 0))
# Second call - duals should accumulate (or stay same if already optimal)
result2 <- couplr:::scale_match_cpp(
cost,
row_match = result1$row_match,
col_match = result1$col_match,
y_u = result1$y_u,
y_v = result1$y_v
)
# The global duals satisfy complementary slackness for original cost
for (i in 1:nrow(cost)) {
j <- result2$row_match[i]
if (j > 0) {
# Matched edge should be tight: y_u[i] + y_v[j] = cost[i,j]
# (up to the cost-length adjustment)
expect_true(result2$y_u[i] + result2$y_v[j] >= cost[i, j] - 1e-9)
expect_true(result2$y_u[i] + result2$y_v[j] <= cost[i, j] + 1 + 1e-9)
}
}
})
test_that("scale_match handles rectangular matrices (more rows than cols)", {
# 4x3 matrix - not all rows can be matched to distinct columns
# The algorithm will pad with dummy columns internally
cost <- matrix(c(
1, 2, 3,
4, 5, 6,
7, 8, 9,
10, 11, 12
), nrow = 4, byrow = TRUE)
result <- couplr:::scale_match_cpp(cost)
# Should find a perfect matching (all rows matched)
expect_true(is_perfect_matching(result$row_match))
# Some rows may be matched to dummy columns (values > ncol(cost))
# Real columns should be 1, 2, 3; dummy columns would be 4+
real_matches <- sum(result$row_match <= 3)
dummy_matches <- sum(result$row_match > 3)
# Exactly 3 rows can match to real columns, 1 must match to dummy
expect_equal(real_matches, 3)
expect_equal(dummy_matches, 1)
# col_match should only have entries for real columns (size 3)
expect_equal(length(result$col_match), 3)
# y_v should only have entries for real columns (size 3)
expect_equal(length(result$y_v), 3)
# Verify consistency for real column assignments
for (i in seq_len(4)) {
j <- result$row_match[i]
if (j <= 3) { # Real column
# Column should point back to this row
expect_equal(result$col_match[j], i)
}
}
# The row matched to the dummy column should be the most expensive
# since dummy columns have cost BIG_INT
dummy_row <- which(result$row_match > 3)
expect_equal(length(dummy_row), 1)
})
test_that("scale_match performance on larger matrix (10x10)", {
skip_on_cran()
set.seed(123)
n <- 10
cost <- matrix(sample(1:100, n*n, replace = TRUE), nrow = n)
result <- couplr:::scale_match_cpp(cost)
expect_true(is_perfect_matching(result$row_match))
expect_true(check_matching_consistency(result$row_match, result$col_match))
expect_true(check_one_feasible(cost, result$row_match, result$col_match,
result$y_u, result$y_v))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.