---
title: "What's New in CVXR"
author: "Anqi Fu, Balasubramanian Narasimhan, and Stephen Boyd"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{What's New in CVXR}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

This vignette highlights notable user-facing changes in each release of
the S7 rewrite of `CVXR`, newest first. For the complete, fine-grained
list see the package `NEWS` file (`news(package = "CVXR")`). For the
introductory tutorial, see `vignette("cvxr_intro")`; for worked
examples, visit the [CVXR website](https://cvxr.rbind.io).

* [CVXR 1.9.1](#cvxr-19x) — disciplined nonlinear programming, derivatives,
  bounds propagation, new atoms
* [CVXR 1.8.x](#cvxr-18x) — the ground-up S7 rewrite

# CVXR 1.9.1 {#cvxr-19x}

`CVXR` 1.9.1 is the first CRAN release since 1.8.2 and is a large one: it folds
in the internal 1.8.2-1 and 1.9.0 development cycles. Its headline additions are
disciplined nonlinear programming, a derivative / sensitivity-analysis API, and
interval-bounds propagation with native solver-bound support.

## Disciplined Nonlinear Programming (DNLP)

`CVXR` 1.9.1 extends modeling beyond convex optimization to **smooth nonlinear
programs**, which need not be convex. You build the problem from differentiable
atoms, check it with `is_dnlp()`, and solve it with `psolve(prob, nlp = TRUE)`.
Every DCP problem is also a DNLP, and the disciplined nonlinear grammar
additionally allows smooth atoms in forms DCP forbids (for example, a product of
two variable-dependent expressions).

```{r dnlp, eval = FALSE}
x <- Variable(2)
prob <- Problem(Minimize(sum_squares(x - c(1, 2))))
is_dnlp(prob)                       # TRUE
psolve(prob, nlp = TRUE)            # solved through the NLP path
```

* **New smooth atoms** usable anywhere in a DNLP: `sin()`, `cos()`, `tan()`,
  `sinh()`, `tanh()`, `asinh()`, `atanh()`, `normcdf()`, and `prod()`.
* **Nonconvex problems** may have several local optima. `best_of = n` solves from
  `n` random initial points (drawn from variable bounds or `sample_bounds()`)
  and keeps the best.
* The NLP path is powered by automatic derivatives from the optional
  `sparsediff` package and a nonlinear solver. Two are supported, both in
  `Enhances` (so guard their use with `requireNamespace()`): **UNO** (the `Uno`
  package — headed for CRAN, self-contained, and the practical default for R
  users) and **IPOPT** (the `ipopt` package — not on CRAN owing to licensing, so
  rarely installed). With `nlp = TRUE`, IPOPT is preferred when present
  (matching CVXPY), otherwise UNO is used. The `UNO` path also recovers
  constraint duals via `dual_value()`; the `IPOPT` path returns none, matching
  CVXPY.

See the [DNLP Tutorial](https://cvxr.rbind.io/examples/dnlp/tutorial.html) for
worked examples.

## Derivatives and sensitivity analysis

`CVXR` 1.9.1 adds the ability to **differentiate the solution map** of a
disciplined problem — to see how the optimal solution responds to small changes
in the parameters (sensitivity analysis) and to compute gradients of scalar
functions of the solution. Request derivatives at solve time with
`requires_grad = TRUE`.

**Forward mode** (perturb parameters, see the change in the solution):

```{r deriv-fwd, eval = FALSE}
psolve(problem, requires_grad = TRUE)
delta(a) <- da                      # perturbation of parameter a
derivative(problem)                 # propagate forward
delta(x)                            # resulting change in variable x
```

**Reverse mode** (gradient of the solution with respect to parameters):

```{r deriv-rev, eval = FALSE}
psolve(problem, requires_grad = TRUE)
backward(problem)                   # propagate backward
gradient(a)                         # d(solution) / d(a)
```

The chain rule is wired through the `Dgp2Dcp` (log/exp) and `Complex2Real`
reductions, so geometric and complex problems differentiate too. The derivative
API is backed by the optional `diffcp` R package. See the
[Derivatives](https://cvxr.rbind.io/examples/derivatives/fundamentals.html)
examples and
[Sensitivity Analysis](https://cvxr.rbind.io/examples/dpp/sensitivity-analysis.html).

## Bounds propagation and richer variable bounds

`get_bounds()` now works on **any expression**, not just variables, propagating
interval bounds through affine, elementwise, and piecewise-linear atoms:

```{r bounds, eval = FALSE}
x <- Variable(3, bounds = list(-1, 2))
get_bounds(A %*% x + b)         # bounds propagated through the affine map
get_bounds(abs(x))             # and through atoms
```

Variable bounds may also be **sparse `Matrix` objects** or **symbolic bounds**
involving `Parameter`s; symbolic bounds are enforced at solve time and update on
DPP re-solves. Positive (DGP) variables accept numeric *and* parametric bounds
under `gp = TRUE`.

## New atoms and DPP refinements

* **`convolve()`** — the (numpy-style) name for the 1-D discrete-convolution
  `conv()` atom; falls through to `stats::convolve()` on numeric input.
* **`is_dpp()` gains a `context` argument** (`"dcp"` or `"dgp"`), matching
  CVXPY's `is_dpp(context = ...)`.

## Solvers

* **CPLEX** now solves LP / SOCP / MI-LP / MI-SOCP through the conic path.
* **Native variable bounds**: HiGHS, Gurobi, CPLEX, XPRESS, PIQP, and SCIP now
  consume dense numeric variable bounds directly (including parametric bounds for
  HiGHS), avoiding extra bound constraints and speeding up DPP re-solves.

## Bug fixes (also affected 1.8.x)

* `problem_data()` / `get_problem_data()` now take an explicit `gp` argument;
  previously `gp = TRUE` passed through `...` was silently ignored, compiling a
  geometric program as a DCP problem.
* `psolve()` now takes explicit `enforce_dpp` and `ignore_dpp` arguments,
  matching CVXPY's `solve()`; previously they were silently swallowed by `...`.

## Performance

Canonicalization and solving are now faster than the 1.8.2 CRAN release
(roughly 5–13% lower wall-clock on solve-dominated problems such as many small
constraints, SOCPs, and Kalman smoothing), with deterministic memory allocation
unchanged.

# CVXR 1.8.x {#cvxr-18x}

## Complete Rewrite Using S7

CVXR 1.8.x is a ground-up rewrite using R's
[S7](https://rconsortium.github.io/S7/) object system, designed to be
isomorphic with [CVXPY 1.8.2](https://www.cvxpy.org/) for long-term
maintainability. It is approximately 4--5x faster than the previous
S4-based release. This section summarizes the key changes from CVXR 1.x
that may affect users.

## New Features

- **S7 class system** replaces S4 for all expression, constraint, and
  problem classes. Significantly faster construction and method dispatch.
- **15 solvers**: CLARABEL (default), SCS, OSQP, HiGHS, MOSEK, Gurobi,
  GLPK, GLPK_MI, ECOS, ECOS_BB, CPLEX, CVXOPT, PIQP, SCIP, and XPRESS.
- **Mixed-integer programming** via GLPK_MI, ECOS_BB, Gurobi, CPLEX,
  HiGHS, SCIP, or XPRESS (`boolean = TRUE` or `integer = TRUE` in `Variable()`).
- **Parameter support** via `Parameter()` class and `EvalParams` reduction.
- **50+ atom classes** covering LP, QP, SOCP, SDP, exponential cone, and
  power cone problems.
- **DPP** (Disciplined Parameterized Programming) for efficient
  parameter re-solve with compilation caching.
- **DGP** (Disciplined Geometric Programming) via `psolve(prob, gp = TRUE)`.
- **DQCP** (Disciplined Quasiconvex Programming) via `psolve(prob, qcp = TRUE)`.
- **Complex variable support** via `Variable(n, complex = TRUE)`.
- **Warm-start support** for several solvers (OSQP, SCS, Gurobi, MOSEK,
  CLARABEL, HiGHS).
- **Matrix package interoperability** via `as_cvxr_expr()`. Matrix
  package objects (`dgCMatrix`, `dgeMatrix`, `dsCMatrix`, `ddiMatrix`,
  `sparseVector`) use S4 dispatch which preempts S7/S3, so they cannot
  be used directly with CVXR operators. Wrapping with `as_cvxr_expr()`
  converts them to CVXR `Constant` objects while preserving sparsity
  (unlike `as.matrix()` which densifies). Base R `matrix` and `numeric`
  objects work natively without wrapping.

## New solve interface

The primary solve function is now `psolve()`, which returns the optimal
value directly:

```{r psolve, eval = FALSE}
library(CVXR)
x <- Variable(2)
prob <- Problem(Minimize(sum_squares(x)), list(x >= 1))
opt_val <- psolve(prob)       # returns optimal value directly
x_val <- value(x)             # extract variable value
prob_status <- status(prob)   # check status
```

The old `solve()` still works but returns a backward-compatible list:

```{r solve, eval = FALSE}
result <- solve(prob)
result$value       # optimal value
result$getValue(x) # variable value (deprecated)
result$status      # problem status
```

## Breaking Changes from CVXR 1.x

### API changes

| Old API | New API |
|---------|---------|
| `solve(problem)` | `psolve(problem)` |
| `result$getValue(x)` | `value(x)` |
| `result$value` | return value of `psolve()` |
| `result$status` | `status(problem)` |
| `result$getDualValue(con)` | `dual_value(con)` |
| `problem_status(prob)` | `status(prob)` |
| `problem_solution(prob)` | `solution(prob)` |
| `get_problem_data(prob, solver)` | `problem_data(prob, solver)` |

### Axis parameter changes

The `axis` parameter now uses R's `apply()` convention (1-based indexing):

| Old CVXR | New CVXR | Meaning |
|----------|----------|---------|
| `axis = 1` | `axis = 1` | Row-wise reduction (unchanged) |
| `axis = 2` | `axis = 2` | Column-wise reduction (unchanged) |
| `axis = NA` | `axis = NULL` | All entries |

Passing `axis = 0` now produces an informative error with migration guidance.

### PSD constraints

PSD constraints use `PSD(A - B)` instead of `A %>>% B` (though `%>>%`
and `%<<%` operators are still available for backward compatibility).

### Solver changes

- **Removed**: CBC
- **Added**: HiGHS (LP, QP, MILP), Gurobi (LP, QP, SOCP, MIP), CVXOPT
  (LP, SOCP), PIQP (QP), SCIP, and XPRESS
- **Default solver**: CLARABEL (replaces ECOS)

### Supported solvers

| Solver | R Package | Type | Problem Classes |
|--------|-----------|------|-----------------|
| CLARABEL | `clarabel` | Conic | LP, QP, SOCP, SDP, ExpCone, PowCone |
| SCS | `scs` | Conic | LP, QP, SOCP, SDP, ExpCone, PowCone |
| MOSEK | `Rmosek` | Conic | LP, QP, SOCP, SDP, ExpCone, PowCone |
| ECOS | `ECOSolveR` | Conic | LP, SOCP, ExpCone |
| ECOS_BB | `ECOSolveR` | Conic | LP, SOCP, ExpCone + MI |
| GUROBI | `gurobi` | Conic/QP | LP, QP, SOCP, MI |
| GLPK | `Rglpk` | Conic | LP |
| GLPK_MI | `Rglpk` | Conic | LP, MILP |
| HIGHS | `highs` | Conic/QP | LP, QP, MILP |
| CVXOPT | `cccp` | Conic | LP, SOCP |
| OSQP | `osqp` | QP | LP, QP |
| CPLEX | `Rcplex` | Conic/QP | LP, QP, SOCP, MI |
| PIQP | `piqp` | QP | LP, QP |
| SCIP | `scip` | Conic | LP, MILP, SOCP, MI-SOCP |
| XPRESS | `xpress` | Conic/QP | LP, QP, SOCP, MI |

Smooth nonlinear programs additionally use the `IPOPT` and `UNO` NLP solvers
(see [CVXR 1.9.1](#cvxr-19x)).

## New Atoms and Functions

### Convenience atoms

| Function | Description |
|----------|-------------|
| `ptp(x)` | Peak-to-peak (range): `max(x) - min(x)` |
| `cvxr_mean(x)` | Arithmetic mean along an axis |
| `cvxr_std(x)` | Standard deviation |
| `cvxr_var(x)` | Variance |
| `vdot(x, y)` | Vector dot product (inner product) |
| `cvxr_outer(x, y)` | Outer product of two vectors |
| `inv_prod(x)` | Reciprocal of product of entries |
| `loggamma(x)` | Elementwise log of gamma function |
| `log_normcdf(x)` | Elementwise log of standard normal CDF |
| `cummax_expr(x)` | Cumulative maximum along an axis |
| `dotsort(X, W)` | Weighted sorted dot product |

### Math function dispatch

Standard R math functions work directly on CVXR expressions:

```{r math, eval = FALSE}
x <- Variable(3)
abs(x)        # elementwise absolute value
sqrt(x)       # elementwise square root
sum(x)        # sum of entries
max(x)        # maximum entry
norm(x, "2")  # Euclidean norm
```

### Boolean logic atoms

For mixed-integer programming: `Not()`, `And()`, `Or()`, `Xor()`,
`implies()`, `iff()`.

### Other new atoms

- `perspective(f, s)` for perspective functions
- `FiniteSet(expr, values)` constraint for discrete optimization
- `ceil_expr()`, `floor_expr()` for DQCP problems
- `condition_number()`, `gen_lambda_max()`, `dist_ratio()` for DQCP

## Backward-Compatibility Aliases

- `tv()` is **deprecated**; use `total_variation()` (still works but warns once)
- `norm2(x)` is **deprecated**; use `p_norm(x, 2)` (still works but warns once)
- `multiply(x, y)` is **deprecated**; use `x * y` for elementwise multiplication
- Old `solve()` still works and returns a compatibility list
- Old function names (`problem_status`, `getValue`, etc.) still work
  but emit once-per-session deprecation warnings

## Migration Guide

To migrate code from CVXR 1.x to 1.8.x:

1. Replace `result <- solve(problem)` with `opt_val <- psolve(problem)`
2. Replace `result$getValue(x)` with `value(x)`
3. Replace `result$value` with the return value from `psolve()`
4. Replace `result$status` with `status(problem)`
5. Replace `result$getDualValue(con)` with `dual_value(con)`
6. Update solver names: `"ECOS"` → `"CLARABEL"`, `"GLPK"` → `"HIGHS"`
7. Update `axis` arguments: `axis = NA` → `axis = NULL` (row/column axis values 1 and 2 are unchanged)
8. Replace `A %>>% B` with `PSD(A - B)` if desired
9. Wrap Matrix package objects with `as_cvxr_expr()` before using
   them in CVXR expressions (e.g., `as_cvxr_expr(A) %*% x` instead
   of `A %*% x` when `A` is a `dgCMatrix` or other Matrix class).
   This preserves sparsity. Base R matrices need no wrapping.
10. **Dimension-preserving operations.** CVXR 1.8 preserves 2D shapes
    throughout, matching CVXPY. In particular, axis reductions like
    `sum_entries(X, axis = 2)` now return a proper row vector of shape
    `(1, n)` rather than collapsing to a 1D vector. When comparing
    such a result with an R numeric vector (which CVXR treats as a
    column), you may need to use `t()` or `matrix(..., nrow = 1)` to
    match shapes:

    ```r
    ## Old (worked in CVXR 1.x because axis reductions were 1D):
    sum_entries(X, axis = 2) == target_vec
    ## New (wrap target as row vector to match the (1, n) shape):
    sum_entries(X, axis = 2) == t(target_vec)
    ```

    Similarly, if you extract a scalar from a CVXR result and need a
    plain numeric value, use `as.numeric()` to drop the matrix
    dimensions.

## CRAN Submission Tip {#cran-submission-tip}

If you encounter issues involving the `Rmosek` package while submitting
your package to CRAN, include the following code in
`<your_pkg>/R/zzz.R` to resolve the issue.

```{r cran-tip, eval = FALSE}
## Content of <your_pkg>/R/zzz.R

.onLoad <- function(libname, pkgname) {
  CVXR::exclude_solvers("MOSEK")
}
.onUnload <- function(libname, pkgname) {
  CVXR::include_solvers("MOSEK")
}
```

## Further Reading

- [CVXR website](https://cvxr.rbind.io) — worked examples
- [Package reference](https://www.cvxgrp.org/CVXR/) — full API documentation
- [CVXPY documentation](https://www.cvxpy.org/) — mathematical framework
- Fu, Narasimhan, and Boyd (2020). "CVXR: An R Package for Disciplined
  Convex Optimization." _Journal of Statistical Software_, 94(14).
  doi:10.18637/jss.v094.i14
