# What’s New in CVXR

#### Anqi Fu, Balasubramanian Narasimhan, and Stephen Boyd

#### 2026-08-23

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.

- CVXR 1.9.1 — disciplined nonlinear programming, derivatives, bounds propagation, new atoms
- CVXR 1.8.x — the ground-up S7 rewrite

# CVXR 1.9.1

`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).

    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 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):

    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):

    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 examples and Sensitivity Analysis.

## 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:

    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

## Complete Rewrite Using S7

CVXR 1.8.x is a ground-up rewrite using R’s S7 object system, designed to be isomorphic with CVXPY 1.8.2 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:

    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:

    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).

## 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:

    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:

        ## 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

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.

    ## 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 — worked examples
- Package reference — full API documentation
- CVXPY documentation — 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