Nothing
# Tests to push coverage above 90%
# Targets: network_simplex, cycle_cancel, lap_utils, morph functions
test_that("network_simplex handles 1x1 matrix", {
cost <- matrix(42, nrow = 1, ncol = 1)
result <- assignment(cost, method = "network_simplex")
expect_equal(result$match, 1L)
expect_equal(result$total_cost, 42)
})
test_that("network_simplex handles rectangular matrix (more cols)", {
cost <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3)
result <- assignment(cost, method = "network_simplex")
expect_equal(length(result$match), 2)
expect_true(all(result$match >= 1 & result$match <= 3))
expect_true(result$match[1] != result$match[2])
})
test_that("network_simplex handles dense small matrix", {
set.seed(42)
cost <- matrix(runif(16), nrow = 4, ncol = 4)
result <- assignment(cost, method = "network_simplex")
expect_equal(length(result$match), 4)
expect_true(all(sort(result$match) == 1:4))
})
test_that("network_simplex with forbidden edges", {
cost <- matrix(c(1, NA, NA, 2), nrow = 2, ncol = 2)
result <- assignment(cost, method = "network_simplex")
expect_equal(result$match[1], 1L)
expect_equal(result$match[2], 2L)
})
test_that("cycle_cancel basic functionality", {
skip_on_cran()
cost <- matrix(c(1, 5, 3, 2), nrow = 2, ncol = 2)
result <- assignment(cost, method = "cycle_cancel")
expect_equal(length(result$match), 2)
# Optimal is diagonal: (0,0)=1 + (1,1)=2 = 3
expect_equal(result$total_cost, 3)
})
test_that("cycle_cancel handles rectangular matrices", {
cost <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3)
result <- assignment(cost, method = "cycle_cancel")
expect_equal(length(result$match), 2)
expect_true(all(result$match >= 1 & result$match <= 3))
})
test_that("cycle_cancel handles matrix with forbidden edges", {
cost <- matrix(c(1, NA, NA, 2), nrow = 2, ncol = 2)
result <- assignment(cost, method = "cycle_cancel")
expect_equal(result$match[1], 1L)
expect_equal(result$match[2], 2L)
})
test_that("cycle_cancel with maximize = TRUE", {
cost <- matrix(c(1, 5, 3, 2), nrow = 2, ncol = 2)
result <- assignment(cost, method = "cycle_cancel", maximize = TRUE)
expect_equal(length(result$match), 2)
# Maximum is anti-diagonal: (0,1)=5 + (1,0)=3 = 8
expect_equal(result$total_cost, 8)
})
test_that("cycle_cancel handles 3x3 matrix", {
cost <- matrix(c(1, 2, 3, 4, 5, 6, 7, 8, 9), nrow = 3, ncol = 3)
result <- assignment(cost, method = "cycle_cancel")
expect_equal(length(result$match), 3)
expect_true(all(sort(result$match) == 1:3))
})
test_that("cycle_cancel handles larger matrix", {
set.seed(123)
cost <- matrix(sample(1:100, 25), nrow = 5, ncol = 5)
result <- assignment(cost, method = "cycle_cancel")
expect_equal(length(result$match), 5)
expect_true(all(sort(result$match) == 1:5))
})
test_that("lap_utils compute_total_cost edge cases via assignment", {
skip_on_cran()
# Test with mixed positive/negative costs
cost <- matrix(c(-1, -5, -3, -2), nrow = 2, ncol = 2)
result <- assignment(cost, method = "jv")
expect_true(result$total_cost < 0)
# Test with very large values
cost <- matrix(c(1e10, 1, 1, 1e10), nrow = 2, ncol = 2)
result <- assignment(cost, method = "jv")
expect_equal(result$total_cost, 2)
# Test with zero costs
cost <- matrix(0, nrow = 3, ncol = 3)
result <- assignment(cost, method = "jv")
expect_equal(result$total_cost, 0)
})
test_that("all methods return consistent results on 4x4 matrix", {
skip_on_cran()
set.seed(42)
cost <- matrix(sample(1:20, 16, replace = TRUE), nrow = 4, ncol = 4)
methods <- c("jv", "hungarian", "ssp", "auction", "csflow")
results <- lapply(methods, function(m) {
tryCatch(
assignment(cost, method = m),
error = function(e) NULL
)
})
# All should return results
valid_results <- Filter(Negate(is.null), results)
expect_true(length(valid_results) >= 3)
# All costs should be similar (optimal)
costs <- sapply(valid_results, function(r) r$total_cost)
expect_true(max(costs) - min(costs) < 1e-6)
})
test_that("solve_gabow_tarjan handles edge cases", {
skip_on_cran()
# 2x2 with forbidden edges
cost <- matrix(c(1, Inf, Inf, 2), nrow = 2, ncol = 2)
result <- tryCatch(
assignment(cost, method = "gabow_tarjan"),
error = function(e) list(error = TRUE)
)
if (!isTRUE(result$error)) {
expect_equal(result$match[1], 1L)
expect_equal(result$match[2], 2L)
}
})
test_that("hungarian handles edge cases", {
skip_on_cran()
# Single element
cost <- matrix(100, nrow = 1, ncol = 1)
result <- assignment(cost, method = "hungarian")
expect_equal(result$match, 1L)
expect_equal(result$total_cost, 100)
# Rectangular
cost <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3)
result <- assignment(cost, method = "hungarian")
expect_equal(length(result$match), 2)
})
test_that("ssap_bucket handles edge cases", {
skip_on_cran()
# Matrix with integer costs
cost <- matrix(c(1L, 5L, 3L, 2L), nrow = 2, ncol = 2)
storage.mode(cost) <- "double"
result <- assignment(cost, method = "ssap_bucket")
expect_equal(result$total_cost, 3)
})
test_that("lapmod handles sparse matrices", {
skip_on_cran()
# Matrix with many forbidden entries
cost <- matrix(Inf, nrow = 4, ncol = 4)
diag(cost) <- c(1, 2, 3, 4)
cost[1, 2] <- 10
cost[2, 1] <- 10
result <- tryCatch(
assignment(cost, method = "lapmod"),
error = function(e) list(error = TRUE)
)
if (!isTRUE(result$error)) {
expect_true(result$total_cost <= 11)
}
})
test_that("push_relabel handles various sizes", {
skip_on_cran()
for (n in c(2, 3, 5)) {
set.seed(42 + n)
cost <- matrix(sample(1:50, n * n, replace = TRUE), nrow = n, ncol = n)
result <- assignment(cost, method = "push_relabel")
expect_equal(length(result$match), n)
expect_true(all(sort(result$match) == 1:n))
}
})
test_that("ramshaw_tarjan handles rectangular matrices", {
skip_on_cran()
cost <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3)
result <- assignment(cost, method = "ramshaw_tarjan")
expect_equal(length(result$match), 2)
})
test_that("csa handles medium matrices", {
skip_on_cran()
set.seed(42)
cost <- matrix(runif(36) * 100, nrow = 6, ncol = 6)
result <- assignment(cost, method = "csa")
expect_equal(length(result$match), 6)
expect_true(all(sort(result$match) == 1:6))
})
test_that("orlin handles various sizes", {
skip_on_cran()
for (n in c(3, 4, 5)) {
set.seed(100 + n)
cost <- matrix(sample(1:50, n * n, replace = TRUE), nrow = n, ncol = n)
result <- tryCatch(
assignment(cost, method = "orlin"),
error = function(e) list(error = TRUE)
)
if (!isTRUE(result$error)) {
expect_equal(length(result$match), n)
}
}
})
test_that("hk01 handles binary costs", {
skip_on_cran()
# Diagonal identity - optimal is 0
# Matrix by column: [0,1,1], [1,0,1], [1,1,0]
cost <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3, ncol = 3)
result <- assignment(cost, method = "hk01")
# Should find optimal assignment
expect_equal(length(result$match), 3)
})
test_that("bruteforce handles small matrices", {
skip_on_cran()
cost <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
result <- assignment(cost, method = "bruteforce")
# Matrix is:
# 1 3
# 2 4
# Optimal: row 0->col 0 (1) + row 1->col 1 (4) = 5
# Or: row 0->col 1 (3) + row 1->col 0 (2) = 5
expect_true(result$total_cost <= 5)
})
test_that("lap_solve_kbest works correctly", {
skip_on_cran()
cost <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
# k = 1
result <- lap_solve_kbest(cost, k = 1)
expect_true(nrow(result) >= 1)
# k = 2 (returns expanded format with all edges)
result <- lap_solve_kbest(cost, k = 2)
expect_true(nrow(result) >= 2) # At least 2 unique solutions
})
test_that("bottleneck_assignment works", {
skip_on_cran()
cost <- matrix(c(1, 5, 3, 2), nrow = 2, ncol = 2)
result <- bottleneck_assignment(cost)
expect_equal(length(result$match), 2)
# Should have a bottleneck value
expect_true(!is.null(result$bottleneck))
})
test_that("assignment_duals returns dual variables", {
skip_on_cran()
cost <- matrix(c(1, 5, 3, 2), nrow = 2, ncol = 2)
result <- assignment_duals(cost)
expect_true("u" %in% names(result))
expect_true("v" %in% names(result))
expect_equal(length(result$u), 2)
expect_equal(length(result$v), 2)
})
test_that("sinkhorn works with entropy regularization", {
skip_on_cran()
cost <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
result <- tryCatch(
sinkhorn(cost, epsilon = 0.1),
error = function(e) list(error = TRUE)
)
if (isTRUE(result$error)) {
expect_true(TRUE) # Error with epsilon param is acceptable (may not be supported)
} else {
expect_true("transport_plan" %in% names(result) || "P" %in% names(result))
}
})
# Additional tests for morph utilities
test_that("morph internal utilities work", {
skip_on_cran()
skip_if_not_installed("magick")
# Test .gif_delay_from_fps
delay <- couplr:::.gif_delay_from_fps(10)
expect_equal(delay, 10L)
delay <- couplr:::.gif_delay_from_fps(20)
expect_equal(delay, 5L)
# Invalid FPS defaults to 10
delay <- couplr:::.gif_delay_from_fps(-1)
expect_equal(delay, 10L)
delay <- couplr:::.gif_delay_from_fps(NA)
expect_equal(delay, 10L)
})
test_that("morph array conversions work", {
skip_on_cran()
skip_if_not_installed("magick")
# Create a simple RGB array
arr <- array(as.integer(sample(0:255, 300, replace = TRUE)), dim = c(10, 10, 3))
# Test .to_planar_rgb and .from_planar_rgb round-trip
planar <- couplr:::.to_planar_rgb(arr)
expect_equal(length(planar), 300)
arr2 <- couplr:::.from_planar_rgb(planar, 10, 10)
expect_equal(dim(arr2), c(10, 10, 3))
expect_equal(as.numeric(arr), as.numeric(arr2))
})
test_that("morph .clamp_rgb works", {
skip_on_cran()
# Test clamping
x <- c(-10, 0, 128, 255, 300)
result <- couplr:::.clamp_rgb(x)
expect_equal(result, c(0L, 0L, 128L, 255L, 255L))
# With array
arr <- array(c(-10, 0, 128, 255, 300, 100), dim = c(2, 3))
result <- couplr:::.clamp_rgb(arr)
expect_equal(dim(result), c(2, 3))
})
test_that("greedy_couples strategies work", {
skip_on_cran()
set.seed(42)
n <- 20
left <- data.frame(id = 1:n, x = rnorm(n), y = rnorm(n))
right <- data.frame(id = 1:n, x = rnorm(n), y = rnorm(n))
for (strategy in c("sorted", "row_best", "pq")) {
result <- greedy_couples(left, right, vars = c("x", "y"), strategy = strategy)
expect_true(nrow(result$pairs) > 0)
expect_true("left_id" %in% names(result$pairs))
expect_true("right_id" %in% names(result$pairs))
}
})
test_that("match_couples with various scale options", {
skip_on_cran()
set.seed(42)
n <- 15
left <- data.frame(id = 1:n, x = rnorm(n, 0, 10), y = rnorm(n))
right <- data.frame(id = 1:n, x = rnorm(n, 0, 10), y = rnorm(n))
for (scale in c("none", "standardize", "range", "robust")) {
result <- match_couples(left, right, vars = c("x", "y"), scale = scale)
expect_true(nrow(result$pairs) > 0)
}
})
test_that("match_couples with max_distance", {
skip_on_cran()
set.seed(42)
n <- 10
left <- data.frame(id = 1:n, x = 1:n)
right <- data.frame(id = 1:n, x = (1:n) + 0.1)
# With low max_distance, some pairs may not match
result <- match_couples(left, right, vars = "x", max_distance = 0.5, scale = "none")
# All pairs should have distance < 0.5
expect_true(all(result$pairs$distance < 0.5 | is.na(result$pairs$distance)))
})
test_that("balance_diagnostics works", {
skip_on_cran()
set.seed(42)
n <- 20
left <- data.frame(id = 1:n, x = rnorm(n), y = rnorm(n))
right <- data.frame(id = 1:n, x = rnorm(n) + 0.1, y = rnorm(n) - 0.1)
result <- match_couples(left, right, vars = c("x", "y"))
balance <- balance_diagnostics(result, left, right, c("x", "y"))
expect_true("var_stats" %in% names(balance))
expect_true(nrow(balance$var_stats) == 2)
})
test_that("preprocess_matching_vars handles edge cases", {
skip_on_cran()
# Constant variable
set.seed(42)
left <- data.frame(id = 1:5, x = rep(1, 5), y = rnorm(5))
right <- data.frame(id = 1:5, x = rep(1, 5), y = rnorm(5))
result <- suppressWarnings(preprocess_matching_vars(left, right, c("x", "y")))
# x should be excluded, y should be kept
expect_true("x" %in% result$excluded_vars)
expect_true("y" %in% result$vars)
})
test_that("compute_distances caching works", {
skip_on_cran()
set.seed(42)
n <- 10
left <- data.frame(id = 1:n, x = rnorm(n))
right <- data.frame(id = 1:n, x = rnorm(n))
dist_cache <- compute_distances(left, right, "x", scale = "standardize")
# Check it has the expected class
expect_true(inherits(dist_cache, "distance_object") || inherits(dist_cache, "couplr_distance"))
# Use cached distances
result <- match_couples(dist_cache)
expect_true(nrow(result$pairs) > 0)
})
test_that("join_matched creates merged dataset", {
skip_on_cran()
set.seed(42)
n <- 10
left <- data.frame(id = 1:n, x = rnorm(n), a = letters[1:n])
right <- data.frame(id = 1:n, x = rnorm(n), b = LETTERS[1:n])
result <- match_couples(left, right, vars = "x")
joined <- join_matched(result, left, right)
# Should have columns from both datasets
expect_true(ncol(joined) > 2)
})
test_that("matchmaker creates blocks", {
skip_on_cran()
set.seed(42)
n <- 20
left <- data.frame(id = 1:n, x = rnorm(n), group = rep(c("A", "B"), each = n/2))
right <- data.frame(id = 1:n, x = rnorm(n), group = rep(c("A", "B"), each = n/2))
result <- matchmaker(left, right, block_type = "group", block_by = "group")
expect_true(!is.null(result))
})
# Additional coverage for C++ solvers
test_that("network_simplex handles 5x5 matrix", {
skip_on_cran()
set.seed(999)
cost <- matrix(sample(1:100, 25, replace = TRUE), nrow = 5, ncol = 5)
result <- assignment(cost, method = "network_simplex")
expect_equal(length(result$match), 5)
expect_true(all(sort(result$match) == 1:5))
})
test_that("cycle_cancel handles transposed matrix", {
skip_on_cran()
# Matrix with more cols than rows (needs transpose internally)
cost <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3, byrow = TRUE)
result <- assignment(cost, method = "cycle_cancel")
expect_equal(length(result$match), 2)
})
test_that("gabow_tarjan handles various scenarios", {
skip_on_cran()
# Simple 2x2
cost <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
result <- assignment(cost, method = "gabow_tarjan")
expect_equal(length(result$match), 2)
# 4x4 with pattern
cost <- matrix(rep(5, 16), nrow = 4, ncol = 4)
diag(cost) <- 1
result <- assignment(cost, method = "gabow_tarjan")
expect_equal(result$total_cost, 4)
})
test_that("various solvers handle identical costs", {
skip_on_cran()
cost <- matrix(rep(10, 9), nrow = 3, ncol = 3)
for (method in c("jv", "hungarian", "auction", "ssp", "network_simplex")) {
result <- tryCatch(
assignment(cost, method = method),
error = function(e) NULL
)
if (!is.null(result)) {
expect_equal(result$total_cost, 30) # 3 * 10
}
}
})
test_that("solvers handle negative costs", {
skip_on_cran()
cost <- matrix(c(-10, -1, -1, -10), nrow = 2, ncol = 2)
result <- assignment(cost, method = "jv")
expect_true(result$total_cost < 0)
expect_equal(result$total_cost, -20)
result <- assignment(cost, method = "hungarian")
expect_equal(result$total_cost, -20)
})
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.