This is the first CRAN release since 1.8.2 and is a large one: it tracks CVXPY 1.9.1 and folds in the changes from the internal 1.8.2-1 and 1.9.0 development cycles. The headline additions are a derivative API for differentiable convex programs, disciplined nonlinear programming (DNLP), and interval-bounds propagation with native solver-bound support.
psolve(prob, nlp = TRUE). The
problem must satisfy the new is_dnlp() grammar (disciplined
nonlinear program); DCP problems are a subset, so any DCP problem is
also a valid DNLP.sin(), cos(), tan(),
sinh(), tanh(), asinh(), atanh(), and normcdf().prod() is now recognized as a smooth atom, so problems involving
products of entries (including prod_entries(X, axis = ...)) are
DNLP and solvable through the NLP path.is_atom_smooth()
matching CVXPY 1.9: affine atoms, exp, log, entr, logistic,
kl_div, rel_entr, xexp, power (with a constant exponent),
geo_mean, log_sum_exp, quad_form, quad_over_lin, and prod.
As a result smooth convex/concave functions such as sum_squares()
are linearizable in both directions (is_smooth() is TRUE), while
piecewise atoms (abs, min, max) remain convex/concave-only.
New predicate exports: is_smooth(), is_atom_smooth(),
is_linearizable_convex(), is_linearizable_concave().abs, max, max_elemwise,
norm_inf, sum_largest, and sum_smallest) now initialize their
epigraph variables during canonicalization, so the NLP backend has a
complete starting point. sum_largest initializes its threshold to the
(k+1)-th largest entry, matching CVXPY's warm-start fix.convolve() atom, CVXPY 1.9's preferred (numpy-style) name for the
1-D discrete-convolution conv() atom. For numeric input it falls through
to stats::convolve().sparsediff package and can solve through the optional ipopt and
Uno R packages. NLP solver names: "IPOPT", "UNO" (and the
variants "uno_ipm" / "uno_sqp"), "KNITRO", and "COPT";
"KNITRO" and "COPT" are registered but require solver bindings
not yet available in R. When installed, IPOPT is the first automatic
NLP solver, matching CVXPY's preference order.quad_over_lin() and sum_squares() now canonicalize axis-aware
reductions (axis = 1 and axis = 2) into batched second-order cone
constraints, closing the CVXPY parity gap for
sum_squares(..., axis = ...) in objectives and constraints.UNO interface defaults to the interior-point ipopt preset
(plus MUMPS), which differs from CVXPY's filtersqp default.
CVXPY's filtersqp uses the BQPD active-set solver for its quadratic
subproblems, which handles indefinite Hessians. The bundled Uno build
is HiGHS-only (BQPD is not bundled and is not CRAN-license
compatible), and HiGHS solves only convex quadratic subproblems, so
filtersqp fails on nonconvex DNLPs. The interior-point ipopt
preset factors the regularized KKT system with MUMPS and is robust on
both convex and nonconvex problems. Force the SQP path with
solver = "uno_sqp" (or preset = "filtersqp"); it is reliable only
for problems whose subproblem Hessians stay positive semidefinite.psolve(prob, nlp = TRUE, best_of = n) solves from n random initial
points and keeps the best result. Random initialization draws from a
variable's sample_bounds(var) <- c(low, high) (when set) or its
finite variable bounds. The per-run objectives are available via
solver_stats(prob)@extra_stats$all_objs_from_best_of.dual_value() returns the constraint duals
recovered from the solver (a CVXR addition; the duals are not exposed
by CVXPY's NLP interface).diffcp R package:
psolve(prob, requires_grad = TRUE) followed by backward(prob) or
derivative(prob); gradient(x)<- and delta(x)<- set tangent /
cotangent values on leaves. The chain rule is wired through Dgp2Dcp
(log/exp) and Complex2Real (real/imag split).get_bounds() now works on any expression, not just variables: it
propagates interval bounds through affine, elementwise, and
piecewise-linear atoms (e.g. get_bounds(A %*% x + b),
get_bounds(abs(x)), get_bounds(sum(x))). Bounds are returned shaped
to the expression's dimensions.Matrix objects or symbolic bounds
involving Parameters. get_bounds() preserves sparse numeric bounds and
skips symbolic bounds, while solve-time attribute lowering enforces the
symbolic constraints. Parameter values containing matching Inf entries
now validate correctly.Parameter bounds updates native solver bounds on DPP re-solves.Parameters before rebuilding their full expressions.Parameters embedded in expression bounds and include
those bounds in DPP/DGP compliance checks.gp = TRUE (e.g. Variable(pos = TRUE, bounds = list(lb, ub)) with lb/ub
Parameters). The DGP reduction log-transforms the bounds into the log domain
and lowers them to constraints; parametric bounds canonicalize through the DGP
tree without eagerly evaluating log(value(param)), so DPP re-solves with
changed bound parameters work.is_dpp() gains a context argument ("dcp" or "dgp"), matching CVXPY's
is_dpp(context=...). In the "dgp" context a variable's symbolic bounds must
be log-log-affine (e.g. a product of positive Parameters is DGP-DPP though
not DCP-DPP; norm()/sum() bounds are rejected).partial_optimize() transform.Rcplex quadratic constraints.sign() atom (DQCP, scalar input).psolve() gains a solver_path argument for solver fallback chains.set_label() / label<- / format_labeled() for attaching
user-supplied labels to expressions.power(), geo_mean(), and p_norm() with approx = TRUE now
warn when the selected solver supports power cones (approx = FALSE
uses the exact PowCone3D / PowConeND form).Rmosek 11.1.1+).solver_stats() for MOSEK now populates solve_time, num_iters,
and extra_stats.param_dict(), var_dict(), and size_metrics() accessors on
Problem.Variable(value = ...) accepts an initial value at construction.CallbackParam class — a Parameter subclass whose value is
computed on every read by a user-supplied callback.project() accepts index-based boolean = c(i, j, ...) and
integer = c(i, j, ...) attributes (1-based).validate_arguments() now called on Cummax, Cumprod, and
MinEntries constructors..fast_new helper for S7 expression construction
(roughly 40% lower wall-clock on atom-dense and PSD-heavy problems),
and routing S7 class-membership checks on the hot canonicalization
path through a cached check on the object's class vector instead of
S7::S7_inherits() (which rebuilt the class name on every call; the
per-node cone-detection scan benefits most). Deterministic memory
allocation is unchanged. These are internal changes with no
user-visible API difference.problem_data() and 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 ...; enforce_dpp = TRUE now raises on a non-DPP
parametrized problem instead of falling back to a non-DPP compile.quad_over_lin(expr, constant) so the
decision uses the canonicalized denominator, matching CVXPY 1.9 behavior for
cases such as quad_over_lin(exp(x), 1) that should keep ExpCone without
adding an SOC block.Variable(c(n, n), diag = TRUE) handling through CvxAttr2Constr.perspective() canonicalizer on PSD, NSD, and diag matrix variables.project() on sparse Matrix-package objects.Complex2Real because CVXR's R
sparse-matrix backend cannot carry complex sparse parameter tensors. Ordinary
complex expression solving is still supported through Complex2Real.scip package. Supports
LP, SOCP, MI-LP, and MI-SOCP problems. SCIP is registered in the
conic solver path with SUPPORTED_CONSTRAINTS = list(Zero, NonNeg, SOC) and MIP_CAPABLE = TRUE.scip_params sub-list
(matching CVXPY convention) for path-style parameter names like
"limits/time", "limits/gap".scip package lacks dual API).TestSCIP class.xpress package with
dual-interface architecture: QP path (XPRESS_QP_Solver) for LP/QP
and conic path (XPRESS_Conic_Solver) for SOCP/MI-LP/MI-SOCP.
Both paths support Zero and NonNeg constraints; the conic path also
supports SOC. MIP warm-start is supported.diag() now works on CVXR expressions: diag(vector_expr) creates
a diagonal matrix (DiagVec), diag(square_matrix_expr) extracts
the diagonal (DiagMat). Falls through to Matrix::diag() for
non-Expression inputs, correctly handling both Matrix S4 objects
and base R matrices.norm() fallthrough now delegates to Matrix::norm() instead of
base::norm(), fixing norm(sparse_matrix) when CVXR is loaded.t() and mean() switched from S7 method() to
registerS3method() to avoid demoting Matrix's S4 generics.All applicable bug fixes from CVXPY v1.8.2 have been ported, each
annotated with ## CVXPY v1.8.2 fix: in the source.
solver_opts() constructor for unified solver options. Standard
tolerance parameters (feastol, reltol, abstol, num_iter) and
solver-specific parameters now flow through psolve(...) via the
solver_opts() constructor.use_quad_obj option (default TRUE): when FALSE, forces
conic decomposition path instead of QP, enabling quad_form_canon
to detect indefinite P matrices.supports_quad_obj() generic on conic solvers (Clarabel and SCS
return TRUE, others FALSE).SpecialIndex atom (mirroring CVXPY's special_index) enables
R-idiomatic element-wise selection on matrix expressions:
x[cbind(rows, cols)]x[mask]x[c(1, 5, 9)]x[c(TRUE, FALSE, ...)]ind <- which(!is.na(Rmiss), arr.ind = TRUE)
prob <- Problem(Minimize(obj), list(X[ind] == Rmiss[ind]))
x[i] on a matrix expression errored with "Single-index
selection on matrices not supported." The workaround required an
element-wise multiply with a binary mask, which was unintuitive.sum_squares(), power(x, 2),
quad_over_lin(), and huber() when the first argument has many
elements (e.g., regression residuals with thousands of observations).
The quadratic canonicalizer was converting a sparse identity matrix
to dense form. Memory now scales linearly with problem size.This is a ground-up rewrite of CVXR using R's S7 object system, designed to be isomorphic with CVXPY 1.8.1 for long-term maintainability. ~4-5x faster than CVXR 1.0-15 on typical problems.
boolean = TRUE or integer = TRUE in Variable()).Parameter() class with DPP (Disciplined
Parameterized Programming) for efficient re-solves when only
parameter values change.psolve() as the primary solve interface, returning the optimal
value directly.solve() backward-compatibility wrapper returning a cvxr_result
list with $value, $status, $solver, $getValue(), and
$getDualValue().verbose = TRUE option in psolve() for structured solve output
with timing information.feastol, reltol, abstol, num_iter)
in psolve() with automatic translation to solver-native names via
solver_default_param(). Solver-native parameters in ... take priority.problem_data(), solve_via_data(),
problem_unpack_results() for compile-once/solve-many workflows.visualize() for problem introspection: text display with Smith
form annotations, interactive HTML output (D3 tree + KaTeX),
on-demand DCP violation analysis for non-compliant problems,
curvature coloring on constraint nodes, and matrix stuffing
visualization (Stages 4-5: Standard Form + Solver Data) via the
new solver parameter.as_cvxr_expr(). Sparse Matrix
objects (dgCMatrix, dgeMatrix, etc.) use S4 dispatch which
preempts S7/S3, so direct arithmetic like sparseA %*% x fails.
Wrapping with as_cvxr_expr(sparseA) %*% x converts to a CVXR
Constant while preserving sparsity. Base R matrix and numeric
work natively without wrapping.+ and - operators, matching CVXPY's
Expression.broadcast(). Expressions with compatible but different
shapes (e.g., (1, n) + (m, 1) → (m, n)) now work correctly in
constraints and objectives. Previously only scalar promotion was
supported.MatrixFrac, TrInv,
SigmaMax, NormNuc, LambdaSumLargest) are now correctly
classified, ensuring solvers like ECOS that don't support SDP are
rejected with a clear error message instead of a cryptic dimension
mismatch. Approximate variants (PnormApprox, PowerApprox,
GeoMeanApprox) correctly route to SOC; exact variants route to
their native cone types (PowCone3D, PowConeND).Variable(n) instead of
new("Variable", n).chooseOpsMethod() and S7 @ support).solve() now returns a cvxr_result S3 list, not an S4 object.
Use psolve() for a direct numeric return value.getValue(x) and getDualValue(con) are deprecated. Use value(x)
and dual_value(con) after solving instead.problem_status(), problem_solution(), and get_problem_data() are
deprecated. Use status(), solution(), and problem_data() instead.axis parameter uses 1-based indexing matching R's apply()
MARGIN convention: axis = 1 for row-wise (reduce columns),
axis = 2 for column-wise (reduce rows), axis = NULL for all
entries.is(x, "Variable") no longer work. Use
S7_inherits(x, Variable).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).scalar_product(x, y) -- alias for vdot.cvxr_outer(x, y) -- outer product of two vectors.inv_prod(x) -- reciprocal of product of entries.loggamma(x) -- elementwise log of gamma function (piecewise linear
approximation).log_normcdf(x) -- elementwise log of standard normal CDF (quadratic
approximation).cummax_expr(x, axis) -- cumulative maximum along an axis.dotsort(X, W) -- weighted sorted dot product (generalization of
sum_largest/sum_smallest).diff_pos(), resolvent() -- DGP convenience functions.status(prob) -- returns the solution status (replaces problem_status()).solution(prob) -- returns the raw Solution object (replaces problem_solution()).problem_data(prob, solver) -- returns solver-ready data (replaces get_problem_data()).tv() is a deprecated alias for total_variation().norm2(x) is a deprecated alias for p_norm(x, 2).multiply(x, y) is a deprecated alias for x * y.installed_solvers() lists available solver packages.psolve(prob, gp = TRUE).
New atoms: prod_entries(), cumprod(), one_minus_pos(),
eye_minus_inv(), pf_eigenvalue(), gmatmul().psolve(prob, qcp = TRUE).
New atoms: ceil_expr(), floor_expr(), condition_number(),
gen_lambda_max(), dist_ratio().Variable(n, complex = TRUE) and
Complex2Real reduction. Atoms: Conj_(), Real_(), Imag_(),
expr_H() for conjugate transpose.perspective(f, s) atom for perspective functions.FiniteSet(expr, values) constraint for discrete optimization.Not(), And(), Or(), Xor(), implies(), iff().%>>% and %<<% operators for PSD and NSD constraints.as_cvxr_expr() helper for wrapping R objects as CVXR constants.
Required for Matrix package objects (dgCMatrix, dgeMatrix, etc.)
which use S4 dispatch that preempts S7/S3. Preserves sparsity
(unlike as.matrix()). Base R matrix/numeric work natively
without wrapping.kron() KRON_L coefficient matrix construction.CvxAttr2Constr index type and matrix variable handling.diag offset for super/sub-diagonals (k != 0).suc - slc reconstruction).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.highs 1.14) but the
upstream R highs PR exposing setSolution() has not yet been
actioned by the maintainers; lives on branch highs-warm-start.complex2real.py:56 declares
UNIMPLEMENTED_COMPLEX_DUALS = (SOC, OpRelEntrConeQuad)). Complex
SOC primal canonicalization works.clarabel requirement and use enhance rather than import
until really necessary (Issue
142).user_limit etc to be
infeasible_inaccurate to match CVXPY for gurobisolve().upper_tri_to_full to C++Power object (Issue 145).qp2quad_form.Rextract_quadratic_coeffs to use sparse matrix and sweep in
place for better memory use (reported by Marissa Reitsma)Rmosek to be removed from CRAN, so moved to drat repodgCMatrix
via (as(as(<matrix>, "CsparseMatrix"), "generalMatrix"))inherits()MOSEK and uncommented
associated test.ParameterOSQP and updates to solver cacheDropped delay_load parameter dropped in
reticulate::import_from_path, per changes in reticulate.
Cleaned up hooks into reticulate for commercial solvers.
LPSOLVE via lpSolveAPIGLPK via RglpkMOSEKGUROBIdrop = FALSE (in function Index.get_special_slice) as suspected.Added a note that CVXR can probably be compiled from source for earlier versions of R. This is issue #24
Bug fix: issue #28
Function intf_sign (interface.R) was unnecessarily using a
tolerance parameter, now eliminated.
Updated Solver.solve to adapt to new ECOSolveR. Require version 0.4 of ECOSolveR now.
Updated unpack_results to behave exactly like in CVXPY. Added
documentation and testthat tests. Documented in the Speed.