tests/testthat/test-lifecycle.R

context("ECOS Lifecycle API")

## Helper: load a test problem and construct sparse G matrix
load_problem <- function(name) {
    d <- readRDS(system.file("testdata", name, package = "ECOSolveR"))
    G <- Matrix::sparseMatrix(x = d$Gpr, i = d$Gir, p = d$Gjc,
                              dims = c(d$m, d$n), index1 = FALSE)
    dims <- lapply(list(l = d$l, q = d$q, e = d$e), as.integer)
    if (!is.null(d$Apr) && length(d$Apr) > 0) {
        A <- Matrix::sparseMatrix(x = d$Apr, i = d$Air, p = d$Ajc,
                                  dims = c(d$p, d$n), index1 = FALSE)
        b <- d$b
    } else {
        A <- NULL
        b <- numeric(0)
    }
    list(c = d$c, G = G, h = d$h, dims = dims, A = A, b = b, raw = d)
}

## -----------------------------------------------------------------
## 1. Basic lifecycle: setup -> solve -> cleanup
## -----------------------------------------------------------------
test_that("basic lifecycle works", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    on.exit(ECOS_cleanup(ws), add = TRUE)
    expect_s3_class(ws, "ecos_workspace")

    res <- ECOS_solve(ws)
    expect_equal(res$retcodes[["exitFlag"]], 0L)
    expect_equal(res$infostring, "Optimal solution found")
})

## -----------------------------------------------------------------
## 2. Results match ECOS_csolve
## -----------------------------------------------------------------
test_that("lifecycle results match ECOS_csolve", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    on.exit(ECOS_cleanup(ws), add = TRUE)

    res_lc <- ECOS_solve(ws)
    res_ref <- ECOS_csolve(c = prob$c, G = prob$G, h = prob$h,
                           dims = prob$dims, A = prob$A, b = prob$b)

    expect_equal(res_lc$x, res_ref$x, tolerance = 1e-8)
    expect_equal(res_lc$y, res_ref$y, tolerance = 1e-8)
    expect_equal(res_lc$s, res_ref$s, tolerance = 1e-8)
    expect_equal(res_lc$z, res_ref$z, tolerance = 1e-8)
    expect_equal(res_lc$retcodes, res_ref$retcodes)
})

## -----------------------------------------------------------------
## 3. Update + re-solve produces different solution
## -----------------------------------------------------------------
test_that("update and re-solve works", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    on.exit(ECOS_cleanup(ws), add = TRUE)

    res1 <- ECOS_solve(ws)
    expect_equal(res1$retcodes[["exitFlag"]], 0L)

    ## Perturb h and update
    new_h <- prob$h + 0.5
    ECOS_update(ws, h = new_h)
    res2 <- ECOS_solve(ws)
    expect_equal(res2$retcodes[["exitFlag"]], 0L)

    ## Solutions should differ
    expect_false(isTRUE(all.equal(res1$x, res2$x)))

    ## Compare with a fresh ECOS_csolve using perturbed h
    res_ref <- ECOS_csolve(c = prob$c, G = prob$G, h = new_h,
                           dims = prob$dims, A = prob$A, b = prob$b)
    expect_equal(res2$x, res_ref$x, tolerance = 1e-6)
})

## -----------------------------------------------------------------
## 4. Wrong-length update errors
## -----------------------------------------------------------------
test_that("wrong-length update vectors error", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    on.exit(ECOS_cleanup(ws), add = TRUE)

    expect_error(ECOS_update(ws, h = numeric(1)), "h length")
    expect_error(ECOS_update(ws, c = numeric(1)), "c length")
    expect_error(ECOS_update(ws, b = numeric(1)), "b length")
    expect_error(ECOS_update(ws, Gpr = numeric(1)), "Gpr length")
    expect_error(ECOS_update(ws, Apr = numeric(1)), "Apr length")
})

## -----------------------------------------------------------------
## 5. Use-after-cleanup errors
## -----------------------------------------------------------------
test_that("use after cleanup errors", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    ECOS_cleanup(ws)

    expect_error(ECOS_solve(ws), "cleaned up")
    expect_error(ECOS_update(ws, h = prob$h), "cleaned up")
    ## Double cleanup should be safe (no error)
    expect_silent(ECOS_cleanup(ws))
})

## -----------------------------------------------------------------
## 6. GC finalizer doesn't crash
## -----------------------------------------------------------------
test_that("GC finalizer does not crash", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    rm(ws)
    expect_silent(gc())
})

## -----------------------------------------------------------------
## 7. Per-solve control override
## -----------------------------------------------------------------
test_that("per-solve control override works", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    on.exit(ECOS_cleanup(ws), add = TRUE)

    ## Solve with maxit=1 -> should hit MAXIT
    res_maxit <- ECOS_solve(ws, control = ecos.control(maxit = 1L))
    expect_equal(res_maxit$retcodes[["exitFlag"]], -1L)

    ## Re-solve with default settings -> should find optimal
    res_opt <- ECOS_solve(ws, control = ecos.control())
    expect_equal(res_opt$retcodes[["exitFlag"]], 0L)
})

## -----------------------------------------------------------------
## 8. Partial updates (update only c, only h)
## -----------------------------------------------------------------
test_that("partial updates work correctly", {
    prob <- load_problem("update_data_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims, A = prob$A, b = prob$b)
    on.exit(ECOS_cleanup(ws), add = TRUE)

    res_orig <- ECOS_solve(ws)

    ## Update only c
    new_c <- prob$c * 2
    ECOS_update(ws, c = new_c)
    res_c <- ECOS_solve(ws)
    ## Optimal value should roughly double since c is scaled
    expect_false(isTRUE(all.equal(res_orig$summary[["pcost"]],
                                  res_c$summary[["pcost"]])))

    ## Update only h back to original, keeping scaled c
    ECOS_update(ws, h = prob$h)
    res_h <- ECOS_solve(ws)
    expect_equal(res_h$retcodes[["exitFlag"]], 0L)
})

## -----------------------------------------------------------------
## 9. Problem without A (no equality constraints)
## -----------------------------------------------------------------
test_that("lifecycle works without equality constraints", {
    prob <- load_problem("MPC01_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims)
    on.exit(ECOS_cleanup(ws), add = TRUE)

    res <- ECOS_solve(ws)
    expect_equal(res$retcodes[["exitFlag"]], 0L)

    ref <- ECOS_csolve(c = prob$c, G = prob$G, h = prob$h,
                       dims = prob$dims)
    expect_equal(res$x, ref$x, tolerance = 1e-6)

    ## Update h and re-solve
    new_h <- prob$h + 0.01
    ECOS_update(ws, h = new_h)
    res2 <- ECOS_solve(ws)
    expect_false(isTRUE(all.equal(res$x, res2$x)))
})

## -----------------------------------------------------------------
## 10. Print method
## -----------------------------------------------------------------
test_that("print method works", {
    prob <- load_problem("MPC01_1.RDS")
    ws <- ECOS_setup(c = prob$c, G = prob$G, h = prob$h,
                     dims = prob$dims)
    on.exit(ECOS_cleanup(ws), add = TRUE)
    expect_output(print(ws), "ecos_workspace")
})

Try the ECOSolveR package in your browser

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

ECOSolveR documentation built on Feb. 18, 2026, 5:06 p.m.