Skip to contents

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 Parameters; 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

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 = NAaxis = 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