---
title: "ECOSolveR Examples"
date: '`r Sys.Date()`'
output:
  html_document:
  fig_caption: yes
  theme: cerulean
  toc: yes
  toc_depth: 2
vignette: >
  %\VignetteIndexEntry{ECOSolveR Examples}
  %\VignetteEngine{knitr::rmarkdown}
  \usepackage[utf8]{inputenc}
---

```{r echo=F}
### get knitr just the way we like it

knitr::opts_chunk$set(
  message = FALSE,
  warning = FALSE,
  error = FALSE,
  tidy = FALSE,
  cache = FALSE
)

```
## $L_1$ minimization (Linear Programming)

We solve the following problem that arises for example in sparse
signal reconstruction problems such as compressed sensing:
$$
\mbox{minimize } ||x||_1  \mbox{   ($L_1$)   }\\
\mbox{subject to } Ax = b
$$

with $x\in R^n$, $A \in R^{m \times n}$ and $m\leq n.$ Reformulate the
problem expressing the $L_1$ norm of $x$ as follows
$$
x \leq u\\
-x \leq u\\
$$

where $u\in R^n$ and we minimize the sum of $u$. The reformulated
problem using the stacked variables

$$
z = \begin{pmatrix}x\\u\end{pmatrix}
$$

is now
$$
\mbox{minimize } c^{\top}z\\
\mbox{subject to } \tilde{A}x = b  \mbox{   (LP)  }\\
     Gx \leq h
$$
where the inequality is with respective to the positive orthant.

Here is the R code that generates a random instance of this problem
and solves it.

```{r}
library(ECOSolveR)
library(Matrix)
set.seed(182391)
n <- 1000L
m <- 10L
density <- 0.01
c <- c(rep(0.0, n), rep(1.0, n))
```

First, a function to generate random sparse matrices with normal
entries.

```{r}
sprandn <- function(nrow, ncol, density) {
    items <- ceiling(nrow * ncol * density)
    matrix(c(rnorm(items),
             rep(0, nrow * ncol - items)),
           nrow = nrow)
}
```

```{r}
A <- sprandn(m, n, density)
Atilde <- Matrix(cbind(A, matrix(rep(0.0, m * n), nrow = m)), sparse = TRUE)
b <- rnorm(m)
I <- diag(n)
G <- rbind(cbind(I, -I),
           cbind(-I, -I))
G <- as(G, "dgCMatrix")
h <- rep(0.0, 2L * n)
dims <- list(l = 2L * n, q = NULL, e = 0L)
```

Note how ECOS expects sparse matrices, not ordinary matrices.

```{r}
## Solve the problem
z <- ECOS_csolve(c = c, G = G, h = h, dims = dims, A = Atilde, b = b)
```

We check that the solution was found.

```{r}
names(z)
z$infostring
```

Extract the solution.

```{r}
x <- z$x[1:n]
u <- z$x[(n+1):(2*n)]
nnzx = sum(abs(x) > 1e-8)
sprintf("x reconstructed with %d non-zero entries", nnzx / length(x) * 100)
```

## Parametric Optimization with the Update-Solve Loop

When solving a sequence of related problems that share the same
sparsity structure but differ in numerical data, the multi-step
lifecycle API avoids repeating the expensive symbolic analysis and
KKT ordering phase.  The pattern is:

1. `ECOS_setup()` — one-time setup (symbolic analysis)
2. Loop: `ECOS_update()` + `ECOS_solve()` — cheap re-solves
3. `ECOS_cleanup()` — free workspace

Here we vary the right-hand side $h$ of the LP from the previous
example, tracing how the optimal value changes as we scale $h$.

```{r}
## Set up workspace once
ws <- ECOS_setup(c = c, G = G, h = h, dims = dims, A = Atilde, b = b)

## Sweep a parameter: scale h from 0 to 1
alphas <- seq(0, 1, length.out = 11)
pcosts <- numeric(length(alphas))

for (i in seq_along(alphas)) {
    ECOS_update(ws, h = h + alphas[i])
    res <- ECOS_solve(ws)
    pcosts[i] <- res$summary[["pcost"]]
}

ECOS_cleanup(ws)

## Show results
data.frame(alpha = alphas, pcost = round(pcosts, 4))
```

For comparison, the same sweep using `ECOS_csolve()` in a loop
would call `ECOS_setup` and `ECOS_cleanup` at every iteration.
The lifecycle API is particularly beneficial when the problem
dimensions are large and setup dominates solve time.
