tests/testthat/test-coverage-push-90.R

# Tests to push coverage above 90%
# Targets: network_simplex, cycle_cancel, gabow_tarjan edge cases

# ---------- network_simplex coverage ----------

test_that("network_simplex handles various matrix sizes", {
  skip_on_cran()

  # Square matrices of different sizes
  for (n in c(2, 3, 5, 7)) {
    set.seed(n * 42)
    cost <- matrix(sample(1:100, n * n, replace = TRUE), n, n)
    result <- assignment(cost, method = "network_simplex")

    expect_equal(length(result$match), n)
    expect_true(all(result$match >= 1 & result$match <= n))
    expect_true(result$total_cost > 0)
  }
})

test_that("network_simplex handles rectangular matrices (more cols)", {
  skip_on_cran()

  # More columns than rows (valid case)
  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))
})

test_that("network_simplex handles more rows than cols", {
  skip_on_cran()

  # More rows than columns - infeasible case
  cost <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 3, ncol = 2)

  # Should either handle gracefully or error
  result <- tryCatch(
    assignment(cost, method = "network_simplex"),
    error = function(e) list(error = TRUE, msg = e$message)
  )

  # Either returns infeasible or handles via transposition
  expect_true(!is.null(result))
})

test_that("network_simplex handles uniform costs", {
  skip_on_cran()

  cost <- matrix(5, nrow = 4, ncol = 4)
  result <- assignment(cost, method = "network_simplex")

  expect_equal(length(result$match), 4)
  expect_equal(result$total_cost, 20)  # 4 * 5
})

test_that("network_simplex handles zero diagonal", {
  skip_on_cran()

  cost <- matrix(10, nrow = 3, ncol = 3)
  diag(cost) <- 0

  result <- assignment(cost, method = "network_simplex")
  expect_equal(result$total_cost, 0)  # Should use diagonal
})

# ---------- cycle_cancel coverage ----------

test_that("cycle_cancel handles various sizes", {
  skip_on_cran()

  for (n in c(2, 3, 4, 5, 6)) {
    set.seed(n * 100)
    cost <- matrix(runif(n * n, 1, 100), n, n)
    result <- assignment(cost, method = "cycle_cancel")

    expect_equal(length(result$match), n)
    expect_true(result$total_cost > 0)
  }
})

test_that("cycle_cancel handles rectangular (more cols)", {
  skip_on_cran()

  cost <- matrix(c(1, 5, 3, 2, 4, 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 rectangular (more rows)", {
  skip_on_cran()

  # This triggers the transpose path
  cost <- matrix(c(1, 5, 3, 2, 4, 6), nrow = 3, ncol = 2)
  result <- assignment(cost, method = "cycle_cancel")

  # After transpose, we get n=2 assignments
  expect_equal(length(result$match), 3)
})

test_that("cycle_cancel handles maximize", {
  skip_on_cran()

  cost <- matrix(c(1, 10, 8, 2), nrow = 2, ncol = 2)
  result <- assignment(cost, method = "cycle_cancel", maximize = TRUE)

  expect_true(result$total_cost >= 10)  # Should get high cost
})

test_that("cycle_cancel handles sparse costs with Inf", {
  skip_on_cran()

  cost <- matrix(c(1, Inf, Inf, 2, 3, Inf, Inf, 4, 5), nrow = 3, ncol = 3)
  result <- tryCatch(
    assignment(cost, method = "cycle_cancel"),
    error = function(e) list(error = TRUE)
  )

  expect_true(!is.null(result))
})

# ---------- gabow_tarjan edge cases ----------

test_that("gabow_tarjan handles various sizes", {
  skip_on_cran()

  for (n in c(2, 3, 4, 5, 6, 8)) {
    set.seed(n * 77)
    cost <- matrix(sample(1:50, n * n, replace = TRUE), n, n)
    result <- assignment(cost, method = "gabow_tarjan")

    expect_equal(length(result$match), n)
    expect_true(all(sort(result$match) == 1:n))
  }
})

test_that("gabow_tarjan handles negative costs", {
  skip_on_cran()

  cost <- matrix(c(-5, -1, -2, -10), nrow = 2, ncol = 2)
  result <- assignment(cost, method = "gabow_tarjan")

  expect_true(result$total_cost < 0)
})

test_that("gabow_tarjan handles uniform costs", {
  skip_on_cran()

  cost <- matrix(7, nrow = 3, ncol = 3)
  result <- assignment(cost, method = "gabow_tarjan")

  expect_equal(result$total_cost, 21)  # 3 * 7
})

test_that("gabow_tarjan handles rectangular", {
  skip_on_cran()

  # More columns than rows
  cost <- matrix(c(1, 5, 3, 2, 4, 6), nrow = 2, ncol = 3)
  result <- assignment(cost, method = "gabow_tarjan")

  expect_equal(length(result$match), 2)
})

test_that("gabow_tarjan with forbidden edges", {
  skip_on_cran()

  # Diagonal forbidden
  cost <- matrix(c(Inf, 1, 2, 3, Inf, 4, 5, 6, Inf), nrow = 3, ncol = 3)
  result <- assignment(cost, method = "gabow_tarjan")

  # Should find assignment avoiding diagonal
  expect_equal(length(result$match), 3)
  for (i in 1:3) {
    expect_false(result$match[i] == i)  # Not on diagonal
  }
})

# ---------- Additional solver coverage ----------

test_that("csflow handles various sizes", {
  skip_on_cran()

  for (n in c(2, 3, 4, 6)) {
    set.seed(n * 55)
    cost <- matrix(sample(1:100, n * n, replace = TRUE), n, n)
    result <- assignment(cost, method = "csflow")

    expect_equal(length(result$match), n)
  }
})

test_that("ssp handles various sizes", {
  skip_on_cran()

  for (n in c(2, 3, 4, 5)) {
    set.seed(n * 66)
    cost <- matrix(sample(1:50, n * n, replace = TRUE), n, n)
    result <- assignment(cost, method = "ssp")

    expect_equal(length(result$match), n)
  }
})

test_that("orlin handles various sizes", {
  skip_on_cran()

  for (n in c(3, 4, 5, 6)) {
    set.seed(n * 88)
    cost <- matrix(sample(1:80, n * n, replace = TRUE), n, n)
    result <- assignment(cost, method = "orlin")

    expect_equal(length(result$match), n)
  }
})

test_that("push_relabel handles various sizes", {
  skip_on_cran()

  for (n in c(2, 3, 4, 5)) {
    set.seed(n * 99)
    cost <- matrix(sample(1:60, n * n, replace = TRUE), n, n)
    result <- assignment(cost, method = "push_relabel")

    expect_equal(length(result$match), n)
  }
})

# ---------- lap_utils coverage ----------

test_that("lap_solve with all methods succeeds", {
  skip_on_cran()

  cost <- matrix(c(5, 2, 3, 7, 1, 8, 4, 6, 9), nrow = 3, ncol = 3)

  methods <- c("hungarian", "jv", "auction", "ssp", "csflow",
               "network_simplex", "cycle_cancel", "gabow_tarjan")

  for (method in methods) {
    result <- tryCatch(
      lap_solve(cost, method = method),
      error = function(e) NULL
    )

    if (!is.null(result)) {
      expect_equal(nrow(result), 3)
    }
  }
})

# ---------- morph_pixel coverage ----------

test_that("pixel_morph with color_walk mode", {
  skip_on_cran()
  skip_if_not_installed("magick")
  skip_if_not_installed("png")

  imgA <- system.file("extdata/icons/circleA_40.png", package = "couplr")
  imgB <- system.file("extdata/icons/circleB_40.png", package = "couplr")

  skip_if(!nzchar(imgA), "Test images not found")
  skip_if(!nzchar(imgB), "Test images not found")

  # Test color_walk mode (for images larger than exact threshold)
  result <- tryCatch(
    pixel_morph_animate(imgA, imgB, mode = "color_walk",
                        n_frames = 3, show = FALSE, upscale = 1),
    error = function(e) NULL
  )

  # Should produce frames
  if (!is.null(result)) {
    expect_true(inherits(result, "magick-image") || is.list(result))
  }
})

# ---------- matching_core edge cases ----------

test_that("match_couples with blocking", {
  skip_on_cran()

  set.seed(42)
  n <- 8
  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))

  # Create block IDs for exact matching within groups
  blocks <- matchmaker(left, right, block_type = "group", block_by = "group")

  # Match within blocks
  result <- match_couples(left, right, vars = "x",
                          block_id = blocks$block_id)

  expect_true(nrow(result$pairs) > 0)
})

test_that("match_couples with max_distance constraint", {
  skip_on_cran()

  set.seed(42)
  n <- 10
  # Create overlapping distributions with some variation
  left <- data.frame(id = 1:n, x = rnorm(n, 0, 1))
  right <- data.frame(id = 1:n, x = rnorm(n, 0.5, 1))

  # Moderate max_distance should work
  result <- match_couples(left, right, vars = "x",
                          max_distance = 3, auto_scale = TRUE)

  # Should match
  expect_true(nrow(result$pairs) > 0)
})

test_that("greedy_couples with all strategies", {
  skip_on_cran()

  set.seed(42)
  n <- 15
  left <- data.frame(id = 1:n, x = rnorm(n), y = rnorm(n))
  right <- data.frame(id = 1:n, x = rnorm(n), y = rnorm(n))

  strategies <- c("sorted", "row_best", "pq")

  for (strat in strategies) {
    result <- greedy_couples(left, right, vars = c("x", "y"),
                             strategy = strat)
    expect_equal(nrow(result$pairs), n)
  }
})

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.