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 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.
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 pathsin(), cos(), tan(),
sinh(), tanh(), asinh(),
atanh(), normcdf(), and
prod().best_of = n solves from n random initial
points (drawn from variable bounds or sample_bounds()) and
keeps the best.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.
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 xReverse 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.
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 atomsVariable 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.
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 = ...).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
....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 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.
boolean = TRUE or
integer = TRUE in Variable()).Parameter()
class and EvalParams reduction.psolve(prob, gp = TRUE).psolve(prob, qcp = TRUE).Variable(n, complex = TRUE).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.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 statusThe old solve() still works but returns a
backward-compatible list:
| 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) |
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 use PSD(A - B) instead of
A %>>% B (though %>>% and
%<<% operators are still available for backward
compatibility).
| 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).
| 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 |
Standard R math functions work directly on CVXR expressions:
For mixed-integer programming: Not(),
And(), Or(), Xor(),
implies(), iff().
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 DQCPtv() 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 multiplicationsolve() still works and returns a compatibility
listproblem_status,
getValue, etc.) still work but emit once-per-session
deprecation warningsTo migrate code from CVXR 1.x to 1.8.x:
Replace result <- solve(problem) with
opt_val <- psolve(problem)
Replace result$getValue(x) with
value(x)
Replace result$value with the return value from
psolve()
Replace result$status with
status(problem)
Replace result$getDualValue(con) with
dual_value(con)
Update solver names: "ECOS" →
"CLARABEL", "GLPK" →
"HIGHS"
Update axis arguments: axis = NA →
axis = NULL (row/column axis values 1 and 2 are
unchanged)
Replace A %>>% B with PSD(A - B)
if desired
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.
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.
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.