Nothing
#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/reductions/solvers/solver.R
#####
## CVXPY SOURCE: reductions/solvers/solver.py
## Solver -- abstract base class for solver reductions
# -- Solver constants ----------------------------------------------
## CVXPY SOURCE: solver.py lines 99-102
SOLVER_VAR_ID <- "var_id"
SOLVER_DUAL_VAR_ID <- "dual_var_id"
SOLVER_EQ_CONSTR <- "eq_constr"
SOLVER_NEQ_CONSTR <- "other_constr"
# -- expand_cones --------------------------------------------------
## CVXPY SOURCE: solver.py lines 26-67
##
## Given the cones a problem needs and the cones a solver natively supports,
## replace each unsupported-but-convertible cone with what it converts to, and
## report which conversions the chain must therefore run.
##
## `cones` and `supported` are lists of S7 class objects (CVXR stores
## `@SUPPORTED_CONSTRAINTS` that way); CVXPY uses sets of classes, so the set
## operations are spelled out with `.cone_has`.
##
## Membership is decided by the class NAME, never by `identical()` on the class
## objects. Python set membership on a type is one hash lookup; `identical()`
## on an S7 class object walks the constructor closure and its parent chain
## (9-25 us per pair), and `expand_cones` runs several times per conic solve.
## When `EXACT_CONE_CONVERSIONS` grew its second entry (1.9.2.9115) that cost
## became a 7-25% wall-clock regression on small conic problems
## (`notes/benchmark_results.md`, "v1.9.2.9142"). Every cone class is defined
## in package CVXR, so the name identifies the class.
##
## PARTIAL: CVXR has no APPROX_CONE_CONVERSIONS table -- the SOC approximations
## are chosen inside the dcp2cone canonicalizers (power/geo_mean/pnorm, driven
## by `solver_context`), not by a cone2cone reduction. So `approx_targets` is
## always empty here, and the approximate half of CVXPY's function has no
## counterpart to port yet. See reductions/cone2cone/approx.R.
.cone_name <- function(cone) attr(cone, "name", exact = TRUE)
.cone_has <- function(cone_list, cone) {
nm <- .cone_name(cone)
for (c in cone_list) if (.cone_name(c) == nm) return(TRUE)
FALSE
}
expand_cones <- function(cones, supported) {
## Which unsupported cones can transitively reach a supported cone via the
## EXACT_CONE_CONVERSIONS DAG? (solver.py:41-50)
reachable <- list()
repeat {
changed <- FALSE
for (entry in EXACT_CONE_CONVERSIONS) {
if (.cone_has(reachable, entry$source)) next
hits <- any(vapply(entry$targets, function(t) {
.cone_has(supported, t) || .cone_has(reachable, t)
}, logical(1L)))
if (hits) {
reachable[[length(reachable) + 1L]] <- entry$source
changed <- TRUE
}
}
if (!changed) break
}
## Expand, following the DAG until nothing new is convertible.
## (solver.py:52-60)
exact_targets <- list()
repeat {
new_targets <- Filter(function(c) {
.cone_has(reachable, c) && !.cone_has(supported, c)
}, cones)
if (length(new_targets) == 0L) break
for (co in new_targets) {
co_name <- .cone_name(co)
exact_targets[[length(exact_targets) + 1L]] <- co
cones <- Filter(function(c) .cone_name(c) != co_name, cones)
entry <- Filter(function(e) .cone_name(e$source) == co_name,
EXACT_CONE_CONVERSIONS)[[1L]]
for (t in entry$targets) {
if (!.cone_has(cones, t)) cones[[length(cones) + 1L]] <- t
}
}
}
list(cones = cones, exact_targets = exact_targets,
approx_targets = list())
}
# -- Solver base class --------------------------------------------
## CVXPY SOURCE: solver.py lines 70-179
## PSD constraint format (CVXPY SOURCE: solver.py lines 87-92). Overridden by
## solvers that support PSD constraints in svec form. PSD_TRIANGLE_KIND is the
## triangle the solver expects (`TriangleKind$LOWER` / `$UPPER`); NA means no
## PSD support via this mechanism (CVXPY's `None`), which includes solvers such
## as CVXOPT that take full PSD matrices. PSD_SQRT2_SCALING says whether the
## off-diagonal entries are scaled by sqrt(2).
##
## Constraint 17 ripple: `.fast_new` does not evaluate property defaults, so
## EVERY solver constructor passes both explicitly -- an unset property reads
## back as NULL, not as the class default.
Solver <- new_class("Solver", parent = Reduction, package = "CVXR",
properties = list(
MIP_CAPABLE = new_property(class_logical, default = FALSE),
BOUNDED_VARIABLES = new_property(class_logical, default = FALSE),
PSD_TRIANGLE_KIND = new_property(class_character, default = NA_character_),
PSD_SQRT2_SCALING = new_property(class_logical, default = NA)
),
constructor = function(MIP_CAPABLE = FALSE, BOUNDED_VARIABLES = FALSE,
PSD_TRIANGLE_KIND = NA_character_,
PSD_SQRT2_SCALING = NA) {
if (FALSE) new_object(S7_object()) ## S7 static-check guard
.fast_new(Solver, S7_object(),
.cache = new.env(parent = emptyenv()),
MIP_CAPABLE = MIP_CAPABLE,
BOUNDED_VARIABLES = BOUNDED_VARIABLES,
PSD_TRIANGLE_KIND = PSD_TRIANGLE_KIND,
PSD_SQRT2_SCALING = PSD_SQRT2_SCALING
)
}
)
## name: subclasses must override
method(solver_name, Solver) <- function(x) {
cli_abort("Class {.cls {class(x)[[1L]]}} must implement {.fn solver_name}.")
}
## import_solver / is_installed (solver.py:110-128): DELIBERATELY NOT PORTED.
##
## CVXPY declares `import_solver()` abstract and has each interface implement it as a
## bare `import <pkg>`; `is_installed()` wraps that in try/except. In Python the probe
## IS the mechanism -- there is no registry to consult, so an exception-driven import is
## the only idiom available. R has `requireNamespace()`, which RETURNS a logical, so
## wrapping it in abort-and-catch to recover that same logical would be a Python idiom
## transplanted into a language that does not need it.
##
## CVXR answers "is this solver installed" through a name->package table instead:
##
## .SOLVER_PACKAGES name -> package solving_chain.R:53
## .solver_package_usable memoised requireNamespace solving_chain.R:84
## .solver_package_available the above, minus exclude_solvers()
## .require_solver_package abort form, called by every solve_via_data
## installed_solvers() the exported user-facing answer exports.R:119
##
## Better, not merely different: (a) it resolves each package ONCE per session, where a
## per-class hook called during chain construction would re-probe -- CVXPY itself
## resolves once per process (`INSTALLED_SOLVERS = installed_solvers()`, defines.py:129),
## so the table is closer to upstream's behavior, not further from it; (b) it keeps the
## Rmosek-stub rule (usable only at version >= 10; CRAN's Rmosek is an ancient stub, a
## failure mode Python has no equivalent of) in ONE place that a per-class
## `requireNamespace` would drop or bury.
##
## Until 1.9.1.9044 this file defined `solver_import()` (aborting unconditionally) and
## `solver_is_installed()` (tryCatch around it). No subclass ever implemented the former,
## so the latter answered FALSE for every solver while installed_solvers() listed fifteen.
## Both had ZERO callers and neither was exported. Deleted rather than implemented --
## dead code mimicking an upstream API is worse than no code, because it misleads its
## first caller. Audit finding #4; verdict `deliberate` in
## notes/audit/completeness_known.tsv. Do not re-add.
##
## One caveat recorded separately: MOSEK.import_solver is the only upstream override that
## does more than import -- it appends ExpCone/PowCone3D to SUPPORTED_CONSTRAINTS behind
## a `hasattr(mosek.conetype, 'pexp')` guard. CVXR declares those cones unconditionally
## (mosek_conif.R), which is correct given the Rmosek >= 10 floor. See ledger ยง4a.
## solve_via_data: subclasses must override
method(solve_via_data, Solver) <- function(x, data, warm_start = FALSE, verbose = FALSE,
solver_opts = list(), ...) {
cli_abort("Class {.cls {class(x)[[1L]]}} must implement {.fn solve_via_data}.")
}
## CVXPY SOURCE: solver.py:142-144 -- no quadratic objective unless a subclass
## says so (QpSolver, Clarabel, SCS).
method(supports_quad_obj, Solver) <- function(x) FALSE
# -- MI_SUPPORTED_CONSTRAINTS --------------------------------------
## CVXPY SOURCE: solver.py:158-160, solving_chain.py:163-166 --
## `getattr(solver, 'MI_SUPPORTED_CONSTRAINTS', solver.SUPPORTED_CONSTRAINTS)`:
## the constraints a solver accepts on a mixed-integer problem, which default
## to SUPPORTED_CONSTRAINTS. A generic with that default rather than a property,
## so the one solver whose list is narrower (MOSEK: no MISDP) overrides it and
## no other constructor changes.
#' @noRd
mi_supported_constraints <- new_generic("mi_supported_constraints", "x")
method(mi_supported_constraints, Solver) <- function(x) x@SUPPORTED_CONSTRAINTS
## CVXPY's EXACT_CONE_CONVERSIONS has NonPos -> {NonNeg} (exact.py:62); CVXR
## rewrites NonPos inside matrix stuffing (`nonpos2nonneg`,
## reductions/utilities.R) instead of in ExactCone2Cone, so the conversion is
## applied here, before `expand_cones`, for the capability question only.
.nonpos_as_nonneg <- function(cones) {
if (!.cone_has(cones, NonPos)) return(cones)
cones <- Filter(function(c) .cone_name(c) != "NonPos", cones)
if (!.cone_has(cones, NonNeg)) cones[[length(cones) + 1L]] <- NonNeg
cones
}
# -- can_solve -----------------------------------------------------
## CVXPY SOURCE: solver.py:146-171
## Can this solver handle a problem with the given structure? Asked of a
## ProblemForm (problems/problem_form.R), before canonicalization.
##
## RECORDED DIVERGENCE (user, 2026-10-14): `use_quad_obj` is R-only. Upstream's
## quad_obj here (solver.py:164) ignores the option, while the chain built
## afterwards honors it (solving_chain.py:215-221), so with use_quad_obj=False
## CVXPY 1.9.3 selects a QP solver for a QP and then fails on the objective's
## SOC ("This may indicate a bug in solver selection"; measured,
## notes/upstream_report_cvxpy_use_quad_obj.md). Asking the same question the
## chain will ask keeps the QP interface out of such a selection, as CVXR's
## 9211 selection did. The default, TRUE, is upstream's behavior.
#' @noRd
can_solve <- new_generic("can_solve", "x",
function(x, problem_form, use_quad_obj = TRUE) S7_dispatch()
)
method(can_solve, Solver) <- function(x, problem_form, use_quad_obj = TRUE) {
mixed_integer <- .pf_is_mixed_integer(problem_form)
if (mixed_integer && !isTRUE(x@MIP_CAPABLE)) return(FALSE)
supported <- if (mixed_integer) mi_supported_constraints(x) else
x@SUPPORTED_CONSTRAINTS
quad_obj <- isTRUE(use_quad_obj) && supports_quad_obj(x) &&
.pf_has_quadratic_objective(problem_form)
cones <- .nonpos_as_nonneg(.pf_cones(problem_form, quad_obj = quad_obj))
cones <- expand_cones(cones, supported)$cones
if (!.pf_has_constraints(problem_form) && isTRUE(x@REQUIRES_CONSTR)) {
return(FALSE)
}
all(vapply(cones, function(c) .cone_has(supported, c), logical(1L)))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.