Nothing
#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/reductions/solvers/conic_solvers/mosek_conif.R
#####
## CVXPY SOURCE: reductions/solvers/conic_solvers/mosek_conif.py
## MOSEK solver interface via Rmosek
##
## MOSEK is a commercial conic solver with native support for LP, QP,
## SOCP, ExpCone, PowCone, and SDP. Uses the standard ConicSolver pipeline
## (ConeMatrixStuffing -> format_constraints -> ConicSolver.reduction_apply).
##
## Key design:
## - Zero constraints -> prob$A + prob$bc (equality bounds, blc = buc = b)
## - NonNeg constraints -> prob$A + prob$bc (inequality bounds, blc = -Inf, buc = b)
## - Conic cones (SOC, PSD, ExpCone, PowCone3D) -> ACC (prob$F + prob$g + prob$cones)
## - PSD via SVEC_PSD_CONE in ACC (not bar variables)
## - SCS's lower-tri column-major with sqrt2 scaling matches MOSEK's SVEC_PSD_CONE
## - ExpCone order: MOSEK PEXP(x,y,z) where x >= y*exp(z/y)
## vs CVXR (x,y,z) where z >= y*exp(x/y), so EXP_CONE_ORDER = c(2,1,0)
## - QP via prob$qobj lower-tri triplets
## - Rmosek SVEC_PSD_CONE bug workaround: dummy bardim + barc when PSD present
# -- MOSEK status map ----------------------------------------------
## Maps MOSEK solution status strings to CVXR status constants.
MOSEK_STATUS_MAP <- list(
"OPTIMAL" = OPTIMAL,
"INTEGER_OPTIMAL" = OPTIMAL, # MIP: optimal integer solution
"PRIMAL_INFEASIBLE_CER" = INFEASIBLE,
"DUAL_INFEASIBLE_CER" = UNBOUNDED,
"NEAR_OPTIMAL" = OPTIMAL_INACCURATE,
"PRIMAL_ILLPOSED_CER" = INFEASIBLE,
"DUAL_ILLPOSED_CER" = UNBOUNDED,
"PRIMAL_AND_DUAL_FEASIBLE" = OPTIMAL_INACCURATE,
"PRIMAL_FEASIBLE" = OPTIMAL_INACCURATE, # MIP: feasible (e.g. time-limited)
## Re-pointed to OPTIMAL_INACCURATE, on a COPY of the map, when the user
## passes `accept_unknown = TRUE`. See solve_via_data / reduction_invert.
## CVXPY SOURCE: mosek_conif.py:456-458.
"UNKNOWN" = SOLVER_ERROR
)
## CVXPY SOURCE: mosek_conif.py:457 -- the option key. Named like
## CLARABEL_ACCEPT_UNKNOWN (clarabel_conif.R:64), which uses the same carrier.
MOSEK_ACCEPT_UNKNOWN <- "accept_unknown"
# -- dims_to_mosek_cones: ConeDims -> prob$cones matrix -------------
## Returns a 3-row matrix with one column per cone block.
## Row 1: type string, Row 2: dimension, Row 3: conepar.
## Also returns the total number of ACC rows (for building F and g).
dims_to_mosek_cones <- function(cone_dims) {
cols <- list()
## NOTE: NonNeg is handled via prob$A/bc bounds, NOT ACC cones.
## SOC -> QUAD
if (length(cone_dims@soc) > 0L) {
for (q in cone_dims@soc) {
cols <- c(cols, list(list("QUAD", q, NULL)))
}
}
## PSD -> SVEC_PSD_CONE
if (length(cone_dims@psd) > 0L) {
for (d in cone_dims@psd) {
svec_dim <- (d * (d + 1L)) %/% 2L
cols <- c(cols, list(list("SVEC_PSD_CONE", svec_dim, NULL)))
}
}
## ExpCone -> PEXP (3 elements each)
if (cone_dims@exp > 0L) {
for (i in seq_len(cone_dims@exp)) {
cols <- c(cols, list(list("PEXP", 3L, NULL)))
}
}
## PowCone3D -> PPOW (3 elements each, with alpha parameters)
## MOSEK PPOW expects conepar = c(alpha, 1-alpha) for x1^alpha * x2^(1-alpha) >= |x3|
## CVXR stores single alpha in cone_dims@p3d
if (length(cone_dims@p3d) > 0L) {
for (alpha in cone_dims@p3d) {
cols <- c(cols, list(list("PPOW", 3L, c(alpha, 1 - alpha))))
}
}
if (length(cols) == 0L) return(NULL)
## Build 3-row matrix
cones_mat <- matrix(list(), nrow = 3, ncol = length(cols))
rownames(cones_mat) <- c("type", "dim", "conepar")
for (j in seq_along(cols)) {
cones_mat[1, j] <- cols[[j]][[1L]]
cones_mat[2, j] <- cols[[j]][[2L]]
cones_mat[3, j] <- list(cols[[j]][[3L]])
}
cones_mat
}
# -- Mosek_Solver class --------------------------------------------
#' @keywords internal
Mosek_Solver <- new_class("Mosek_Solver", parent = ConicSolver, package = "CVXR",
constructor = function() {
if (FALSE) new_object(S7_object()) ## S7 static-check guard
.fast_new(Mosek_Solver, S7_object(),
.cache = new.env(parent = emptyenv()),
MIP_CAPABLE = TRUE,
BOUNDED_VARIABLES = FALSE,
## CVXPY SOURCE: mosek_conif.py lines 110-111 -- BUT the scaling flag is a
## DELIBERATE divergence. CVXPY sets PSD_SQRT2_SCALING = False because its
## MOSEK interface feeds the unscaled lower triangle to a bare PSD cone;
## CVXR's MOSEK interface uses MOSEK's SVEC_PSD_CONE, deliberately reusing
## SCS's sqrt(2)-scaled lower-tri format (ADR DM.2), so it must pass TRUE.
## Copying CVXPY's value mechanically here would break every MOSEK SDP:
## the principle is minimize deviation, not transcribe blindly (D_19.6).
PSD_TRIANGLE_KIND = TriangleKind$LOWER,
PSD_SQRT2_SCALING = TRUE,
## CVXPY SOURCE: mosek_conif.py:109 -- SvecPSD: MOSEK's SVEC_PSD_CONE
## takes the packed triangle, which PSDToSvecPSD produces.
SUPPORTED_CONSTRAINTS = list(Zero, NonNeg, SOC, ExpCone,
PowCone3D, SvecPSD),
EXP_CONE_ORDER = c(2L, 1L, 0L),
REQUIRES_CONSTR = FALSE
)
}
)
method(solver_name, Mosek_Solver) <- function(x) MOSEK_SOLVER
# -- MOSEK solve_via_data ------------------------------------------
## Converts CVXR solver data (A, b, c, dims, P) to MOSEK prob list
## and calls Rmosek::mosek().
method(solve_via_data, Mosek_Solver) <- function(x, data, warm_start = FALSE, verbose = FALSE,
solver_opts = list(), ...) {
.require_solver_package(MOSEK_SOLVER)
dots <- list(...)
solver_cache <- dots[["solver_cache"]]
A_full <- data[[SD_A]]
b_full <- data[[SD_B]]
c_vec <- data[[SD_C]]
dims <- data[[SD_DIMS]]
n <- length(c_vec) # number of variables
## -- MIP plumbing (CVXPY SOURCE: mosek_conif.py _build_slack_task) ----
## CVXR's conic reduction stashes bool_idx/int_idx (1-based) on data.
## MOSEK does NOT support mixed-integer SDP (CVXPY documents this at
## mosek_conif.py:393-394); reject the combination upfront.
bool_idx <- data[["bool_idx"]] %||% integer(0)
int_idx <- data[["int_idx"]] %||% integer(0)
is_mip <- length(bool_idx) > 0L || length(int_idx) > 0L
if (is_mip && length(dims@psd) > 0L) {
cli::cli_abort(c(
"MOSEK does not support mixed-integer semidefinite programs.",
i = "Drop the integer/boolean variables, or use a different solver for the MISDP."
))
}
## -- Build MOSEK prob list --------------------------------------
prob <- list()
prob$sense <- "min"
prob$c <- c_vec
prob$bx <- rbind(blx = rep(-Inf, n), bux = rep(Inf, n))
## Boolean variables: bound to [0, 1]. Combined with intsub (below)
## this is exactly MOSEK's "binary" treatment.
if (length(bool_idx) > 0L) {
prob$bx[1L, bool_idx] <- 0
prob$bx[2L, bool_idx] <- 1
}
## Integer subindex (1-based). Union of boolean and integer variables:
## both become MSK_VAR_TYPE_INT inside MOSEK; booleans are additionally
## constrained to [0,1] via bx above.
if (is_mip) {
prob$intsub <- as.integer(sort(unique(c(bool_idx, int_idx))))
}
## -- Row splitting ----------------------------------------------
## Solver convention: data$A * x + s = data$b, s in K
##
## MOSEK row assignment:
## Zero rows -> prob$A/bc with blc = buc = b (equality)
## NonNeg rows -> prob$A/bc with blc = b, buc = Inf (inequality)
## Conic rows -> ACC: F*x + g in K where F = -A, g = b
##
## NonNeg goes to prob$A/bc (not ACC) because MOSEK does not allow
## mixing quadratic objectives (qobj) with conic ACC constraints.
zero_dim <- dims@zero
nonneg_dim <- dims@nonneg
linear_dim <- zero_dim + nonneg_dim
## Linear portion (Zero + NonNeg) -> prob$A / prob$bc
if (linear_dim > 0L) {
A_linear <- A_full[seq_len(linear_dim), , drop = FALSE]
b_linear <- b_full[seq_len(linear_dim)]
prob$A <- A_linear
## Bounds: Zero rows get equality, NonNeg rows get one-sided
## Zero: A*x + s = b, s = 0 -> A*x = b -> blc = buc = b
## NonNeg: A*x + s = b, s >= 0 -> A*x <= b -> blc = -Inf, buc = b
blc <- rep(-Inf, linear_dim)
buc <- b_linear
if (zero_dim > 0L) {
blc[seq_len(zero_dim)] <- b_linear[seq_len(zero_dim)]
}
prob$bc <- rbind(blc = blc, buc = buc)
} else {
prob$A <- Matrix::sparseMatrix(i = integer(0), j = integer(0),
x = numeric(0), dims = c(0L, n))
prob$bc <- rbind(blc = numeric(0), buc = numeric(0))
}
## -- ACC: conic rows (SOC, PSD, ExpCone, PowCone3D) ------------
## ACC: F*x + g in K where s = b - A*x in K -> F = -A, g = b
total_rows <- nrow(A_full)
conic_rows <- if (linear_dim < total_rows) (linear_dim + 1L):total_rows else integer(0)
if (length(conic_rows) > 0L) {
prob$F <- -A_full[conic_rows, , drop = FALSE]
prob$g <- b_full[conic_rows]
## Build cone specifications (excluding NonNeg which is in prob$A/bc)
cones_mat <- dims_to_mosek_cones(dims)
if (!is.null(cones_mat)) {
prob$cones <- cones_mat
}
}
## -- SVEC_PSD_CONE bug workaround ------------------------------
## Rmosek 11.1.1 BETA crashes if SVEC_PSD_CONE is used but prob$barc
## is not defined. Add dummy 1x1 bar variable with zero objective.
if (length(dims@psd) > 0L) {
prob$bardim <- c(1L)
prob$barc <- list(j = 1L, k = 1L, l = 1L, v = 0)
}
## -- QP: quadratic objective via prob$qobj ----------------------
if (!is.null(data[[SD_P]])) {
P <- data[[SD_P]]
## Extract lower-triangle triplets (MOSEK wants i >= j, 1-based)
P_tril <- Matrix::tril(P)
P_tril <- methods::as(P_tril, "TsparseMatrix")
if (length(P_tril@x) > 0L) {
prob$qobj <- list(
i = P_tril@i + 1L, # 1-based
j = P_tril@j + 1L, # 1-based
v = P_tril@x
)
}
}
## -- Solver options ---------------------------------------------
## `getinfo = TRUE` makes Rmosek return MOSEK info-items as
## `result$dinfo` / `result$iinfo` / `result$liinfo`. Consumed by
## reduction_invert(Mosek_Solver) to populate `solver_stats`'s
## SOLVE_TIME, NUM_ITERS, and EXTRA_STATS fields. Mirrors CVXPY
## mosek_conif.py:531-540 (which queries the same enum items via
## task.getdouinf / task.getintinf / task.getlintinf).
opts <- list(verbose = if (verbose) 1L else 0L, soldetail = 1L,
getinfo = TRUE)
## CVXPY SOURCE: mosek_conif.py:456-458 --
## if solver_opts['accept_unknown']:
## STATUS_MAP[mosek.solsta.unknown] = s.OPTIMAL_INACCURATE
## `accept_unknown` is a CVXPY option, not a MOSEK parameter, so it is taken
## out here rather than forwarded: Rmosek would carry an unrecognized name
## into `opts` and MOSEK would reject or ignore it.
##
## It travels to reduction_invert() on the RESULT, for the reason spelled out
## in clarabel_conif.R:145-152 -- upstream reads it off
## `inverse_data.solver_options` because its Solver.solve() runs apply/solve/
## invert in one call, whereas CVXR's solve API is decomposed and public, so
## invert only ever sees compile-time inverse_data. Same information, same
## precondition, different carrier.
accept_unknown <- isTRUE(solver_opts[[MOSEK_ACCEPT_UNKNOWN]])
solver_opts[[MOSEK_ACCEPT_UNKNOWN]] <- NULL
## Route options to their Rmosek carrier. Rmosek splits them: SOLVER
## parameters live in problem$dparam/$iparam/$sparam (mosek.Rd; both
## "MSK_DPAR_X" and short "X" spellings accepted -- verified
## empirically 2026-08-22), while `opts` holds only the INTERFACE
## options listed below. The previous code merged EVERYTHING into
## `opts`, so no solver parameter could ever reach MOSEK; worse,
## Rmosek then returned a result without $response$code and the
## unguarded comparison in reduction_invert crashed with "missing
## value where TRUE/FALSE needed".
## CVXPY SOURCE: mosek_conif.py:628-676 -- `mosek_params` entries are
## applied by MSK_DPAR_/MSK_IPAR_/MSK_SPAR_ prefix and leftover
## unknown options raise ValueError (:674-676). CVXR mirrors that
## contract on Rmosek's carriers, and additionally accepts Rmosek's
## native dparam/iparam/sparam lists.
## CVXPY SOURCE: mosek_conif.py:690-710 parse_eps_keyword() -- the `eps`
## keyword fans out to every MOSEK tolerance parameter
## (tolerance_params(), mosek_conif.py:713-739, reproduced verbatim
## below); tolerances that were set explicitly -- through ANY of CVXR's
## carriers, including the standard-name translation -- take precedence.
if (!is.null(solver_opts[["eps"]])) {
eps <- solver_opts[["eps"]]
solver_opts[["eps"]] <- NULL
tol_params <- c(
"MSK_DPAR_INTPNT_CO_TOL_DFEAS", "MSK_DPAR_INTPNT_CO_TOL_INFEAS",
"MSK_DPAR_INTPNT_CO_TOL_MU_RED", "MSK_DPAR_INTPNT_CO_TOL_PFEAS",
"MSK_DPAR_INTPNT_CO_TOL_REL_GAP",
"MSK_DPAR_INTPNT_TOL_DFEAS", "MSK_DPAR_INTPNT_TOL_INFEAS",
"MSK_DPAR_INTPNT_TOL_MU_RED", "MSK_DPAR_INTPNT_TOL_PFEAS",
"MSK_DPAR_INTPNT_TOL_REL_GAP",
"MSK_DPAR_BASIS_REL_TOL_S", "MSK_DPAR_BASIS_TOL_S",
"MSK_DPAR_BASIS_TOL_X",
"MSK_DPAR_MIO_TOL_ABS_GAP", "MSK_DPAR_MIO_TOL_ABS_RELAX_INT",
"MSK_DPAR_MIO_TOL_FEAS", "MSK_DPAR_MIO_TOL_REL_GAP")
already_set <- c(names(solver_opts),
names(solver_opts[["mosek_params"]]),
names(solver_opts[["dparam"]]),
paste0("MSK_DPAR_", names(solver_opts[["dparam"]])))
for (tp in tol_params) {
if (!(tp %in% already_set)) solver_opts[[tp]] <- eps
}
}
rmosek_iface_opts <- c("verbose", "usesol", "useparam", "soldetail",
"getinfo", "writebefore", "writeafter")
route_msk <- function(name, value) {
if (grepl("^MSK_DPAR_", name)) prob$dparam[[name]] <<- value
else if (grepl("^MSK_IPAR_", name)) prob$iparam[[name]] <<- value
else if (grepl("^MSK_SPAR_", name)) prob$sparam[[name]] <<- value
else cli_abort(c(
"Invalid MOSEK parameter {.val {name}}.",
"i" = "Parameter names must start with {.code MSK_DPAR_}, {.code MSK_IPAR_}, or {.code MSK_SPAR_}; short names go in the {.arg dparam}/{.arg iparam}/{.arg sparam} lists."
))
}
for (opt_name in names(solver_opts)) {
val <- solver_opts[[opt_name]]
if (opt_name %in% c("dparam", "iparam", "sparam")) {
## Rmosek-native parameter lists, merged into the problem.
prob[[opt_name]] <- utils::modifyList(
if (is.null(prob[[opt_name]])) list() else as.list(prob[[opt_name]]),
as.list(val))
} else if (identical(opt_name, "mosek_params")) {
## CVXPY's carrier, accepted so migrated code keeps working.
for (p in names(val)) route_msk(p, val[[p]])
} else if (grepl("^MSK_[DIS]PAR_", opt_name)) {
route_msk(opt_name, val)
} else if (opt_name %in% rmosek_iface_opts) {
opts[[opt_name]] <- val
} else {
## CVXPY SOURCE: mosek_conif.py:674-676 -- unknown options raise.
cli_abort(c(
"Invalid option {.arg {opt_name}} for solver {.val MOSEK}.",
"i" = "Use {.arg dparam}/{.arg iparam}/{.arg sparam} lists, MSK_*-prefixed parameter names, or the Rmosek interface options ({.val {rmosek_iface_opts}})."
))
}
}
## -- Warm-start: pass previous solution as initial point -------
## Rmosek supports warm-start via prob$sol: pass the previous
## solution structure and MOSEK uses it as initial point.
## opts$usesol = TRUE is the Rmosek default.
## NOTE: CVXPY does NOT implement MOSEK warm-start -- this is an
## R-specific enhancement using native Rmosek capabilities.
cache_key <- MOSEK_SOLVER
if (warm_start && !is_mip && !is.null(solver_cache) && exists(cache_key, envir = solver_cache)) {
cached <- get(cache_key, envir = solver_cache)
cached_sol <- cached$sol$itr
if (!is.null(cached_sol) &&
!is.null(MOSEK_STATUS_MAP[[cached_sol$solsta]]) &&
MOSEK_STATUS_MAP[[cached_sol$solsta]] %in% SOLUTION_PRESENT &&
!is.null(cached_sol$xx) && length(cached_sol$xx) == n) {
prob$sol <- cached$sol
}
}
## -- Call MOSEK -------------------------------------------------
result <- Rmosek::mosek(prob, opts)
## -- Cache for future warm-starts ------------------------------
## Only cache continuous solves; MIP warm-start via integer feasible
## solution is not implemented in this path.
if (!is_mip && !is.null(solver_cache) && isTRUE(result$response$code == 0L) &&
!is.null(result$sol$itr)) {
assign(cache_key, result, envir = solver_cache)
}
## Attached AFTER caching so the cached entry stays a pure solver result.
result[[MOSEK_ACCEPT_UNKNOWN]] <- accept_unknown
result
}
# -- MOSEK info-item -> attr_list helper ---------------------------
## CVXPY SOURCE: mosek_conif.py v1.8.2 lines 531-540.
##
## Pull MOSEK's per-solve info-items off the Rmosek result list (which
## carries them under `dinfo` / `iinfo` / `liinfo` when the call was
## made with `getinfo = TRUE`) and pack them into the CVXR attr_list
## using the same keys Problem$solver_stats reads (RK_SOLVE_TIME,
## RK_NUM_ITERS, RK_EXTRA_STATS).
##
## NUM_ITERS mirrors CVXPY exactly: sum of intpnt_iter + sim_primal_iter
## + sim_dual_iter + mio_num_relax (so simplex / interior-point /
## branch-and-bound nodes all roll into one number, regardless of
## which optimizer MOSEK chose). MIO-specific fields go in
## EXTRA_STATS, matching CVXPY's dict layout.
##
## Defensive: any missing info-item resolves to 0 / NULL via the
## `%||%` fallback rather than crashing the solver pipeline.
.mosek_extract_attr <- function(result) {
`%||%` <- function(a, b) if (is.null(a)) b else a
dinfo <- result$dinfo %||% list()
iinfo <- result$iinfo %||% list()
liinfo <- result$liinfo %||% list()
attr_list <- list()
attr_list[[RK_SOLVE_TIME]] <- dinfo[["OPTIMIZER_TIME"]] %||% NA_real_
n_intpnt <- iinfo[["INTPNT_ITER"]] %||% 0L
n_sim_p <- iinfo[["SIM_PRIMAL_ITER"]] %||% 0L
n_sim_d <- iinfo[["SIM_DUAL_ITER"]] %||% 0L
n_mio_r <- iinfo[["MIO_NUM_RELAX"]] %||% 0L
attr_list[[RK_NUM_ITERS]] <-
as.integer(n_intpnt + n_sim_p + n_sim_d + n_mio_r)
extra <- list()
extra[["mio_intpnt_iter"]] <- liinfo[["MIO_INTPNT_ITER"]] %||% 0L
extra[["mio_simplex_iter"]] <- liinfo[["MIO_SIMPLEX_ITER"]] %||% 0L
attr_list[[RK_EXTRA_STATS]] <- extra
attr_list
}
# -- MOSEK reduction_invert ----------------------------------------
## Parses MOSEK-specific result format into a CVXR Solution.
method(reduction_invert, Mosek_Solver) <- function(x, solution, inverse_data, ...) {
## CVXPY SOURCE: mosek_conif.py:531-540 -- attr is populated once
## per solve, regardless of status. Done here so failure paths
## (SOLVER_ERROR / INFEASIBLE / UNBOUNDED) still surface timing
## and iteration counts via solver_stats().
attr_list <- .mosek_extract_attr(solution)
## Check for MOSEK errors. A missing or NA response code (Rmosek can
## return one when it rejects its inputs before optimizing) is a solver
## error too -- the old bare `!= 0L` comparison turned that case into
## "missing value where TRUE/FALSE needed", masking the real failure.
if (!isTRUE(solution$response$code == 0L)) {
return(failure_solution(SOLVER_ERROR, attr_list))
}
## MIP solves return the integer-feasible point in sol$int; continuous
## solves return the interior-point solution in sol$itr. Rmosek's
## getspecs_soltype maps MSK_SOL_ITG -> "int" (rmsk_utils_mosek.cc:144).
## CVXR's conic reduction sets is_mip on the inverse_data when bool/int
## variables are present (conic_solver.R:280).
is_mip <- isTRUE(inverse_data[["is_mip"]])
sol <- if (is_mip) solution$sol$int else solution$sol$itr
if (is.null(sol)) {
return(failure_solution(SOLVER_ERROR, attr_list))
}
## Map MOSEK status to CVXR status.
## Mirrors CVXPY mosek_conif.py v1.8.2 lines 541-560: check prosta first
## for the two infeasibility certificates, then fall back to the solsta map.
## - For MIP, prosta == "PRIMAL_INFEASIBLE" => INFEASIBLE
## (solsta is "UNKNOWN" because no integer-feasible solution was found).
## - prosta == "DUAL_INFEASIBLE" => UNBOUNDED for both continuous and MIP.
## (CVXPY also recovers an IIS via dualization in the dual_infeas branch;
## we do not have that infrastructure in CVXR yet, so we only set status.)
prosta <- sol$prosta
if (is_mip && identical(prosta, "PRIMAL_INFEASIBLE")) {
status <- INFEASIBLE
} else if (identical(prosta, "DUAL_INFEASIBLE")) {
status <- UNBOUNDED
} else {
solsta <- sol$solsta
## CVXPY SOURCE: mosek_conif.py:456-458 -- take a COPY of the map and
## re-point the `unknown` entry when `accept_unknown` was requested. The
## flag rides on the solver result; see solve_via_data() above.
## Guarded on a primal iterate being present, so the status is never
## promoted on an empty solution -- the same precondition Clarabel's
## version applies (clarabel_conif.R:154-157).
status_map <- MOSEK_STATUS_MAP
if (isTRUE(solution[[MOSEK_ACCEPT_UNKNOWN]]) && !is.null(sol$xx)) {
status_map[["UNKNOWN"]] <- OPTIMAL_INACCURATE
}
status <- status_map[[as.character(solsta)]]
if (is.null(status)) status <- SOLVER_ERROR
}
if (status %in% SOLUTION_PRESENT) {
dims <- inverse_data[[SD_DIMS]]
opt_val <- sol$pobjval + inverse_data[[SD_OFFSET]]
## Primal variables from sol$xx
primal_vars <- list()
primal_vars[[as.character(inverse_data[[SOLVER_VAR_ID]])]] <- sol$xx
## MIP path: MOSEK does not return dual variables for integer
## solutions; return the primal-only Solution and skip the dual
## reconstruction below (mirrors CVXPY's MIP-path behaviour).
if (is_mip) {
return(Solution(status, opt_val, primal_vars, list(), attr_list))
}
## -- Dual variables -------------------------------------------
## Rmosek 11.x does NOT return sol$y directly. Instead, linear
## constraint duals come from sol$slc (lower bound slack) and
## sol$suc (upper bound slack).
##
## Sign convention: MOSEK's y = slc - suc satisfies A^T y = c,
## but Clarabel/SCS use z satisfying A^T z = -c. So z = -(slc - suc)
## = suc - slc. This maps both Zero and NonNeg duals correctly.
##
## ACC conic duals come from sol$doty (already in CVXR convention).
##
## CVXR inverse_data splits:
## SOLVER_EQ_CONSTR -> Zero constraints
## SOLVER_NEQ_CONSTR -> [NonNeg, SOC, PSD, ExpCone, PowCone3D]
##
## We concatenate nonneg portion of linear_y with sol$doty to form
## the full ineq_dual vector.
zero_dim <- dims@zero
nonneg_dim <- dims@nonneg
linear_dim <- zero_dim + nonneg_dim
## Reconstruct dual vector from slc/suc (suc - slc = Clarabel convention)
linear_y <- if (linear_dim > 0L && !is.null(sol$suc) &&
length(sol$suc) >= linear_dim) {
sol$suc[seq_len(linear_dim)] - sol$slc[seq_len(linear_dim)]
} else {
numeric(0)
}
## Equality (zero cone) duals
if (zero_dim > 0L && length(linear_y) >= zero_dim) {
eq_dual <- get_dual_values(
linear_y[seq_len(zero_dim)],
extract_dual_value,
inverse_data[[SOLVER_EQ_CONSTR]]
)
} else {
eq_dual <- list()
}
## NonNeg duals from linear_y
nonneg_duals <- if (nonneg_dim > 0L && length(linear_y) >= linear_dim) {
linear_y[(zero_dim + 1L):linear_dim]
} else {
numeric(0)
}
conic_duals <- if (!is.null(sol$doty) && length(sol$doty) > 0L) {
doty <- sol$doty
## Un-permute ExpCone dual blocks from MOSEK order [2,1,0] back to CVXR [0,1,2]
if (dims@exp > 0L) {
## Calculate offset to ExpCone blocks within doty
## ACC order (no NonNeg): SOC, PSD, ExpCone, PowCone3D
exp_offset <- sum(dims@soc)
if (length(dims@psd) > 0L) {
exp_offset <- exp_offset + sum(vapply(dims@psd, function(d) (d * (d + 1L)) %/% 2L, integer(1L)))
}
for (k in seq_len(dims@exp)) {
base_idx <- exp_offset + (k - 1L) * 3L
## Swap indices 1 and 3 (MOSEK [z,y,x] -> CVXR [x,y,z])
tmp <- doty[base_idx + 1L]
doty[base_idx + 1L] <- doty[base_idx + 3L]
doty[base_idx + 3L] <- tmp
}
}
doty
} else {
numeric(0)
}
## Concatenate for get_dual_values (order matches SOLVER_NEQ_CONSTR)
full_ineq_vec <- c(nonneg_duals, conic_duals)
if (length(full_ineq_vec) > 0L && length(inverse_data[[SOLVER_NEQ_CONSTR]]) > 0L) {
ineq_dual <- get_dual_values(
full_ineq_vec,
extract_dual_value,
inverse_data[[SOLVER_NEQ_CONSTR]]
)
} else {
ineq_dual <- list()
}
dual_vars <- c(eq_dual, ineq_dual)
Solution(status, opt_val, primal_vars, dual_vars, attr_list)
} else {
failure_solution(status, attr_list)
}
}
method(print, Mosek_Solver) <- function(x, ...) {
cat("Mosek_Solver()\n")
invisible(x)
}
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.