In this vignette, we illustrate the use of the sequential quadratic programming (SQP) algorithm implemented in `mixsqp`

, and we compare its runtime and accuracy against an interior-point (IP) solver implemented by the MOSEK commercial software (it is called by the “KWDual” function in the REBayes package).

If you do not have the Rmosek and REBayes packages installed on your computer, you may skip over these steps in the vignette.

Load the `mixsqp`

package.

`library(mixsqp)`

Optionally, load the `Rmosek`

package. (The Rmosek package is only used to check the version of MOSEK.)

`library(Rmosek)`

Next, initialize the sequence of pseudorandom numbers.

`set.seed(1)`

We begin with a small example to show how `mixsqp`

works.

```
L <- simulatemixdata(1000,20)$L
dim(L)
# [1] 1000 20
```

This call to `simulatemixdata`

created an \(n \times m\) conditional likelihood matrix for a mixture of zero-centered normals, with \(n = 1000\) and \(m = 20\). By default, `simulatemixdata`

normalizes the rows of the likelihood matrix so that the maximum entry in each row is 1.

Now we fit the mixture model using the SQP algorithm:

```
fit.sqp <- mixsqp(L)
# Running mix-SQP algorithm 0.1-79 on 1000 x 20 matrix
# convergence tol. (SQP): 1.0e-08
# conv. tol. (active-set): 1.0e-10
# zero threshold (solution): 1.0e-06
# zero thresh. (search dir.): 1.0e-08
# max. iter (SQP): 1000
# max. iter (active-set): 100
# iter objective max(rdual) nnz max.diff nqp nls
# 1 +7.43494e-01 +2.102e-01 20 NA NA NA
# 2 +7.10831e-01 +2.612e+00 4 3.44e-01 33 1
# 3 +6.62093e-01 +1.084e+00 4 1.08e-01 5 1
# 4 +6.45338e-01 +3.914e-01 5 2.39e-01 10 1
# 5 +6.35842e-01 +1.149e-01 4 3.58e-02 3 1
# 6 +6.31837e-01 +2.390e-02 5 2.06e-01 4 1
# 7 +6.28742e-01 +3.280e-03 4 7.64e-02 3 1
# 8 +6.28208e-01 +5.598e-05 4 8.00e-03 2 1
# 9 +6.28198e-01 -1.570e-08 4 1.13e-04 2 1
# Convergence criteria met---optimal solution found.
```

In this example, the SQP algorithm converged to a solution in a small number of iterations.

By default, `mixsqp`

outputs information on its progress. It begins by summarizing the optimization problem and the algorithm settings used. (Since we did not change these settings in the `mixsqp`

call, all the settings shown here are the default settings.)

After that, it outputs, at each iteration, information about the current solution, such as the value of the objective (“objective”) and the number of nonzeros (“nnz”).

The “max(rdual)” column shows the quantity used to assess convergence. It reports the maximum value of the “dual residual”; the SQP solver terminates when the maximum dual residual is less than `conv.tol`

, which by default is \(10^{-8}\). In this example, we see that the dual residual shrinks rapidly toward zero.

Another useful indicator of convergence is the “max.diff” column—it reports the maximum difference between the solution estimates at two successive iterations. We normally expect these differences to shrink as we approach the solution, which is precisely what we see in this example.

This information is also provided in the return value, which we can use, for example, to create a plot of the objective value at each iteration of the SQP algorithm:

```
numiter <- nrow(fit.sqp$progress)
plot(1:numiter,fit.sqp$progress$objective,type = "b",
pch = 20,lwd = 2,xlab = "SQP iteration",
ylab = "objective",xaxp = c(1,numiter,numiter - 1))
```

To assess the accuracy of the SQP solution, we can compare against the solution computed by the IP algorithm. (If you do not have the REBayes package installed, you can skip this step.)

`fit.ip <- mixkwdual(L)`

If you run the IP algorithm, you should see that the IP and SQP solutions achieve nearly the same objective value.

```
cat(sprintf("Objective at SQP solution: %0.16f\n",fit.sqp$value,digits = 16))
cat(sprintf("Objective at IP solution: %0.16f\n",fit.ip$value,digits = 16))
cat(sprintf("Difference in objectives: %0.4e\n",fit.sqp$value - fit.ip$value))
# Objective at SQP solution: 0.6281978481946300
# Objective at IP solution: 0.6281978456083795
# Difference in objectives: 2.5863e-09
```

We observed that the SQP and IP methods achieve nearly the same solution quality in the example above. Here, we explore the computational properties of the SQP and IP algorithms in a larger data set.

As before, we compute the \(n \times m\) likelihood matrix for a mixture of zero-centered normals. This time, we use a finer grid of \(m = 100\) normal densities, as well as many more samples.

```
L <- simulatemixdata(1e5,100)$L
dim(L)
# [1] 100000 100
```

Now we fit the model using the SQP algorithm:

```
timing <- system.time(fit.sqp <- mixsqp(L))
cat(sprintf("Computation took %0.2f seconds\n",timing["elapsed"]))
# Running mix-SQP algorithm 0.1-79 on 100000 x 100 matrix
# convergence tol. (SQP): 1.0e-08
# conv. tol. (active-set): 1.0e-10
# zero threshold (solution): 1.0e-06
# zero thresh. (search dir.): 1.0e-08
# max. iter (SQP): 1000
# max. iter (active-set): 100
# iter objective max(rdual) nnz max.diff nqp nls
# 1 +7.79069e-01 +2.447e-01 100 NA NA NA
# 2 +6.92758e-01 +2.219e+01 4 3.62e-01 100 1
# 3 +6.37165e-01 +9.824e+00 7 4.91e-01 47 1
# 4 +6.23319e-01 +4.545e+00 4 2.07e-01 14 1
# 5 +6.17706e-01 +1.251e+00 5 2.97e-01 13 1
# 6 +6.16019e-01 +2.574e-01 5 2.73e-01 14 1
# 7 +6.15240e-01 +4.100e-02 5 2.19e-01 35 1
# 8 +6.15066e-01 +4.111e-03 7 1.02e-01 24 1
# 9 +6.15053e-01 +2.241e-04 7 2.80e-03 2 1
# 10 +6.15052e-01 +9.288e-07 7 9.53e-04 2 1
# 11 +6.15052e-01 -1.543e-08 7 1.14e-05 2 1
# Convergence criteria met---optimal solution found.
# Computation took 8.09 seconds
```

If you have the REBayes package, you can also run the IP method:

```
timing <- system.time(fit.ip <- mixkwdual(L))
cat(sprintf("Computation took %0.2f seconds\n",timing["elapsed"]))
# Computation took 12.58 seconds
```

If you run the IP algorithm, you should find that the SQP algorithm was considerably faster than the IP solver, and it converged to a solution with nearly the same objective value as the IP solution.

```
cat(sprintf("Objective at SQP solution: %0.16f\n",fit.sqp$value,digits = 16))
cat(sprintf("Objective at IP solution: %0.16f\n",fit.ip$value,digits = 16))
cat(sprintf("Difference in objectives: %0.4e\n",fit.sqp$value - fit.ip$value))
# Objective at SQP solution: 0.6150522313978147
# Objective at IP solution: 0.6150522313757822
# Difference in objectives: 2.2032e-11
```

This next code chunk gives information about the computing environment used to generate the results contained in this vignette, including the version of R and the packages used.

```
sessionInfo()
# R version 3.4.3 (2017-11-30)
# Platform: x86_64-apple-darwin15.6.0 (64-bit)
# Running under: macOS High Sierra 10.13.6
#
# Matrix products: default
# BLAS: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRblas.0.dylib
# LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib
#
# locale:
# [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#
# attached base packages:
# [1] stats graphics grDevices utils datasets methods base
#
# other attached packages:
# [1] Rmosek_8.0.69 Matrix_1.2-12 mixsqp_0.1-79
#
# loaded via a namespace (and not attached):
# [1] Rcpp_0.12.19 lattice_0.20-35 digest_0.6.17 rprojroot_1.3-2
# [5] grid_3.4.3 backports_1.1.2 magrittr_1.5 evaluate_0.11
# [9] stringi_1.2.4 rmarkdown_1.10 tools_3.4.3 stringr_1.3.1
# [13] REBayes_1.3 yaml_2.2.0 compiler_3.4.3 htmltools_0.3.6
# [17] knitr_1.20
```

If you used `REBayes`

and `Rmosek`

above, it is also helpful to record the version of MOSEK that was used.

```
mosek_version()
# [1] "MOSEK 8.1.0.27"
```