                 Changes in version 1.9.2 (2026-08-24)                  

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.

Incorrect results fixed

  - A constant weight on 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.
  - A failed solve poisoned the compiled-chain cache: repeating the
    identical call could silently return an answer computed with the
    previous options, or die with an unrelated NULL error instead of the
    real message.
  - The 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).
  - DQCP problems with nested inverses could return a confidently wrong
    "optimal" answer: the bisection parameter was canonicalized away, so
    every query point looked feasible.
  - Variable(pos = TRUE) and neg = TRUE emitted no sign constraint, so
    bounded problems were reported unbounded on most solvers
    (since 1.8.2).
  - Partial 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.
  - Numeric bounds of the wrong shape on a symmetric leaf were silently
    truncated (HiGHS returned a feasible but suboptimal answer). Bounds
    are now shape-checked, and conflicting dimension-reducing attributes
    (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.
  - A complex 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.
  - Quasiconvex (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.
  - Duals of a PSD constraint on a Hermitian matrix were half their
    correct value; PSD duals were transposed for sizes 3 and up on
    CLARABEL/SCS/MOSEK; PowCone3D duals were transposed across cones in
    multi-cone constraints. Objectives and primal solutions were never
    affected.
  - A complex Parameter never triggered the complex-to-real reduction,
    so problems CVXPY solves errored with "Inequality constraints cannot
    be complex."
  - Gurobi: 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.
  - CPLEX: the status table's labels — and the mappings that followed
    them — were shifted by one across the abort-status block. Statuses
    are now derived from (solstat, pfeas, dfeas) as upstream does.
  - Smaller fixes: 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 variables have their constraint's shape

  - 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.
  - Linear indexing (d[3]) still works; code calling length() or dim()
    on a dual may need adjusting.

Mixed-integer variable attributes

  - 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.
  - Index lists are validated at construction — out-of-range,
    fractional, duplicated, or 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.

Other improvements

  - Common-subexpression elimination: a subtree appearing more than once
    is canonicalized once. Transparent — solutions, objective values,
    and duals are unchanged, and only structurally identical subtrees
    are merged.
  - 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.
  - DQCP bisection compiles its one-parameter subproblem family once
    when the reduction emits no lazy constraints, instead of
    recanonicalizing at every query — about 14x on 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().
  - Solver selection can skip commercial solvers:
    options(CVXR.default_to_commercial_solvers = FALSE) or the
    CVXR_DEFAULT_TO_COMMERCIAL_SOLVERS environment variable. Default
    unchanged; explicit solver = unaffected.

Solver behavior

  - SCS: acceleration defaults now match SCS's own C defaults, which is
    what CVXPY gets (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.
  - CLARABEL: 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.
  - HiGHS: an infeasible problem returns a Farkas dual ray through
    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.
  - The cone scan that picks a solver looks inside constraints, not just
    the objective; and 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.
  - A standard parameter (feastol/reltol/abstol/num_iter) with no
    mapping for the chosen solver now warns instead of being silently
    ignored.
  - MOSEK solver parameters actually reach MOSEK now. They were merged
    into Rmosek's interface options instead of 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).
  - The 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.

Performance

  - About a third faster than 1.9.1 on a benchmark suite, every problem
    faster and allocating less — from 8% on the smallest problems to
    ~60% at 4000 constraints. Dual recovery is now linear in the number
    of constraints (was quadratic), cutting memory up to ~60% on
    constraint-heavy problems.
  - geo_mean(), power(), and p_norm() with fractional exponents are
    15-37% faster end to end.

Notes

  - PSD constraints are packed to triangular form in one 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.

                 Changes in version 1.9.1 (2026-06-09)                  

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.

Disciplined nonlinear programming (DNLP)

  - Solve smooth nonlinear programs with 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.
  - New smooth atoms usable in DNLPs: 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.
  - The classically-differentiable atoms now report 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().
  - Piecewise-linear atoms used in a DNLP (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.
  - New 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().
  - The DNLP path is powered by derivatives from the optional 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.
  - The 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.
  - Best-of-N random restarts for nonconvex DNLPs: 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.
  - After an NLP solve, dual_value() returns the constraint duals
    recovered from the solver (a CVXR addition; the duals are not
    exposed by CVXPY's NLP interface).

Derivative API for differentiable convex programs

  - New derivative API backed by the 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).

Bounds propagation

  - 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.
  - Variable bounds may now be sparse 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.
  - Solvers that expose native variable bounds now consume dense numeric
    bounds directly instead of adding bound constraints: HiGHS (LP/MILP
    and QP paths), Gurobi (QP/MIQP and conic/SOCP), CPLEX (QP/MIQP),
    XPRESS (QP/MIQP and conic/SOCP), PIQP (QP), and SCIP (conic). Sparse
    bounds continue through constraint lowering.
  - Parametric variable bounds use bound tensors (HiGHS), so changing
    Parameter bounds updates native solver bounds on DPP re-solves.
  - Bounds inferred for DCP auxiliary variables are now preserved for
    solvers that consume native variable bounds.
  - Attribute reduction now compacts 2D symmetric, PSD, NSD, and
    diagonal Parameters before rebuilding their full expressions.
  - Variables now report Parameters embedded in expression bounds and
    include those bounds in DPP/DGP compliance checks.

HiGHS warm-start and column-name validation

  - HiGHS warm-start is now enabled. 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.
  - Known limitation: writing a model to a file with the original
    variable names is not yet supported — the R highs package exposes no
    column-name setter, so a written model would carry generic names
    (c0, c1, ...).

Geometric and parameterized programming

  - Positive (DGP) variables now accept numeric and parametric bounds
    under 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).
  - New partial_optimize() transform.
  - CPLEX now solves LP/SOCP/MI-LP/MI-SOCP problems through the conic
    path by translating SOC blocks to Rcplex quadratic constraints.

New atoms, transforms, and accessors

  - New sign() atom (DQCP, scalar input).
  - psolve() gains a solver_path argument for solver fallback chains.
  - New 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).
  - MOSEK now supports mixed-integer programs (requires Rmosek 11.1.1+).
  - solver_stats() for MOSEK now populates solve_time, num_iters, and
    extra_stats.
  - New param_dict(), var_dict(), and size_metrics() accessors on
    Problem.
  - Variable(value = ...) accepts an initial value at construction.
  - New 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.

Performance

  - Problem canonicalization and solving are faster than the 1.8.2 CRAN
    release on a wide range of problems. Two changes account for most of
    the gain: a new .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.

Bug fixes

  - Bug fix (also affected 1.8.x): 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.
  - Bug fix (also affected 1.8.x): 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.
  - Fixed QP-path canonicalization of 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.
  - Fix Variable(c(n, n), diag = TRUE) handling through CvxAttr2Constr.
  - Fix perspective() canonicalizer on PSD, NSD, and diag matrix
    variables.
  - Fix project() on sparse Matrix-package objects.

Notes

  - Complex-parameter DPP fast-path parity remains a deliberate feature
    gap: complex parameters are evaluated before Complex2Real because
    CVXR's R sparse-matrix backend cannot carry complex sparse parameter
    tensors. Ordinary complex expression solving is still supported
    through Complex2Real.
  - CVXR's 2-D positive-dimensional model does not support zero-sized
    expressions, constraints, or solves. Generated or data-dependent
    models should skip vacuous constraints and use explicit zero
    contributions when a mask or filter selects no entries.

                 Changes in version 1.8.2 (2026-04-04)                  

New solvers: SCIP and XPRESS (15 total)

  - Added SCIP solver (14th solver) via the R 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 solver parameters can be passed via scip_params sub-list
    (matching CVXPY convention) for path-style parameter names like
    "limits/time", "limits/gap".
  - Duals are not currently extracted (R scip package lacks dual API).
  - 29 tests mirroring CVXPY's TestSCIP class.
  - Added XPRESS solver (15th solver) via the R 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 fixes

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

CVXPY 1.8.2 parity

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_obj

  - New 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.
  - New use_quad_obj option (default TRUE): when FALSE, forces conic
    decomposition path instead of QP, enabling quad_form_canon to detect
    indefinite P matrices.
  - New supports_quad_obj() generic on conic solvers (Clarabel and SCS
    return TRUE, others FALSE).

Element-wise matrix indexing (SpecialIndex)

  - New SpecialIndex atom (mirroring CVXPY's special_index) enables
    R-idiomatic element-wise selection on matrix expressions:
      - 2-column matrix indexing: x[cbind(rows, cols)]
      - Logical matrix indexing: x[mask]
      - Linear integer indexing: x[c(1, 5, 9)]
      - Logical vector indexing: 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.

Bug Fixes

  - Fixed O(n²) memory usage in 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.

                 Changes in version 1.8.1 (2026-03-06)                  

Complete rewrite using S7 object system

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.

New features

  - S7 class system replaces S4 for all expression, constraint, and
    problem classes. Significantly faster construction and method
    dispatch.
  - 13 solvers at initial release: Clarabel (default), SCS, OSQP, HiGHS,
    MOSEK, Gurobi, GLPK, GLPK_MI, ECOS, ECOS_BB, CPLEX, CVXOPT, PIQP.
  - Mixed-integer programming via GLPK_MI, ECOS_BB, Gurobi, or HiGHS
    (boolean = TRUE or integer = TRUE in Variable()).
  - Parameter support via Parameter() class with DPP (Disciplined
    Parameterized Programming) for efficient re-solves when only
    parameter values change.
  - 50+ atom classes covering LP, QP, SOCP, SDP, exponential cone, and
    power cone problems.
  - 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.
  - Standard solver parameters (feastol, reltol, abstol, num_iter) in
    psolve() with automatic translation to solver-native names via
    solver_default_param(). Solver-native parameters in ... take
    priority.
  - Automatic solver selection based on problem type.
  - Warm-start support for 7 solvers (OSQP, SCS, Clarabel, Gurobi,
    MOSEK, PIQP; HiGHS blocked by R package limitation).
  - Decomposed solve API: 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.
  - Matrix package interoperability via 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.
  - Full 2D broadcasting for + 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.
  - Correct solver-cone routing matching CVXPY's atom classifications.
    Atoms that canonicalize to PSD cones (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).

Breaking changes from CVXR 1.x

  - S7 classes replace S4. Use Variable(n) instead of new("Variable",
    n).
  - Requires R >= 4.3.0 (for 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.
  - The 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.
  - Internal class structure completely changed (S7 instead of S4). Code
    that accessed slots directly will need updating.
  - S4 class checks like is(x, "Variable") no longer work. Use
    S7_inherits(x, Variable).

Convenience atoms and functions

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

Clean API names

  - 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()).
  - Old names still work but emit once-per-session deprecation warnings.

Backward-compatibility aliases

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

Advanced features

  - DPP (Disciplined Parameterized Programming) for efficient parameter
    re-solve with compilation caching.
  - DGP (Disciplined Geometric Programming) via psolve(prob, gp = TRUE).
    New atoms: prod_entries(), cumprod(), one_minus_pos(),
    eye_minus_inv(), pf_eigenvalue(), gmatmul().
  - DQCP (Disciplined Quasiconvex Programming) via psolve(prob, qcp =
    TRUE). New atoms: ceil_expr(), floor_expr(), condition_number(),
    gen_lambda_max(), dist_ratio().
  - Complex variable support via 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.
  - Boolean logic atoms: 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.

Bug fixes

  - Fixed kron() KRON_L coefficient matrix construction.
  - Fixed CvxAttr2Constr index type and matrix variable handling.
  - Fixed Clarabel PowConeND bridge for uniquely-named cones.
  - Fixed C++ diag offset for super/sub-diagonals (k != 0).
  - Fixed DQCP bisection: infinity check, stale bounds for Maximize,
    conditional high-endpoint seeding.
  - Fixed MOSEK dual extraction (suc - slc reconstruction).
  - Fixed OSQP/HiGHS inequality dual sign convention.

Known limitations

  - Matrix package objects (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.
  - Warm-start for HiGHS is implemented (works with 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.
  - Complex SOC dual variables are not recovered (CVXPY itself does not
    implement this — complex2real.py:56 declares
    UNIMPLEMENTED_COMPLEX_DUALS = (SOC, OpRelEntrConeQuad)). Complex SOC
    primal canonicalization works.
  - Deferred to a future release: RelEntrConeQuad / OpRelEntrConeQuad,
    quantum atoms, CPLEX conic (SOC) path.

                 Changes in version 1.0-15 (2024-11-07)                 

  - Revert clarabel requirement and use enhance rather than import until
    really necessary (Issue 142).
  - Update return codes for user_limit etc to be infeasible_inaccurate
    to match CVXPY for gurobi
  - Add an S3 print method for result from solve().
  - Move upper_tri_to_full to C++
  - Drop use of R6 classes
  - Address bug in copying Power object (Issue 145).

                 Changes in version 1.0-14 (2024-06-27)                 

  - Address further inefficiency in use of diagonal matrices in
    qp2quad_form.R
  - Add initial interface to clarabel solver

                 Changes in version 1.0-13 (2024-06-01)                 

  - Address inefficient processing of cones for MOSEK (Issue 137
    reported by aszekMosek)
  - Fix extract_quadratic_coeffs to use sparse matrix and sweep in place
    for better memory use (reported by Marissa Reitsma)

                 Changes in version 1.0-12 (2024-02-01)                 

  - Rmosek to be removed from CRAN, so moved to drat repo
  - Cleaned up some problematic Rd files shown in CRAN results

                 Changes in version 1.0-11 (2022-10-30)                 

  - Being more careful about coercing to dgCMatrix via (as(as(<matrix>,
    "CsparseMatrix"), "generalMatrix"))
  - Modify all class inheritance checks to use inherits()

                 Changes in version 1.0-10 (2021-11-10)                 

  - Now requiring the updated scs 3.0 as an import

                 Changes in version 1.0-9 (2021-01-19)                  

  - Now importing ECOSolveR version 0.5.4 and higher
  - Added fixes for Matrix 1.3 update
  - Somewhat better typesetting for Power class documentation (Issue
    #86)

                        Changes in version 1.0-3                        

  - Conforming to CRAN suggestions on non-CRAN packages, but no actual
    changes.

                        Changes in version 1.0-2                        

  - Added exponential cone support for MOSEK and uncommented associated
    test.
  - Added JSS publication DOI.

                 Changes in version 1.0-1 (2020-04-02)                  

  - Many small fixes to solver interfaces
  - Reference semantics for Parameter
  - Warm start for OSQP and updates to solver cache
  - Solver parameter defaults explicit now
  - New tests added for solver combinations

                  Changes in version 1.0 (2020-02-02)                   

  - Major release implementing reductions, many new solvers.

                  Changes in version 0.99 (2018-05-26)                  

  - Bug fix: duplicated integer and boolean indices.
  - Bug fix: correct typo in constraint specification to GLPK.
  - Added tutorial articles based on v0.99 to CVXR website on using
    other solvers, integer programming, MOSEK and GUROBI examples.

                       Changes in version 0.98-1                        

  - Minor typographical fixes.

                        Changes in version 0.98                         

  - Dropped delay_load parameter dropped in
    reticulate::import_from_path, per changes in reticulate.

  - Cleaned up hooks into reticulate for commercial solvers.

                       Changes in version 0.97-1                        

  - Minor typo and documentation fixes.

                        Changes in version 0.97                         

  - Added LPSOLVE via lpSolveAPI
  - Added GLPK via Rglpk
  - Added MOSEK
  - Added GUROBI
  - Bug fix: issue #25. All CVXR expressions retain dimensions. Culprit
    was drop = FALSE (in function Index.get_special_slice) as suspected.

                        Changes in version 0.96                         

  - Added a note that CVXR can probably be compiled from source for
    earlier versions of R. This is issue #24

  - Using pkgdown. This also addresses issue #23

  - Bug fix: issue #28 Function intf_sign (interface.R) was
    unnecessarily using a tolerance parameter, now eliminated.

                  Changes in version 0.95 (2018-02-20)                  

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

                 Changes in version 0.94-4 (2017-11-20)                 

  - First CRAN release 2017-11-20.
