R/245_reductions_solvers_conic_solvers_ecos_bb_conif.R

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/reductions/solvers/conic_solvers/ecos_bb_conif.R
#####

## CVXPY SOURCE: reductions/solvers/conic_solvers/ecos_bb_conif.py
## ECOS_BB (branch-and-bound) solver interface for mixed-integer problems.
##
## Inherits from ECOS_Solver, overrides:
## - MIP_CAPABLE = TRUE
## - solver_name -> "ECOS_BB"
## - solve_via_data: passes boolean/integer variable indices to ECOS_csolve()
## - reduction_invert: skips duals when problem is MIP
##
## R interface: ECOSolveR::ECOS_csolve(bool_vars = ..., int_vars = ...)


# -- ECOS_BB_Solver class ------------------------------------------
## CVXPY SOURCE: ecos_bb_conif.py lines 26-114

ECOS_BB_Solver <- new_class("ECOS_BB_Solver", parent = ECOS_Solver,
  package = "CVXR",
  constructor = function() {
    new_object(S7_object(),
      .cache = new.env(parent = emptyenv()),
      MIP_CAPABLE = TRUE,
      BOUNDED_VARIABLES = FALSE,
      SUPPORTED_CONSTRAINTS = list(Zero, NonNeg, SOC, ExpCone),
      EXP_CONE_ORDER = c(0L, 2L, 1L),
      REQUIRES_CONSTR = FALSE
    )
  }
)

method(solver_name, ECOS_BB_Solver) <- function(x) ECOS_BB_SOLVER

# -- ECOS_BB solve_via_data ----------------------------------------
## CVXPY SOURCE: ecos_bb_conif.py lines 82-114
## Same as ECOS but additionally passes bool_vars and int_vars.

method(solve_via_data, ECOS_BB_Solver) <- function(x, data, warm_start = FALSE, verbose = FALSE,
                                                     solver_opts = list(), ...) {
  if (!requireNamespace("ECOSolveR", quietly = TRUE)) {
    cli_abort("Package {.pkg ECOSolveR} is required but not installed.")
  }

  cones <- dims_to_solver_dict_ecos(data[[SD_DIMS]])

  ## Split solver data into eq (Zero) and ineq parts
  zero_dim <- data[[SD_DIMS]]@zero
  total_rows <- nrow(data[[SD_A]])

  ## Equality constraints
  if (zero_dim > 0L) {
    A_eq <- data[[SD_A]][seq_len(zero_dim), , drop = FALSE]
    b_eq <- data[[SD_B]][seq_len(zero_dim)]
  } else {
    A_eq <- NULL
    b_eq <- numeric(0)
  }

  ## Inequality constraints
  if (zero_dim < total_rows) {
    G <- data[[SD_A]][(zero_dim + 1L):total_rows, , drop = FALSE]
    h <- data[[SD_B]][(zero_dim + 1L):total_rows]
  } else {
    G <- NULL
    h <- numeric(0)
  }

  ## Ensure dgCMatrix for ECOSolveR
  if (!is.null(G) && !inherits(G, "dgCMatrix")) {
    G <- methods::as(G, "dgCMatrix")
  }
  if (!is.null(A_eq) && !inherits(A_eq, "dgCMatrix")) {
    A_eq <- methods::as(A_eq, "dgCMatrix")
  }

  ## Handle mi_verbose option (CVXPY ecos_bb_conif.py lines 93-97)
  mi_verbose <- verbose
  if ("mi_verbose" %in% names(solver_opts)) {
    mi_verbose <- solver_opts[["mi_verbose"]]
    solver_opts[["mi_verbose"]] <- NULL
  }

  ## Build control list
  ## ECOSolveR uses UPPERCASE control names (FEASTOL, MI_MAX_ITERS, ...),
  ## but CVXPY convention is lowercase (feastol, mi_max_iters, ...).
  ctrl <- ECOSolveR::ecos.control()
  ctrl[["VERBOSE"]] <- as.integer(verbose)
  for (opt_name in names(solver_opts)) {
    ctrl[[toupper(opt_name)]] <- solver_opts[[opt_name]]
  }

  ## Integer/boolean variable indices (already 1-based from R pipeline)
  bool_vars <- data[["bool_idx"]]
  int_vars <- data[["int_idx"]]
  if (is.null(bool_vars)) bool_vars <- integer(0)
  if (is.null(int_vars)) int_vars <- integer(0)

  ## Call ECOS with MIP extensions
  result <- ECOSolveR::ECOS_csolve(
    c         = data[[SD_C]],
    G         = G,
    h         = h,
    dims      = cones,
    A         = A_eq,
    b         = b_eq,
    bool_vars = bool_vars,
    int_vars  = int_vars,
    control   = ctrl
  )

  result
}

# -- ECOS_BB reduction_invert -------------------------------------
## CVXPY SOURCE: ecos_bb_conif.py lines 49-80
## Same as ECOS but no duals for MIP problems.

method(reduction_invert, ECOS_BB_Solver) <- function(x, solution, inverse_data, ...) {
  attr_list <- list()

  ## Extract status from exit flag
  exit_flag <- solution$retcodes[["exitFlag"]]
  if (is.null(exit_flag)) exit_flag <- solution$retcodes[[1L]]
  status <- ECOS_STATUS_MAP[[as.character(exit_flag)]]
  if (is.null(status)) status <- SOLVER_ERROR

  ## Timing attributes
  if (!is.null(solution$timing[["tsolve"]]))
    attr_list[[RK_SOLVE_TIME]] <- solution$timing[["tsolve"]]
  if (!is.null(solution$timing[["tsetup"]]))
    attr_list[[RK_SETUP_TIME]] <- solution$timing[["tsetup"]]
  iter_val <- solution$retcodes[["iter"]]
  if (!is.null(iter_val))
    attr_list[[RK_NUM_ITERS]] <- iter_val

  if (status %in% SOLUTION_PRESENT) {
    primal_val <- solution$summary[["pcost"]]
    opt_val <- primal_val + inverse_data[[SD_OFFSET]]

    primal_vars <- list()
    primal_vars[[as.character(inverse_data[[SOLVER_VAR_ID]])]] <- solution$x

    ## No duals for MIP problems (CVXPY ecos_bb_conif.py lines 62-76)
    dual_vars <- NULL
    if (!isTRUE(inverse_data[["is_mip"]])) {
      dual_vars <- get_dual_values(
        solution$z,
        extract_dual_value,
        inverse_data[[SOLVER_NEQ_CONSTR]]
      )

      ## Permute ExpCone duals
      for (con in inverse_data[[SOLVER_NEQ_CONSTR]]) {
        if (S7_inherits(con, ExpCone)) {
          cid <- as.character(con@id)
          n_cones <- num_cones(con)
          perm <- expcone_permutor(n_cones, c(0L, 2L, 1L))
          dual_vars[[cid]] <- dual_vars[[cid]][perm]
        }
      }

      eq_duals <- get_dual_values(
        solution$y,
        extract_dual_value,
        inverse_data[[SOLVER_EQ_CONSTR]]
      )
      dual_vars <- c(dual_vars, eq_duals)
    }

    ## CVXPY passes dual_vars=None for MIP; we use list() for R consistency
    if (is.null(dual_vars)) dual_vars <- list()

    Solution(status, opt_val, primal_vars, dual_vars, attr_list)
  } else {
    failure_solution(status, attr_list)
  }
}

# -- print ---------------------------------------------------------

method(print, ECOS_BB_Solver) <- function(x, ...) {
  cat("ECOS_BB_Solver()\n")
  invisible(x)
}

Try the CVXR package in your browser

Any scripts or data that you put into this service are public.

CVXR documentation built on March 6, 2026, 9:10 a.m.