These fix wrong answers, so code that relied on the old results will see different numbers.
quad_form(w, Sigma)) with every cross term halved. Gurobi
now receives the full matrix, as in CVXPY. Present in 1.9.2.cvxr_norm(X, p) and norm(X, type) now
agree with base R’s norm() and CVXPY on matrices: with no
axis, p = 1, 2 and
Inf are the maximum absolute column sum, the largest
singular value and the maximum absolute row sum. They were entrywise;
use cvxr_norm(X, "fro") or norm1(X) for those.
p on a matrix or row vector is now an error,
“Unsupported matrix norm.”, as in CVXPY; use p_norm(x, p)
for the entrywise norm.norm() on an expression accepts every base R type
("O", "M" and "E" failed, and
"I" was the largest absolute entry). With no
type it is the spectral norm ("2"), as in
CVXPY.gmatmul(A, X) with a Parameter in both
A and X gave a wrong answer under
gp = TRUE, and parameter substitution rebuilt it with
A and X swapped. Both fixed (CVXPY 1.9.3 still
has the swap).x / A with a matrix or vector A divided
every entry of x by A[1, 1]. It now divides
entrywise, as in CVXPY. Division by a scalar is unchanged. Present in
1.9.2.dgCMatrix gave wrong answers or errors: a unit
Diagonal(n) was read as all zeros, so
quad_form(x, Diagonal(n)) was unbounded, and symmetric,
triangular, triplet, pattern and logical sparse matrices failed in
canonicalization. Every sparse input is now stored as a general double
dgCMatrix, as in CVXPY, and a logical matrix constant keeps
its shape instead of becoming a column. Present in 1.9.2.bounds matrix was applied only at its stored
entries, so a symmetric (dsCMatrix) bound on a dense
variable left half the entries unbounded and a triplet matrix with
repeated entries used the first copy. As in CVXPY, a sparse bound is now
accepted only on a sparsity variable with the same pattern,
and repeated entries are summed. Solvers that take variable bounds
natively (HiGHS, GUROBI, SCIP, XPRESS) now receive such bounds as bounds
rather than as constraints. Present in 1.9.2.perspective() of a function of a
symmetric, PSD or NSD matrix
variable of size 3 or more returned a wrong optimum.sum_entries(x, axis = 2) of a one-row expression gave
wrong results or crashed R. Present in 1.9.2.%*% with a scalar operand that does not conform
(2 %*% x for a 2 x 3 x) or a non-conforming 1
x 1 operand built a product that gave a scalar product or crashed R.
Both are now errors, as in base R; use * to multiply by a
scalar. A scalar operand that conforms (x %*% 2 for a 3 x 1
x) is a product, as in base R.x / d with shapes that do not broadcast, such as a 2 x
3 x and a length-3 d, recycled the divisor
silently. It is now an error, as in CVXPY; a row or column divisor still
broadcasts.value() of a sum, a product or an index of a
sparse-matrix constant failed with “non-conformable arrays” or
“subscript out of bounds”, and a sum of two sparse constants came back
flattened to one column. These now evaluate; a mixed sparse and dense
result is a base matrix, a sparse one stays sparse.sum(), max(), min() and
prod() of an expression now honor axis and
keepdims, as in CVXPY. A named axis was taken
as one more operand, so sum(x, axis = 2) returned
sum(x + 2). Any other name labels an operand, as in
base::sum(), so sum(a = x) and
do.call(sum, parts) with a named list work as before.sum(), prod(), max() and
min() with more than one operand now mean what they mean in
base R: each operand is reduced on its own and the results combined, so
sum(x, 2) is sum(x) + 2 and
max(x, y) is one scalar, the largest entry of either. They
were sum(x + 2), prod(x * y) and the
elementwise maximum and minimum; use max_elemwise() and
min_elemwise() for those.matrix_trace(A * B) returned the trace of the matrix
product (CVXPY 1.9.3, #3492).cumsum(X) of a matrix expression is now column-wise, as
in CVXPY and as cumprod() and cummax() already
were; value() and the solver used to disagree.
cumsum_axis(X, axis = NULL) gives all entries in
column-major order as a column.lambda_sum_largest(X, k) accepts a fractional
k, as CVXPY does; k was silently
truncated.min_elemwise() and
max_elemwise() with operands of different shapes now
broadcast as NumPy does; a row operand was permuted.derivative() and backward() through a
Hermitian variable or parameter of size 4 or more had imaginary entries
transposed.backward() through a sparsity variable
returned garbage, and on the NLP path the bounds of a
sparsity variable were dropped. A recovered variable value
is now projected onto its attribute (a nonneg -1e-9 reads
back as 0), as in CVXPY.psolve(solver = "CLARABEL", warm_start = TRUE)
re-solves from scratch when the cones changed (e.g. a parametric
power-cone exponent), instead of returning the previous optimum. CVXPY
1.9.3 still has this defect.PSD/NSD lost
its cone (#3342), and one declared diag or with a
sparsity pattern solved as dense (CVXPY 1.9.3 still ignores
those). Both are now enforced.conv(), cumsum() and kron()
were treated as log-log affine, so a geometric program containing them
was accepted and could solve to a wrong value. They are now not DGP, as
in CVXPY, and such a problem is rejected.problem_data() (or
get_problem_data()) on a geometric program between two
solves made the next solve after a parameter change return the previous
optimum. problem_data() now goes through the same compile
cache as psolve(), as in CVXPY.max_elemwise() and min_elemwise() of a
column and a row recycled instead of broadcasting, so
psolve() returned a wrong objective and a second solve
failed.log(x, base) on an expression ignored base
and gave the natural log. It now honors base, which must be
a positive number other than 1.outer(x, y, FUN) on an expression ignored
FUN and gave the product. Any FUN other than
"*" is now an error.x[NULL, ] and x[NULL] on an expression
returned all of x. A NULL subscript is now an
error.x[c(TRUE, NA, FALSE), ] on an expression dropped the
NA. An NA or character subscript is now a
clear error instead of an obscure one.diag(x, nrow) on an expression ignored
nrow. nrow, ncol and
names are now errors there; the offset is
k.log1p(x) on an expression is now the log1p
atom instead of log(x + 1), so it is accurate near 0 and
keeps the sign of x.geo_mean() accepts an argument of any shape and gains
axis and keepdims (CVXPY 1.9.3, #3493).cvxr_var() supports axis and
keepdims, like cvxr_mean() and
cvxr_std() (#3496).cvxr_norm(X, "nuc") returns the nuclear norm (it
errored).geo_mean(x, approx = FALSE), and any
PowConeND constraint, now solve on SCS and MOSEK, not just
Clarabel. PowConeND() with a row-vector z, or
a 1 x n W with axis = 1, now works and has
correct duals.perspective(f, s) where f needs a positive
semidefinite cone (for example lambda_max() or
sigma_max()) now solves on Clarabel, SCS and MOSEK.grad() works for partial_optimize()
expressions and lambda_sum_largest(), and is correct for
complex arguments of norm_nuc(), sigma_max(),
quad_form(), quad_over_lin() and
matrix_frac().indicator() and partial_optimize()
expressions now have bounds, as in CVXPY: (0, Inf) for an
indicator and unbounded for a partial problem.symmetric,
PSD, NSD, diag or
sparsity variable now solves on QP-capable solvers (#3384),
and quad_over_lin() / sum_squares() with an
axis take the QP path, so OSQP accepts them.psolve(..., nlp = TRUE)) now needs
sparsediff (>= 0.6.1) and consequences thereof. It also
accepts NonNeg(), NonPos() and
Zero() constraints, which it used to abort on.curvatures(), log_log_curvature() and
is_diag(), as CVXPY’s Expression.curvatures,
Expression.log_log_curvature and
Leaf.is_diag().curvature() now reports log-log and quasi curvature
(e.g. "LOG-LOG CONVEX", "QUASILINEAR") instead
of "UNKNOWN", as in CVXPY.psolve() now checks that every Parameter
has a value before it starts any solve, as CVXPY does.psolve(gp = TRUE) now checks variable bounds when
deciding whether a parameterized problem is DPP, as CVXPY does, and
requires a conic solver.is_dpp() is now one DCP pass inside the DPP scope, as
in CVXPY, instead of that pass plus a recursion over every
argument.quad_over_lin(x, p) with a Parameter
denominator (#2433), a PowCone3D()/PowConeND()
with a Parameter exponent, and dotsort(x, W)
with a non-affine parametric W. gmatmul(A, X)
requires a parametric A to be the Parameter
itself. cumsum() of a Parameter is DPP
(#3398).psolve(requires_grad = TRUE) checks match CVXPY: a
non-DPP problem is a DPPError, qcp = TRUE is
refused, and solver = "SCS" routes to DIFFCP.SolverError.psolve(solver_path = ...) forwards every
psolve() argument to each attempt
(enforce_dpp, ignore_dpp and nlp
were dropped), and only a SolverError moves on to the next
solver, as in CVXPY. The conditions CVXPY raises as
SolverError now carry that class in R too.bfs = TRUE and save_file = <path> are
accepted.highs_options list,
as in CVXPY, and an option HiGHS rejects is an error instead of being
dropped. A stop on mip_max_improving_sols returns the
incumbent with status user_limit.warm_start = TRUE seeds the start point from
the variables’ values, save_file writes the model, and
env is rejected, as in CVXPY.
solver_stats(prob)@num_iters is 0 instead of
NULL when presolve finished the problem.reoptimize = TRUE re-solves with presolve off on
“infeasible or unbounded”, as in CVXPY; solve_time is set;
unknown options abort. A continuous problem with a second-order cone now
returns dual values (all were missing), as in CVXPY.dual_value() (#3197), and extra_stats
is the full SCS result.solver_stats(prob)@extra_stats now carries the raw
solver result for OSQP, ECOS, ECOS_BB, Gurobi, DIFFCP, HiGHS and PIQP,
as CVXPY does, and solution(prob) holds only the problem’s
own variables.backend = "dense" builds a dense model (it was
silently sparse).write_mps path that cannot be written is an
error (it was silently skipped), and a failed optimization reports the
solver’s message as a warning before the solver-error status.infeasible, as in CVXPY; it failed with a solver error.
GLPK’s presolve is now on by default (pass presolve = FALSE
to turn it off).psolve() differed
from the same draws without the solve. Present in every earlier
version.diag() on an ordinary R matrix or vector now behaves
exactly as base R’s diag(): diag(nrow = 3) and
names = FALSE work, and k (expressions only)
is an error instead of being ignored.norm(A) on an ordinary R matrix without a
type now uses base R’s default one-norm ("O"),
not the spectral norm; expressions keep "2".convolve() on ordinary numeric vectors now returns
exactly stats::convolve()’s result, with its
conj and type arguments, as the 1.9.2 NEWS
promised; expressions still get the numpy-style convolution.sd() and var() on an expression are
cvxr_std() and cvxr_var() (ddof 0, as in
CVXPY); na.rm, use and a second variable
y now give a clear error instead of an obscure one.mean() on an expression is cvxr_mean();
trim and na.rm now give a clear error, and an
axis other than 1 or 2 (such as 1.5) is an error instead of
being truncated.x^c(1, 2),
power(x, c(1, 2))) is now a clear error; with
approx = FALSE it was silently accepted.cvxr_power(), the same function as
power(). power() masks the unrelated
stats::power(), a glm() link, so with CVXR
attached write stats::power() in a glm()
family.bounds accept a 1-d array, a
sparseVector and a sparse matrix with unsorted row indices,
which Constant() already accepted.power(x, p) now takes its sign, curvature, domain,
value and printed name from p as written, as CVXPY does,
rather than from the rational approximation used to solve it.NonPos(expr) explicitly now warns, once per
session, that it is deprecated in favor of operator overloading
(expr <= 0) or NonNeg(-expr), in CVXPY’s
words and with class CvxpyDeprecationWarning.axis; an
axis with cvxr_norm(X, "fro") or
"nuc" (#3501); a complex variable declared
nonneg, nonpos, pos,
neg or with bounds, which were silently
ignored (#3342); partial_optimize() of a complex inner
problem.allclose tolerance (relative
1e-5, absolute 1e-8), as in CVXPY; quad_form(x, P) used to
reject a P that CVXPY accepts.get_bounds(),
grad() and domain() of several atoms,
dual_cone(), is_symmetric() of
Re(X), constr_size(), is_dpp()
with an unsupported context, interval bounds of matrix products, and
is_quadratic() of x / y with a non-constant
y (now FALSE).
value(constraint, tolerance = ) exposes the violation
tolerance.visualize() no longer errors on a problem containing
sqrt(), inv_pos() or a fractional
power() (broken in 1.9.2).cvxr_norm(x, p) with a string p that is
not a norm name reports “Unsupported norm option”, as CVXPY does; it was
an R internal error. Assigning a sparse value containing
Inf to a sparsity variable works; it was an R internal
error.max_elemwise() and min_elemwise() take
na.rm, and hstack() and vstack()
take deparse.level, as pmax() and
rbind() do; neither has an effect.
max_elemwise(u, v, na.rm = TRUE) used to take
TRUE as a third operand. Named operands, as from
do.call(vstack, split(...)), are accepted as before.residual() and violation() of an
SOC constraint whose value is a sparse matrix are right;
they were garbage or an error. A PowConeND with a sparse
alpha keeps one cone per column on solvers with native
N-dimensional power cones. A sparse constant prints as
[n x m matrix].norm1() and log_sum_exp() with an
axis evaluate on a sparse value; they were an error. So do
length_expr() and log_det(), and
visualize() shows a 1x1 sparse constant as its number.matrix_frac(x, P) with a non-square constant
P reports the shape error; it said P was
singular.length_expr(), eye_minus_inv(),
huber() and dotsort() reject a complex
argument, as CVXPY does; they accepted one.as(<ngCMatrix>, "dgCMatrix") is deprecated, and
Gurobi QPs no longer trigger the dtCMatrix one.reduction_accepts() now
refuses a mixed-integer problem when the solver is not MIP-capable, as
CVXPY’s ConicSolver.accepts does. psolve()
already refused such a solve while choosing the solver, so no solve
changes. constr_expr() returns lhs - rhs for
==, <= and >=, as CVXPY’s
.expr does.sum_squares(w - q) goes from 21 ms
/ 24 MB to 11 ms / 0.9 MB, an SOC problem from 17.5 ms / 12.7 MB to 12
ms / 0.9 MB, an LP with a 100 x 500 dense matrix from 55 ms / 23 MB to
50 ms / 8 MB.sum_squares(y - q),
quad_form(y, P) + t(q) %*% y, …) now keeps its parameter
program across solves, as in CVXPY.gp, ignore_dpp and use_quad_obj;
enforce_dpp no longer takes part.abs(A %*% x) with a large sparse
A on HiGHS, Gurobi, SCIP or XPRESS allocated two dense
copies of A.psolve(nlp = TRUE) hands a dense constant matrix with
at least 5 percent nonzeros to the derivative engine as dense, as CVXPY
does: 1.7 to 2.6 times faster for a dense 1000 x 1000 A.
Pass a sparse Matrix to keep the sparse form for a
structured matrix.psolve(nlp = TRUE) no longer makes dense copies of a
sparse constant matrix while building the problem: a solve with a 3000 x
3000 one allocated 208 MB and now allocates 2 MB.This release tracks CVXPY 1.9.2. It is chiefly a correctness release: a number of bugs that returned plausible wrong answers without any error are fixed, and dual variables now come back with the shape of their constraint.
sum(square(x)) was silently
dropped, so penalized least-squares (ridge) problems were solved with
weight 1 (since 1.8.2). sum_squares(),
quad_form(), and unweighted terms were unaffected.NULL error
instead of the real message.sparsity variable attribute was accepted and then
ignored — the variable solved as fully dense. It is now enforced;
off-pattern entries come back as structural zeros, matching CVXPY.gradient(p) from the derivative API was exactly 0
whenever value(p) == 0, and correct elsewhere. Fixed
jointly with diffcp; requires
diffcp (>= 0.1.2).Variable(pos = TRUE) and neg = TRUE
emitted no sign constraint, so bounded problems were reported unbounded
on most solvers (since 1.8.2).boolean/integer attributes were
silently ignored — the problem solved as its continuous relaxation and
reported "optimal". They are now honored and require a
mixed-integer solver.symmetric + diag, PSD +
NSD, …) are rejected, as in CVXPY.project() and value assignment disagreed with the
solver path on partial boolean/integer
indices: one index spelling rejected valid values, another silently
accepted invalid ones.quad_form() with a negative semidefinite
matrix passed is_dcp() and then failed to compile; it now
solves. An indefinite matrix is no longer mislabeled PSD.qcp = TRUE) problems treated a matrix
product A %*% x as an elementwise one; it is now rejected
up front with CVXPY’s message. Elementwise and scalar products are
unaffected.vdot() and scalar_product() omitted the
conjugation for complex arguments. Matching upstream,
vdot() of a non-affine real argument is now of unknown
curvature.conv(expr, kernel) — the constant kernel second — read
monotonicity off the wrong argument and accepted a concave composition
as convex.sqrt() and inv_pos() now canonicalize to
second-order cones as in CVXPY, not power cones: ECOS and
mixed-integer SOCP solvers accept them, and an SCS
quasiconvex failure mode (NaN objective) is gone. Use
power(x, 0.5, approx = FALSE) for the power-cone form.CLARABEL/SCS/MOSEK;
PowCone3D duals were transposed across cones in multi-cone
constraints. Objectives and primal solutions were never affected.Parameter never triggered the complex-to-real
reduction, so problems CVXPY solves errored with “Inequality constraints
cannot be complex.”SUBOPTIMAL mapped to
optimal_inaccurate, claiming a near-optimal solution that
need not exist, and the QP path promoted SOLVER_ERROR to
OPTIMAL_INACCURATE. Both now match CVXPY.(solstat, pfeas, dfeas) as upstream
does.floor() could be off by one within
solver tolerance of an integer; log1p() reported the bounds
of log(x); get_bounds() errored on fractional
powers; a hermitian leaf could not hold a complex value;
assigning NaN to a leaf raised an R internal error instead
of the real message.dual_value() returns a matrix shaped like its
constraint — n-by-n for a PSD constraint, n-by-1 for a vector constraint
— instead of a flat vector. Scalar constraints, SOC, and
ExpCone are exempt, matching CVXPY.d[3]) still works; code calling
length() or dim() on a dual may need
adjusting.boolean and integer accept four spellings:
whole-variable (TRUE), flat 1-based positions,
cbind(row, col) pairs, and a logical mask of the variable’s
shape.NA indices raise. A partial list
combined with
symmetric/PSD/NSD/diag
is rejected, as in CVXPY; logic atoms (And,
Or, …) accept partially-boolean variables.Minimize()/Maximize() support
+, -, *, / as in
CVXPY; negation or a negative scale flips the sense, and mixing senses
in a sum raises. is_dpp() rejects an unrecognized context
instead of silently answering the "dcp" question.maximize sqrt(x)/exp(x). Answers are unchanged. An explicit
bisection bound of 0 can now grow, and two valid quasiconvex forms
previously rejected now solve.norm(x, 1) is recognized as log-log convex;
partial_trace() and partial_transpose()
propagate PSD/symmetric/hermitian structure; atanh()
reports its domain instead of raising.domain() on a variable or parameter returns the
attribute-implied constraints (previously an empty list).matrix_frac() evaluates an ill-conditioned second
argument and warns, where base R’s solve() refused and
CVXPY (via NumPy) computes silently.Inf is accepted where CVXPY accepts it — constraint
right-hand sides and variable bounds — and the decomposed
solve_via_data() path validates problem data exactly like
psolve().options(CVXR.default_to_commercial_solvers = FALSE) or the
CVXR_DEFAULT_TO_COMMERCIAL_SOLVERS environment variable.
Default unchanged; explicit solver = unaffected.acceleration_lookback = 10, interval 10);
the R scs package’s defaults had disabled acceleration.
Numbers may move at the ~1e-5 level; convergence on hard problems
improves.reduced_tol_infeas_abs is set to
5e-12 on clarabel 0.11.2 and earlier (a shim —
0.11.3 fixed the default), restoring the almost-infeasible certificates
that quasiconvex bisection needs. Pass the option explicitly to
override.accept_unknown is now supported for CLARABEL and MOSEK,
matching CVXPY: a stalled solve that still produced primal and dual
iterates is reported as optimal_inaccurate. Deliberately
opt-in; other statuses are unaffected.dual_value(), as CLARABEL/OSQP/MOSEK already did. When
HiGHS produces no ray (ipm/pdlp,
bound-infeasibility), duals stay NULL rather than a zero
vector that would read as a certificate. An unmapped HiGHS status is
reported as a solver error.square(x) builds
power(x, 2) as in CVXPY, so a squared term no longer reads
as a power-cone requirement excluding mixed-integer SOCP solvers.feastol/reltol/abstol/num_iter)
with no mapping for the chosen solver now warns instead of being
silently ignored.problem$dparam/$iparam, so no parameter ever
arrived and passing one crashed with an unrelated R error. All three
carriers work: Rmosek-native
dparam/iparam/sparam lists,
MSK_*-prefixed names, and CVXPY’s
mosek_params; unknown options raise, as in CVXPY.
feastol, reltol, and num_iter
translate to MOSEK’s native tolerances; HiGHS gains feastol
(primal_feasibility_tolerance).eps keyword works as in CVXPY: for SCS it sets
eps_abs and eps_rel (it was silently ignored);
for MOSEK it fans out to all 17 tolerance parameters, explicitly-set
tolerances taking precedence.geo_mean(), power(), and
p_norm() with fractional exponents are 15-37% faster end to
end.PSD -> SvecPSD reduction following CVXPY 1.9, replacing
per-solver copies of that logic (the source of the transposed-dual bug
above). No user-facing API change.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.psolve(prob, solver = "HIGHS", warm_start = TRUE) reuses
the previous solution via the persistent-solver API
(hi_solver_set_solution) across LP, MILP, and QP paths.
This requires highs (>= 1.14), now available on CRAN; on
older highs the warm-start tests skip and solves fall back
to cold starts.validate_column_name() checks a name against the HiGHS
LP-file column-name rules, mirroring CVXPY’s
highs_conif.validate_column_name.highs package
exposes no column-name setter, so a written model would carry generic
names (c0, c1, …).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() and
norm() dispatch fixesdiag() 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() and
use_quad_objsolver_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)New 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, ...)]This makes it natural to constrain specific entries of a matrix variable, e.g., fixing observed entries of a partially-known matrix:
ind <- which(!is.na(Rmiss), arr.ind = TRUE)
prob <- Problem(Minimize(obj), list(X[ind] == Rmiss[ind]))Previously, 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.
Internally uses sparse selection matrix multiplication (reshape → sparse matmul), requiring no C++ changes.
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.