tests/testthat/test-assignment-auction-gs.R

# Test file for Gauss-Seidel Auction Algorithm
# Save as: tests/testthat/test-assignment-auction-gs.R

test_that("auction_gs works on small square matrix", {
  set.seed(42)
  n <- 5
  M <- matrix(runif(n*n), n, n)
  
  # Compare with other methods
  a_gs <- assignment(M, method = "auction_gs")
  a_jv <- assignment(M, method = "jv")
  
  expect_true(is.list(a_gs))
  expect_true("match" %in% names(a_gs))
  expect_true("total_cost" %in% names(a_gs))
  expect_equal(length(a_gs$match), n)
  
  # Should get same (or very close) cost as JV
  expect_equal(a_gs$total_cost, a_jv$total_cost, tolerance = 1e-6)
})

test_that("auction_gs handles rectangular n < m", {
  set.seed(43)
  n <- 4; m <- 7
  M <- matrix(runif(n*m), n, m)
  
  a_gs <- assignment(M, method = "auction_gs")
  a_ssp <- assignment(M, method = "ssp")
  
  expect_equal(length(a_gs$match), n)
  expect_true(all(a_gs$match >= 1 & a_gs$match <= m))
  expect_equal(a_gs$total_cost, a_ssp$total_cost, tolerance = 1e-6)
})

test_that("auction_gs handles rectangular n > m (auto-transpose)", {
  set.seed(44)
  n <- 8; m <- 4
  M <- matrix(runif(n*m), n, m)
  
  # This should work with auto-transpose
  a_gs <- assignment(M, method = "auction_gs")
  a_ssp <- assignment(M, method = "ssp")
  
  expect_equal(length(a_gs$match), n)
  # When n > m, only m rows can be matched, rest will be 0 (unmatched)
  matched <- a_gs$match[a_gs$match > 0]
  expect_true(all(matched >= 1 & matched <= m))
  expect_equal(sum(a_gs$match > 0), m)  # Exactly m assignments
  expect_equal(a_gs$total_cost, a_ssp$total_cost, tolerance = 1e-6)
})

test_that("auction_gs handles NA values", {
  set.seed(45)
  n <- 5; m <- 7
  M <- matrix(runif(n*m), n, m)
  
  # Set some entries to NA (forbidden)
  M[1, 1:3] <- NA
  M[2, c(2,4,6)] <- NA
  M[5, 1] <- NA
  
  a_gs <- assignment(M, method = "auction_gs")
  a_ssp <- assignment(M, method = "ssp")
  
  # Check that no NA cells were chosen
  for (i in 1:n) {
    j <- a_gs$match[i]
    expect_false(is.na(M[i, j]))
  }
  
  expect_equal(a_gs$total_cost, a_ssp$total_cost, tolerance = 1e-6)
})

test_that("auction_gs handles Inf values", {
  set.seed(46)
  n <- 4; m <- 6
  M <- matrix(runif(n*m), n, m)
  
  # Set some entries to Inf (forbidden)
  M[1, c(1,3,5)] <- Inf
  M[3, 2:4] <- Inf
  
  a_gs <- assignment(M, method = "auction_gs")
  
  # Check that no Inf cells were chosen
  for (i in 1:n) {
    j <- a_gs$match[i]
    expect_true(is.finite(M[i, j]))
  }
})

test_that("auction_gs errors when row has no valid assignments", {
  M <- matrix(c(
    1, 2, 3,
    4, 5, 6,
    NA, NA, NA
  ), nrow = 3, byrow = TRUE)
  
  expect_error(assignment(M, method = "auction_gs"), "Infeasible")
})

test_that("auction_gs maximization works", {
  set.seed(47)
  n <- 5
  M <- matrix(runif(n*n), n, n)
  
  a_max <- assignment(M, maximize = TRUE, method = "auction_gs")
  a_min <- assignment(-M, maximize = FALSE, method = "auction_gs")
  
  expect_equal(a_max$total_cost, -a_min$total_cost, tolerance = 1e-6)
})

test_that("auction_gs with custom epsilon", {
  set.seed(48)
  n <- 4
  M <- matrix(runif(n*n), n, n)
  
  # Try different epsilon values
  a1 <- assignment(M, method = "auction_gs", auction_eps = 1e-6)
  a2 <- assignment(M, method = "auction_gs", auction_eps = 1e-9)
  a3 <- assignment(M, method = "auction_gs", auction_eps = 1e-12)
  
  # All should give valid assignments
  expect_true(all(a1$match >= 1 & a1$match <= n))
  expect_true(all(a2$match >= 1 & a2$match <= n))
  expect_true(all(a3$match >= 1 & a3$match <= n))
  
  # Smaller epsilon should give more accurate results
  expect_lte(abs(a3$total_cost - a2$total_cost), abs(a2$total_cost - a1$total_cost))
})

test_that("auction_gs returns bid count diagnostic", {
  set.seed(49)
  n <- 6
  M <- matrix(runif(n*n), n, n)
  
  result <- lap_solve_auction_gs(M, maximize = FALSE, eps = 1e-6)
  
  expect_true("bids" %in% names(result))
  expect_true(is.numeric(result$bids))
  expect_true(result$bids > 0)
  expect_true(result$bids <= 200000000)  # Should be well below iteration guard
})

test_that("auction_gs compares favorably with standard auction", {
  set.seed(50)
  n <- 10
  M <- matrix(runif(n*n), n, n)
  
  result_gs <- lap_solve_auction_gs(M, maximize = FALSE, eps = 1e-8)
  result_std <- lap_solve_auction(M, maximize = FALSE, eps = 1e-8)
  
  # Should get same cost (within tolerance)
  expect_equal(result_gs$total_cost, result_std$total_cost, tolerance = 1e-6)
  
  # GS often makes fewer bids (though not guaranteed)
  # Just check that both terminated successfully
  expect_true("bids" %in% names(result_gs))
})

test_that("auction_gs works on larger problems", {
  testthat::skip_on_cran()
  
  set.seed(51)
  n <- 100; m <- 150
  M <- matrix(runif(n*m), n, m)
  
  # Add some NA entries
  na_idx <- sample.int(length(M), size = as.integer(0.1 * length(M)))
  M[na_idx] <- NA
  
  # Ensure each row has at least one valid entry
  for (i in 1:n) {
    if (all(is.na(M[i,]))) {
      j <- sample.int(m, 1)
      M[i, j] <- runif(1)
    }
  }
  
  a_gs <- assignment(M, method = "auction_gs")
  
  expect_equal(length(a_gs$match), n)
  expect_true(all(a_gs$match >= 1 & a_gs$match <= m))
  expect_true(all(!is.na(M[cbind(1:n, a_gs$match)])))
})

Try the couplr package in your browser

Any scripts or data that you put into this service are public.

couplr documentation built on Jan. 20, 2026, 5:07 p.m.