# tests/testthat/test-assignment-lapmod.R # Tests for LAPMOD sparse LAP solver test_that("lapmod solves simple 3x3 problem correctly", { cost <- matrix(c(4, 2, 5, 3, 3, 6, 7, 5, 4), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") expect_equal(result$method_used, "lapmod") expect_equal(length(result$match), 3) expect_true(all(result$match >= 1 & result$match <= 3)) expect_equal(length(unique(result$match)), 3) # All different columns # Check cost is correct (should be 9: row1->col2 (2) + row2->col1 (3) + row3->col3 (4)) expect_equal(result$total_cost, 9) }) test_that("lapmod handles rectangular matrix (n < m)", { cost <- matrix(c(1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") expect_equal(length(result$match), 3) expect_true(all(result$match >= 1 & result$match <= 4)) expect_equal(length(unique(result$match)), 3) # Optimal: row1->col1 (1) + row2->col2 (1) + row3->col3 (1) = 3 expect_equal(result$total_cost, 3) }) test_that("lapmod handles NA (forbidden) entries", { cost <- matrix(c(NA, 2, 5, 3, NA, 6, 7, 5, NA), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") # Should avoid diagonal (NA entries) expect_true(result$match[1] != 1) expect_true(result$match[2] != 2) expect_true(result$match[3] != 3) }) test_that("lapmod handles Inf (forbidden) entries", { cost <- matrix(c(Inf, 2, 5, 3, Inf, 6, 7, 5, Inf), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") # Should avoid diagonal (Inf entries) expect_true(result$match[1] != 1) expect_true(result$match[2] != 2) expect_true(result$match[3] != 3) }) test_that("lapmod handles sparse matrix (many NA entries)", { # Create a 10x10 matrix with ~70% NA set.seed(42) n <- 10 cost <- matrix(NA_real_, nrow = n, ncol = n) # Fill in ~30% of entries for (i in 1:n) { # Ensure each row has at least 2-3 options cols <- sample(1:n, 3) for (j in cols) { cost[i, j] <- runif(1, 1, 10) } } result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") expect_equal(length(result$match), n) expect_equal(length(unique(result$match)), n) # Verify no NA was chosen for (i in 1:n) { expect_false(is.na(cost[i, result$match[i]])) } }) test_that("lapmod maximization works", { cost <- matrix(c(4, 2, 5, 3, 3, 6, 7, 5, 4), nrow = 3, byrow = TRUE) min_result <- assignment(cost, maximize = FALSE, method = "lapmod") max_result <- assignment(cost, maximize = TRUE, method = "lapmod") expect_lt(min_result$total_cost, max_result$total_cost) # Max should be 18: row1->col3 (5) + row2->col3 (6)... wait need unique # Max should be: row1->col3 (5) + row2->col2 (3)... let me check # Actually: row3->col1 (7) + row2->col3 (6) + row1->col2 (2) = 15? No... # row3->col1 (7) + row2->col3 (6) + row1->col3 can't both have col3 # Maximum unique: row1->col3 (5) + row2->col3 can't... # Let's just check it's bigger expect_true(max_result$total_cost >= min_result$total_cost) }) test_that("lapmod gives same result as JV for dense matrix", { set.seed(123) n <- 20 cost <- matrix(runif(n * n, 1, 100), nrow = n) jv_result <- assignment(cost, method = "jv") lapmod_result <- assignment(cost, method = "lapmod") expect_equal(lapmod_result$total_cost, jv_result$total_cost, tolerance = 1e-9) }) test_that("lapmod gives same result as JV for rectangular matrix", { set.seed(456) n <- 15 m <- 25 cost <- matrix(runif(n * m, 1, 100), nrow = n, ncol = m) jv_result <- assignment(cost, method = "jv") lapmod_result <- assignment(cost, method = "lapmod") expect_equal(lapmod_result$total_cost, jv_result$total_cost, tolerance = 1e-9) }) test_that("lapmod handles single row/column", { # Single row cost1 <- matrix(c(3, 1, 4), nrow = 1) result1 <- assignment(cost1, method = "lapmod") expect_equal(result1$match[1], 2) # Column with minimum cost expect_equal(result1$total_cost, 1) # Single column (will be transposed) # 3x1 matrix: rows compete for 1 column # Row 2 (cost 1) wins cost2 <- matrix(c(3, 1, 4), ncol = 1) result2 <- assignment(cost2, method = "lapmod") expect_equal(result2$match[2], 1) # Row 2 gets column 1 expect_equal(result2$total_cost, 1) }) test_that("lapmod errors on infeasible problem", { # Row with all NA cost <- matrix(c(NA, NA, NA, 1, 2, 3, 4, 5, 6), nrow = 3, byrow = TRUE) expect_error(assignment(cost, method = "lapmod"), "Infeasible") }) test_that("lapmod handles negative costs", { cost <- matrix(c(-4, -2, -5, -3, -3, -6, -7, -5, -4), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") # Minimum with negatives: most negative wins # row3->col1 (-7) + row2->col3 (-6) + row1->col2 (-2) = -15? Let's check # Or: row3->col1 (-7) + row2->col3 (-6) + row1->col3 can't # Valid: row1->col3 (-5) + row2->col3 can't... # row1->col1 (-4) + row2->col2 (-3) + row3->col3 (-4) = -11 # row1->col2 (-2) + row2->col1 (-3) + row3->col3 (-4) = -9 # row1->col2 (-2) + row2->col3 (-6) + row3->col1 (-7) = -15 expect_equal(result$total_cost, -15) }) test_that("lapmod handles mixed positive/negative costs", { cost <- matrix(c(1, -2, 3, -1, 2, -3, 2, -1, 1), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") # Verify against JV jv_result <- assignment(cost, method = "jv") expect_equal(result$total_cost, jv_result$total_cost, tolerance = 1e-9) }) test_that("lapmod is selected by auto for large sparse problems", { set.seed(789) n <- 150 cost <- matrix(NA_real_, nrow = n, ncol = n) # Fill ~30% of entries for (i in 1:n) { cols <- sample(1:n, ceiling(n * 0.3)) for (j in cols) { cost[i, j] <- runif(1, 1, 10) } } result <- assignment(cost, method = "auto") expect_equal(result$method_used, "lapmod") }) test_that("lapmod handles 2x2 correctly", { cost <- matrix(c(1, 2, 3, 4), nrow = 2, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") # Optimal: row1->col1 (1) + row2->col2 (4) = 5 # Or: row1->col2 (2) + row2->col1 (3) = 5 expect_equal(result$total_cost, 5) }) test_that("lapmod handles ties correctly", { cost <- matrix(c(1, 1, 1, 1, 1, 1, 1, 1, 1), nrow = 3, byrow = TRUE) result <- assignment(cost, method = "lapmod") expect_equal(result$status, "optimal") expect_equal(result$total_cost, 3) expect_equal(length(unique(result$match)), 3) # All different columns }) test_that("lapmod benchmark: faster than JV on sparse problems", { skip_if_not_installed("bench") set.seed(999) n <- 500 cost <- matrix(NA_real_, nrow = n, ncol = n) # Fill ~20% of entries (very sparse) for (i in 1:n) { cols <- sample(1:n, ceiling(n * 0.2)) for (j in cols) { cost[i, j] <- runif(1, 1, 100) } } # Just verify it runs - actual benchmarking is optional lapmod_result <- assignment(cost, method = "lapmod") expect_equal(lapmod_result$status, "optimal") })