CVXR 1.8 is a ground-up rewrite using R’s S7 object system, designed to mirror CVXPY 1.8 for long-term maintainability. It is approximately 4–5x faster than the previous S4-based release and adds many new features.
This vignette summarizes the key changes from CVXR 1.x that may
affect existing users. For the introductory tutorial, see
vignette("cvxr_intro"). For worked examples, visit the CVXR website.
The primary solve function is now psolve(), which
returns the optimal value directly. Variable values are extracted with
value() and problem status with status():
library(CVXR)
x <- Variable(2, name = "x")
prob <- Problem(Minimize(sum_squares(x)), list(x >= 1))
opt_val <- psolve(prob, solver = "CLARABEL")
opt_val
#> [1] 2
value(x)
#> [,1]
#> [1,] 1
#> [2,] 1
status(prob)
#> [1] "optimal"The old solve() still works and returns a
backward-compatible list:
result <- solve(prob, solver = "CLARABEL")
#> ℹ In a future CVXR release, `solve()` will return the optimal value directly
#> (like `psolve()`).
#> ℹ The `$getValue()`/`$getDualValue()` interface will be removed.
#> ℹ New API: `psolve(prob)`, then `value(x)`, `dual_value(constr)`,
#> `status(prob)`.
#> This message is displayed once per session.
result$value
#> [1] 2
result$status
#> [1] "optimal"| Old (CVXR 1.x) | New (CVXR 1.8) |
|---|---|
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) |
get_problem_data(prob, solver) |
problem_data(prob, solver) |
Old function names still work but emit once-per-session deprecation warnings.
CVXR 1.8 ships with four built-in solvers and supports nine additional solvers via optional packages:
installed_solvers()
#> [1] "CLARABEL" "SCS" "OSQP" "HIGHS" "MOSEK" "GUROBI"
#> [7] "GLPK" "GLPK_MI" "ECOS" "ECOS_BB" "CPLEX" "CVXOPT"
#> [13] "PIQP"| Solver | R Package | Problem Classes |
|---|---|---|
| CLARABEL | clarabel |
LP, QP, SOCP, SDP, ExpCone, PowCone |
| SCS | scs |
LP, QP, SOCP, SDP, ExpCone, PowCone |
| OSQP | osqp |
LP, QP |
| HIGHS | highs |
LP, QP, MILP |
| MOSEK | Rmosek |
LP, QP, SOCP, SDP, ExpCone, PowCone |
| GUROBI | gurobi |
LP, QP, SOCP, MIP |
| ECOS / ECOS_BB | ECOSolveR |
LP, SOCP, ExpCone (+ MIP) |
| GLPK / GLPK_MI | Rglpk |
LP (+ MILP) |
| CPLEX | Rcplex |
LP, QP, MIP |
| CVXOPT | cccp |
LP, SOCP, SDP |
| PIQP | piqp |
LP, QP |
CLARABEL is the default solver and handles the widest range of problem types among the built-in solvers. You can specify a solver explicitly:
You can temporarily exclude solvers from automatic selection without uninstalling them:
available_solvers()
#> [1] "CLARABEL" "SCS" "OSQP" "HIGHS" "MOSEK" "GUROBI"
#> [7] "GLPK" "GLPK_MI" "ECOS" "ECOS_BB" "CPLEX" "CVXOPT"
#> [13] "PIQP"
exclude_solvers("SCS")
available_solvers()
#> [1] "CLARABEL" "OSQP" "HIGHS" "MOSEK" "GUROBI" "GLPK"
#> [7] "GLPK_MI" "ECOS" "ECOS_BB" "CPLEX" "CVXOPT" "PIQP"
include_solvers("SCS")Geometric programs are solved by converting to convex form via log-log transformation:
x <- Variable(pos = TRUE, name = "x")
y <- Variable(pos = TRUE, name = "y")
obj <- Minimize(x * y)
constr <- list(x * y >= 40, x <= 20, y >= 2)
prob <- Problem(obj, constr)
cat("Is DGP:", is_dgp(prob), "\n")
#> Is DGP: TRUE
opt_val <- psolve(prob, gp = TRUE, solver = "CLARABEL")
cat("Optimal:", opt_val, " x =", value(x), " y =", value(y), "\n")
#> Optimal: 40 x = 9.96834 y = 4.012704Quasiconvex problems are solved via bisection:
Parameters allow efficient re-solving when only data changes:
n <- 5
x <- Variable(n, name = "x")
lam <- Parameter(nonneg = TRUE, name = "lambda", value = 1.0)
set.seed(42)
A <- matrix(rnorm(10 * n), 10, n)
b <- rnorm(10)
prob <- Problem(Minimize(sum_squares(A %*% x - b) + lam * p_norm(x, 1)))
cat("Is DPP:", is_dpp(prob), "\n")
#> Is DPP: TRUE
psolve(prob, solver = "CLARABEL")
#> [1] 9.741033
value(x)
#> [,1]
#> [1,] 0.1311826
#> [2,] 0.1125202
#> [3,] 0.3561130
#> [4,] 0.5394796
#> [5,] 0.7324974Changing the parameter and re-solving reuses the cached compilation:
Variables can be declared boolean or integer:
CVXR supports complex-valued optimization:
For mixed-integer formulations, boolean logic atoms are available:
b1 <- Variable(boolean = TRUE, name = "b1")
b2 <- Variable(boolean = TRUE, name = "b2")
## implies() returns an Expression; compare with == 1 to make a constraint
prob <- Problem(Maximize(b1 + b2),
list(implies(b1, b2) == 1, b1 + b2 <= 1))
psolve(prob, solver = "HIGHS")
#> [1] 1
cat("b1 =", value(b1), " b2 =", value(b2), "\n")
#> b1 = 0 b2 = 1For bootstrapping or simulation, you can compile once and solve many times:
visualize() provides problem introspection in text or
interactive HTML:
x <- Variable(3, name = "x")
prob <- Problem(Minimize(p_norm(x, 1) + sum_squares(x)),
list(x >= -1, sum(x) == 1))
visualize(prob)
#> ── Expression Tree (MINIMIZE) ──────────────────────────────────────────────────
#> t_3 = AddExpression(...) [convex, \mathbb{R}_+, 1x1]
#> |-- t_1 = Norm1(...) [convex, \mathbb{R}_+, 1x1]
#> | \-- x [affine, 3x1]
#> \-- t_2 = QuadOverLin(...) [convex, \mathbb{R}_+, 1x1]
#> |-- x [affine, 3x1]
#> \-- 1 [constant, 1x1]
#> ── Constraints ─────────────────────────────────────────────────────────────────
#> ✓ [1] Inequality 3x1
#> -1 [constant, 1x1]
#> x [affine, 3x1]
#> ✓ [2] Equality 1x1
#> t_4 = SumEntries(...) [affine, ?, 1x1]
#> \-- x [affine, 3x1]
#> 1 [constant, 1x1]
#> ── SMITH FORM ──────────────────────────────────────────────────────────────────
#> t_{1} = phi^{Norm1}({x})
#> t_{2} = phi^{qol}({x}, {1})
#> t_{3} = {t_{1}} + {t_{2}}
#> t_{4} = phi^{\Sigma}({x})
#> ── RELAXED SMITH FORM ──────────────────────────────────────────────────────────
#> t_{1} >= phi^{Norm1}({x})
#> t_{2} >= phi^{qol}({x}, {1})
#> t_{3} = {t_{1}} + {t_{2}}
#> t_{4} = 1^' {x}
#> ── CONIC FORM ──────────────────────────────────────────────────────────────────
#> t_{1} >= phi^{Norm1}({x}) [conic form: see canonicalizer]
#> (\frac{{1}+t_{2}}{2}, \frac{{1}-t_{2}}{2}, {x}) in Q^{n+2}
#> {1} in Q^1
#> t_{3} = {t_{1}} + {t_{2}}
#> t_{4} = 1^' {x}For interactive HTML output with collapsible expression trees, DCP analysis, and curvature coloring:
| 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 |
cvxr_outer(x, y) |
Outer product |
inv_prod(x) |
Reciprocal of product |
loggamma(x) |
Log of gamma function |
log_normcdf(x) |
Log of standard normal CDF |
cummax_expr(x) |
Cumulative maximum |
dotsort(X, W) |
Weighted sorted dot product |
Standard R math functions work directly on CVXR expressions:
x <- Variable(3, name = "x")
abs(x) # elementwise absolute value
#> CVXR::Abs(x)
sqrt(x) # elementwise square root
#> Power(x, 0.5)
sum(x) # sum of entries
#> SumEntries(x, NULL, FALSE)
max(x) # maximum entry
#> CVXR::MaxEntries(x, NULL, FALSE)
norm(x, "2") # Euclidean norm
#> CVXR::PnormApprox(x, 2, NULL, FALSE, 1024)Not(), And(), Or(),
Xor(), implies(), iff() — for
mixed-integer programming with boolean variables.
perspective(f, s) for perspective functionsFiniteSet(expr, values) constraint for discrete
optimizationceil_expr(), floor_expr() for DQCP
problemscondition_number(), gen_lambda_max(),
dist_ratio() for DQCPTo migrate code from CVXR 1.x to 1.8:
Replace result <- solve(problem) with
opt_val <- psolve(problem)
Replace result$getValue(x) with
value(x)
Replace result$status with
status(problem)
Replace result$getDualValue(con) with
dual_value(con)
Replace axis = NA with axis = NULL
(axis values 1 and 2 are unchanged)
Update solver preferences: the default is now CLARABEL (was ECOS)
Wrap Matrix package objects with as_cvxr_expr()
before using them with CVXR operators (e.g.,
as_cvxr_expr(A_sparse) %*% x instead of
A_sparse %*% x). This preserves sparsity — unlike
as.matrix(), which densifies. Base R matrix
and numeric objects work natively without
wrapping.
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 vector), you may need to use
t() or matrix(..., nrow = 1) to match shapes.
For example:
## 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.
All old function names continue to work with deprecation warnings.