context("mixsqp") skip_if_mixkwdual_doesnt_work <- function() { skip_if_not_installed("REBayes") skip_if_not_installed("Rmosek") skip_if(!is.element("mosek_lptoprob",getNamespaceExports("Rmosek"))) } test_that("Version number in mixsqp with verbose = TRUE is correct",{ L <- rbind(c(1,1,0), c(1,1,1)) out <- capture.output(mixsqp(L)) x <- unlist(strsplit(out[1]," "))[4] expect_equal(packageDescription("mixsqp")$Version,x) }) test_that(paste("mix-SQP allows zero likelihoods, but reports an error", "when initial estimate does not satisfy L*x > 0"),{ L <- rbind(c(1,1,0), c(1,1,1)) capture.output(out <- mixsqp(L)) expect_equal(out$status,mixsqp:::mixsqp.status.converged) expect_error(mixsqp(L,x0 = c(0,0,1))) }) test_that(paste("mix-SQP converges to correct solution even when initial", "estimate is very poor"),{ e <- 1e-8 L <- rbind(c(1,1,e), c(1,2,1)) capture.output(out1 <- mixsqp(L)) capture.output(out2 <- mixsqp(L,x0 = c(0,0,1))) expect_equal(out1$value,out2$value,tol = 1e-8) # This second example is particularly challenging because two of the # columns of the likelihood matrix are identical. L <- rbind(c(1,1,e), c(1,1,1)) capture.output(out1 <- mixsqp(L)) capture.output(out2 <- mixsqp(L,x0 = c(0,0,1))) expect_equal(out1$value,out2$value,tol = 1e-8) }) test_that(paste("mix-SQP gives correct solutions for 2 x 2 and", "2 x 3 likelihood matrices"),{ # In this first example, the correct solution is (1/2,1/2). e <- 1e-8 L <- rbind(c(1,e), c(e,1)) capture.output(out <- mixsqp(L)) expect_equal(out$x,c(0.5,0.5),tolerance = 1e-8,scale = 1) # In this second example, any solution of the form (x1,x2,0) gives # the same value for the objective, and the third mixture weight # should be exactly zero. L <- rbind(c(1,1,e), c(1,1,1)) capture.output(out1 <- mixsqp(L,x0 = c(1,1,0),control = list(eps = 0))) capture.output(out2 <- mixsqp(L,x0 = c(0,1,1),control = list(eps = 0))) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) expect_equal(out2$status,mixsqp:::mixsqp.status.converged) expect_equal(out1$x[3],0,tolerance = 0) expect_equal(out2$x[3],0,tolerance = 0) }) test_that(paste("mix-SQP and KWDual return the same solution for", "1000 x 10 likelihood matrix, and mix-SQP correctly", "estimates the nonzeros"),{ # Simulate a 1,000 x 10 likelihood matrix. Note that I add row and # column names to the matrix to check that the column names are # retained in the solution vector. set.seed(1) n <- 1000 m <- 10 L <- simulatemixdata(n,m)$L rownames(L) <- paste0("x",1:n) colnames(L) <- paste0("s",1:m) # Apply mix-SQP solver to the data set. Check that the solution # entries are labeled correctly. capture.output(out1 <- mixsqp(L)) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) expect_equal(names(out1$x),colnames(L)) # Apply KWDual solver to the data set. skip_if_mixkwdual_doesnt_work() out2 <- mixkwdual(L) expect_equal(names(out2$x),colnames(L)) # The outputted solutions, and the objective values at those # solutions, should be nearly identical. expect_equal(out1$x,out2$x,tolerance = 1e-4,scale = 1) expect_equal(out1$value,out2$value,tolerance = 1e-8,scale = 1) # The very small mixture weights estimated by KWDual are all smaller # in the mix-SQP output. i <- which(out2$x < 0.001) expect_equivalent(out1$x[i] < out2$x[i],rep(TRUE,length(i))) }) test_that(paste("mix-SQP & KWDual return the same solution for", "1000 x 10 likelihood matrix with unequal row weights"),{ # Simulate a 1000 x 10 likelihood matrix, and different weights for # the rows of this matrix. set.seed(1) L <- simulatemixdata(1000,10)$L w <- runif(1000) w <- w/sum(w) # Apply mix-SQP solver to the data set. capture.output(out1 <- mixsqp(L,w)) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) # Apply KWDual solver to the data set. skip_if_mixkwdual_doesnt_work() out2 <- mixkwdual(L,w) # The outputted solutions, and the objective values at those # solutions, should be nearly identical. expect_equal(out1$x,out2$x,tolerance = 1e-4,scale = 1) expect_equal(out1$value,out2$value,tolerance = 1e-6,scale = 1) }) test_that(paste("mix-SQP returns the same solution regardless whether", "the likelihood matrix is normalized"),{ # Simulate two 100 x 10 likelihood matrices---one normalized and one # unnormalized---and different weights for the rows of this matrix. set.seed(1) L1 <- simulatemixdata(100,10,normalize.rows = TRUE)$L set.seed(1) L2 <- simulatemixdata(100,10,normalize.rows = FALSE)$L w <- runif(100) w <- w/sum(w) # Apply mix-SQP to normalized and unnormalized data sets. capture.output(out1 <- mixsqp(L1,w)) capture.output(out2 <- mixsqp(L2,w)) # The outputted solutions should be nearly identical (although the # values of the objectives will be different). expect_equal(out1$status,mixsqp:::mixsqp.status.converged) expect_equal(out2$status,mixsqp:::mixsqp.status.converged) expect_equal(out1$x,out2$x,tolerance = 1e-8,scale = 1) }) test_that(paste("mix-SQP returns the correct solution when log-likelihoods", "are provided"),{ # Simulate a 100 x 10 likelihood matrix as well as different weights # for the rows of this matrix. set.seed(1) L <- simulatemixdata(100,10,normalize.rows = FALSE)$L w <- runif(100) w <- w/sum(w) # Compute the log-likelihood matrix for the same data. set.seed(1) logL <- simulatemixdata(100,10,log = TRUE)$L # Apply mix-SQP to the likelihoods and the log-likelihoods. capture.output(out1 <- mixsqp(L,w)) capture.output(out2 <- mixsqp(logL,w,log = TRUE)) # The outputted solutions should be nearly identical (though the # values of the objectives will be different). expect_equal(out1$status,mixsqp:::mixsqp.status.converged) expect_equal(out2$status,mixsqp:::mixsqp.status.converged) expect_equal(out1$x,out2$x,tolerance = 1e-8,scale = 1) }) test_that(paste("mix-SQP returns the same solution when using full data", "matrix and the low-rank SVD approximation"),{ # Simulate a 1,000 x 80 likelihood matrix. set.seed(1) L <- simulatemixdata(1000,80)$L w <- runif(1000) w <- w/sum(w) # Apply mix-SQP to normalized and unnormalized data sets. capture.output(out1 <- mixsqp(L,w,control = list(tol.svd = 0))) capture.output(out2 <- mixsqp(L,w,control = list(tol.svd = 1e-8))) # The outputted solutions---and the gradients and Hessians at the # estimated solutions---should be nearly identical. expect_equal(out1$value,out2$value,tolerance = 1e-6,scale = 1) expect_equal(out1$x,out2$x,tolerance = 1e-6,scale = 1) expect_equal(out1$grad,out2$grad,tolerance = 1e-6,scale = 1) expect_equal(out1$hessian,out2$hessian,tolerance = 1e-6,scale = 1) }) test_that("mix-SQP successfully \"escapes\" a sparse initial estimate",{ set.seed(1) n <- 100 m <- 10 out <- simulate_data_koenker(n,m) L <- out$L w <- out$w x0 <- c(1,rep(0,m - 1)) capture.output(fit1 <- mixsqp(L,w,x0,control = list(numiter.em = 0))) expect_equal(fit1$status,mixsqp:::mixsqp.status.converged) # Compare mix-SQP solution to KWDual. skip_if_mixkwdual_doesnt_work() fit2 <- mixkwdual(L,w) expect_equal(fit1$x,fit2$x,tolerance = 1e-4,scale = 1) expect_lte(fit1$value,fit2$value) }) test_that(paste("mix-SQP gives correct solution for Beckett & Diaconis", "tack rolling example"),{ data(tacks) L <- tacks$L w <- tacks$w capture.output(out <- mixsqp(L,w)) # The objective value at the mix-SQP solution should be very close # to the objective value at the KWDual solution. expect_equal(out$status,mixsqp:::mixsqp.status.converged) expect_equal(mixobjective(L,out$x,w),mixobjective(L,tacks$x,w), tolerance = 1e-4) }) # This is mainly to test post-processing of the output when the # algorithm reaches the maximum number of iterations. This example is # used in one of the other tests above. test_that("mix-SQP gives warning with convergence failure",{ e <- 1e-8 L <- rbind(c(1,1,e), c(1,1,1)) expect_warning(capture_output(out <- mixsqp(L,x0 = c(0,1,1), control = list(numiter.em = 0, maxiter.sqp = 1)))) expect_equal(out$status,mixsqp.status.didnotconverge) expect_equal(dim(out$progress),c(1,7)) }) # This test comes from Issue #3. test_that(paste("mix-SQP gives correct solution for \"short and fat\" matrix,", "even when linear systems in active-set method are not", "necessarily s.p.d."),{ set.seed(1) L <- matrix(rgamma(1000,1,1),nrow = 10) capture.output(out1 <- mixsqp(L)) # The mix-SQP solution should be very close to the KWDual solution # and, more importantly, the quality of the mix-SQP solution should # be very similar, even when the Newton search direction in the # active-set method is not necessarily unique (i.e., the Hessian is # not s.p.d.). skip_if_mixkwdual_doesnt_work() out2 <- mixkwdual(L) expect_equal(out1$x,out2$x,tolerance = 1e-4,scale = 1) expect_equal(out1$value,out2$value,tolerance = 1e-6,scale = 1) }) # This test comes from Issue #5. test_that(paste("mix-SQP converges, and outputs correct solution, for example", "in which the \"dual residual\" never reaches exactly zero"),{ # Generate the data set for testing. set.seed(1) n <- 1e5 m <- 12 L <- simulatemixdata(n,m)$L # Here we also check convergence for the case when no numerical # stability measure is used for the active-set linear systems (i.e., # eps = 0). Also, when convtol.sqp = 0, the mix-SQP algorithm should # report that it failed to converge in this example. suppressWarnings(capture.output(out1 <- mixsqp(L,control = list(eps = 0,convtol.sqp = 0,maxiter.sqp = 10)))) capture.output(out2 <- mixsqp(L)) capture.output(out3 <- mixsqp(L,control = list(eps = 0))) expect_equal(out1$status,"exceeded maximum number of iterations") expect_equal(out2$status,mixsqp:::mixsqp.status.converged) expect_equal(out3$status,mixsqp:::mixsqp.status.converged) # When the mix-SQP iterates converge, they should be very close to # the KWDual solution. skip_if_mixkwdual_doesnt_work() out4 <- mixkwdual(L) expect_equal(out2$x,out4$x,tolerance = 1e-5,scale = 1) expect_equal(out3$x,out4$x,tolerance = 1e-5,scale = 1) expect_equal(out2$value,out4$value,tolerance = 1e-6,scale = 1) expect_equal(out3$value,out4$value,tolerance = 1e-6,scale = 1) }) test_that(paste("Case is properly handled in which all columns except", "one are filled with zeros"),{ set.seed(1) n <- 200 m <- 10 i <- 7 L <- simulatemixdata(n,m)$L L[,-i] <- 0 xsol <- rep(0,m) xsol[i] <- 1 expect_warning(capture.output(out1 <- mixsqp(L))) expect_equal(out1$status,mixsqp:::mixsqp.status.didnotrun) expect_null(out1$progress) expect_equal(out1$x,xsol) skip_if_mixkwdual_doesnt_work() expect_warning(capture.output(out2 <- mixkwdual(L))) expect_equal(out2$x,xsol) }) test_that("Case is properly handled in which one column of L is all zeros",{ set.seed(1) n <- 200 m <- 10 i <- 7 L <- simulatemixdata(n,m)$L # Run the mix-SQP algorithm when all columns have nonzeros. L[,i] <- 1e-8 capture.output(out1 <- mixsqp(L)) # Set one of the columns to be all zeros, and re-run the mix-SQP # algorithm. L[,i] <- 0 expect_warning(capture.output(out2 <- mixsqp(L))) # The two solutions should be pretty much the same. expect_equal(out1$x,out2$x,tolerance = 1e-4,scale = 1) expect_equal(out1$value,out2$value,tolerance = 1e-8,scale = 1) # Also check KWDual solution. skip_if_mixkwdual_doesnt_work() expect_warning(out3 <- mixkwdual(L)) expect_equal(out1$x,out3$x,tolerance = 0.001,scale = 1) expect_equal(out1$value,out3$value,tolerance = 1e-6,scale = 1) }) test_that("Case is properly handled when L has only one column",{ skip_if_mixkwdual_doesnt_work() set.seed(1) L <- matrix(runif(100)) suppressWarnings(out1 <- mixsqp(L)) suppressWarnings(out2 <- mixkwdual(L)) expect_equal(out1$x,1) expect_equal(out2$x,1) expect_equal(out1$value,out2$value) }) test_that(paste("mix-SQP converges to solution for \"flat\" objective even", "if initial progress is poor"),{ set.seed(1) n <- 10000 m <- 20 L <- matrix(runif(n*m),n,m) capture.output(out <- mixsqp(L)) expect_equal(out$status,mixsqp:::mixsqp.status.converged) }) # This test comes from Issue #19. test_that("mix-SQP converges in a more difficult example",{ load("flashr.example.RData") capture.output(out1 <- mixsqp(L)) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) skip_if_mixkwdual_doesnt_work() out2 <- mixkwdual(L) expect_equal(out1$x,out2$x,tolerance = 0.001,scale = 1) }) # This test comes from Issue #29. test_that("mix-SQP converges despite poor initialization",{ load("ashr.example.RData") capture.output(out1 <- mixsqp(L,x0 = x0)) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) }) # This test comes from Issue #30. test_that("mix-SQP works for difficult smashr example",{ load("smashr.example.RData") capture.output(out1 <- mixsqp(L,w,x0)) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) skip_if_mixkwdual_doesnt_work() out2 <- mixkwdual(L) expect_equal(out1$x,out2$x,tolerance = 1e-6,scale = 1) }) # This example was generated by running: # # library(ashr) # set.seed(1) # logitp <- c(rep(0,800), runif(200,-3,3)) # p <- 1/(1 + exp(-logitp)) # n <- rep(100,1000) # x <- rbinom(1000,n,p) # out <- ash(rep(0,length(x)),1,lik = lik_binom(x,n,link = "logit"), # mode = "estimate") # test_that("mix-SQP works for difficult ashr example with binomial likelihood",{ skip_if_mixkwdual_doesnt_work() load("ashr.binom.example.RData") capture.output(out1 <- mixsqp(L)) out2 <- mixkwdual(L) expect_equal(out1$value,out2$value,tolerance = 1e-8,scale = 1) expect_equal(out1$x,out2$x,tolerance = 1e-4,scale = 1) }) # This test comes from stephens999/ashr Issue #76. test_that("mix-SQP works for a difficult mashr example",{ skip_if_mixkwdual_doesnt_work() skip_if_not(file.exists("mashr.example.RData")) load("mashr.example.RData") capture.output(out1 <- mixsqp(L,w)) out2 <- mixkwdual(L,w) expect_equal(out1$value,out2$value,tolerance = 1e-8,scale = 1) expect_equal(out1$x,out2$x,tolerance = 1e-4,scale = 1) }) # This test comes from Issue #42. test_that(paste("mix-SQP converges, and gives solution that is equally as", "good as KWDual for difficult NPMLE problem"),{ skip_if_not(file.exists("npmle.RData")) load("npmle.RData") capture.output(out1 <- mixsqp(L)) expect_equal(out1$status,mixsqp:::mixsqp.status.converged) skip_if_mixkwdual_doesnt_work() out2 <- mixkwdual(L) # The likelihood surface is very "flat", so the solutions are not # expected to be the same; however, the quality of the mix-SQP # solution should be as good, or nearly as good, as the solution # returned by the KWDual solver. expect_equal(out1$value,out2$value,tolerance = 1e-4,scale = 1) })