What’s New in CVXR 1.8.x

Author

Anqi Fu and Balasubramanian Narasimhan

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.1 for long-term maintainability. This page summarizes the key changes from CVXR 1.x that may affect users.

Note: Package authors encountering Rmosek issues while submitting to CRAN should especially see CRAN Submission Tip below.

New Features

  • S7 class system replaces S4 for all expression, constraint, and problem classes. Significantly faster construction and method dispatch.
  • 13 solvers: CLARABEL (default), SCS, OSQP, HiGHS, MOSEK, Gurobi, GLPK, GLPK_MI, ECOS, ECOS_BB, CPLEX, CVXOPT, and PIQP.
  • Mixed-integer programming via GLPK_MI, ECOS_BB, Gurobi, CPLEX, or HiGHS (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 6 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.

Breaking Changes from CVXR 1.x

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 compatibility list:

result <- solve(prob)
result$value       # optimal value
result$getValue(x) # variable value (deprecated)
result$status      # problem status

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)
  • Default solver: CLARABEL (replaces ECOS)

Supported solvers

Solver R Package Type Problem Classes
CLARABEL clarabel (>= 0.11.2) Conic LP, QP, SOCP, SDP, ExpCone, PowCone
SCS scs (>= 3.2.7) Conic LP, QP, SOCP, SDP, ExpCone, PowCone
MOSEK Rmosek (>= 11.1.1) Conic LP, QP, SOCP, SDP, ExpCone, PowCone
ECOS ECOSolveR (>= 0.6) Conic LP, SOCP, ExpCone
ECOS_BB ECOSolveR (>= 0.6) Conic LP, SOCP, ExpCone + MI
GUROBI gurobi (>= 13.0.1) Conic/QP LP, QP, SOCP, MI
GLPK Rglpk (>= 0.6.5.1) Conic LP
GLPK_MI Rglpk (>= 0.6.5.1) Conic LP, MILP
HIGHS highs (>= 1.12.0.3) Conic/QP LP, QP, MILP
CVXOPT cccp (>= 0.3.3) Conic LP, SOCP
OSQP osqp (>= 1.0.0) QP LP, QP
CPLEX Rcplex (>= 0.3.8) QP LP, QP, MI
PIQP piqp (>= 0.6.2) QP LP, QP

New Atoms and Functions

Convenience atoms

Function Description
ptp(x, axis, keepdims) Peak-to-peak (range): max(x) - min(x)
cvxr_mean(x, axis, keepdims) Arithmetic mean along an axis
cvxr_std(x, axis, keepdims, ddof) Standard deviation
cvxr_var(x, axis, keepdims, ddof) 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, axis) Cumulative maximum along an axis
dotsort(X, W) Weighted sorted dot product

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

Backward-Compatibility Aliases

  • tv() is an alias for total_variation()
  • norm2(x) is an alias for p_norm(x, 2)
  • multiply(x, y) is an alias for elementwise multiplication
  • installed_solvers() lists available solver packages
  • 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. Replace add_to_solver_blacklist() calls (no longer needed)

  7. Update solver names: "ECOS""CLARABEL", "GLPK""HIGHS"

  8. Update axis arguments: axis = NAaxis = NULL (row/column axis values 1 and 2 are unchanged)

  9. Replace A %>>% B with PSD(A - B) if desired

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

  11. 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 Rmosek package while submitting your package to CRAN, just 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")
}

Known Limitations

  • Matrix package objects (dgCMatrix, dgeMatrix, ddiMatrix, sparseVector) cannot be used directly with CVXR operators due to S4 dispatch preempting S7/S3. Wrap with as_cvxr_expr() first. Base R matrix/numeric work natively. This is an R dispatch limitation requiring upstream changes in S7 and/or Matrix.
  • Warm-start for HiGHS is blocked (R highs package lacks setSolution() API).
  • Derivative/sensitivity API deferred (requires diffcp, no R equivalent).

References