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() 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.