def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): """Returns the result of the call to the solver. Parameters ---------- data : dict Data generated via an apply call. warm_start : Bool Whether to warm_start diffcp. verbose : Bool Control the verbosity. solver_opts : dict SCS-specific solver options. Returns ------- The result returned by a call to scs.solve(). """ import diffcp A = data[s.A] b = data[s.B] c = data[s.C] cones = scs_conif.dims_to_solver_dict(data[ConicSolver.DIMS]) # Default to eps = 1e-4 instead of 1e-3. solver_opts['eps'] = solver_opts.get('eps', 1e-4) results = diffcp.solve_and_derivative_internal( A, b, c, cones, verbose=verbose, raise_on_error=False, **solver_opts) if solver_cache is not None: solver_cache[self.name()] = results return results
def __init__(self, problem, parameters, variables): """Construct a CvxpyLayer Args: problem: The CVXPY problem; must be DPP. parameters: A list of CVXPY Parameters in the problem; the order of the Parameters determines the order in which parameter values must be supplied in the forward pass. Must include every parameter involved in problem. variables: A list of CVXPY Variables in the problem; the order of the Variables determines the order of the optimal variable values returned from the forward pass. """ super(CvxpyLayer, self).__init__() if not problem.is_dpp(): raise ValueError('Problem must be DPP.') if not set(problem.parameters()) == set(parameters): raise ValueError("The layer's parameters must exactly match " "problem.parameters") if not set(variables).issubset(set(problem.variables())): raise ValueError("Argument variables must be a subset of " "problem.variables") self.param_order = parameters self.param_ids = [p.id for p in self.param_order] self.variables = variables self.var_dict = {v.id for v in self.variables} # Construct compiler data, _, _ = problem.get_problem_data(solver=cp.SCS) self.compiler = data[cp.settings.PARAM_PROB] self.cone_dims = dims_to_solver_dict(data["dims"])
def _define_data(self, data: Dict[str, Any]) -> Tuple: """Define data parts from the data reference.""" c = data[s.C] b = data[s.B] A = dok_matrix(data[s.A]) # Save the dok_matrix. data[s.A] = A dims = dims_to_solver_dict(data[s.DIMS]) return A, b, c, dims
def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, solver_cache=None): """Returns the result of the call to the solver. Parameters ---------- data : dict Data generated via an apply call. warm_start : Bool Whether to warm_start diffcp. verbose : Bool Control the verbosity. solver_opts : dict SCS-specific solver options. Returns ------- The result returned by a call to scs.solve(). """ import diffcp A = data[s.A] b = data[s.B] c = data[s.C] cones = scs_conif.dims_to_solver_dict(data[ConicSolver.DIMS]) solver_opts["solve_method"] = solver_opts.get("solve_method", s.SCS) warm_start_tuple = None if solver_opts["solve_method"] == s.SCS: import scs if Version(scs.__version__) < Version('3.0.0'): # Default to eps = 1e-4 instead of 1e-3. solver_opts["eps"] = solver_opts.get("eps", 1e-4) else: solver_opts['eps_abs'] = solver_opts.get('eps_abs', 1e-5) solver_opts['eps_rel'] = solver_opts.get('eps_rel', 1e-5) if warm_start and solver_cache is not None and \ self.name() in solver_cache: warm_start_tuple = (solver_cache[self.name()]["x"], solver_cache[self.name()]["y"], solver_cache[self.name()]["s"]) start = time.time() results = diffcp.solve_and_derivative_internal( A, b, c, cones, verbose=verbose, warm_start=warm_start_tuple, raise_on_error=False, **solver_opts) end = time.time() results["TOT_TIME"] = end - start results["solve_method"] = solver_opts["solve_method"] if solver_cache is not None: solver_cache[self.name()] = results return results
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): """Returns the result of the call to the solver. Parameters ---------- data : dict Data generated via an apply call. warm_start : Bool Whether to warm_start diffcp. verbose : Bool Control the verbosity. solver_opts : dict SCS-specific solver options. Returns ------- The result returned by a call to scs.solve(). """ import diffcp A = data[s.A] b = data[s.B] c = data[s.C] cones = scs_conif.dims_to_solver_dict(data[ConicSolver.DIMS]) # This interface is tied to SCS, so force SCS for now. solver_opts['solve_method'] = 'SCS' # Default to eps = 1e-4 instead of 1e-3. solver_opts['eps'] = solver_opts.get('eps', 1e-4) warm_start_tuple = None if warm_start and solver_cache is not None and \ self.name() in solver_cache: warm_start_tuple = (solver_cache[self.name()]["x"], solver_cache[self.name()]["y"], solver_cache[self.name()]["s"]) start = time.time() results = diffcp.solve_and_derivative_internal( A, b, c, cones, verbose=verbose, warm_start=warm_start_tuple, raise_on_error=False, **solver_opts) end = time.time() results['TOT_TIME'] = end - start if solver_cache is not None: solver_cache[self.name()] = results return results
def __init__(self, problem, parameters, variables, gp=False): """Construct a CvxpyLayer Args: problem: The CVXPY problem; must be DPP. parameters: A list of CVXPY Parameters in the problem; the order of the Parameters determines the order in which parameter values must be supplied in the forward pass. variables: A list of CVXPY Variables in the problem; the order of the Variables determines the order of the optimal variable values returned from the forward pass. gp: Whether to parse the problem using DGP (True or False). """ if gp: if not problem.is_dgp(dpp=True): raise ValueError('Problem must be DPP.') else: if not problem.is_dcp(dpp=True): raise ValueError('Problem must be DPP.') if set(parameters) != set(problem.parameters()): raise ValueError("The layer's parameters must exactly match " "problem.parameters") if not set(variables).issubset(set(problem.variables())): raise ValueError('Argument `variables` must be a subset of ' '`problem.variables()`') self.params = parameters self.gp = gp if self.gp: for param in parameters: if param.value is None: raise ValueError("An initial value for each parameter is " "required when gp=True.") data, solving_chain, _ = ( problem.get_problem_data(solver=cp.SCS, gp=True) ) self.asa_maps = data[cp.settings.PARAM_PROB] self.dgp2dcp = solving_chain.get(cp.reductions.Dgp2Dcp) self.param_ids = [p.id for p in self.asa_maps.parameters] else: data, _, _ = problem.get_problem_data(solver=cp.SCS) self.asa_maps = data[cp.settings.PARAM_PROB] self.param_ids = [p.id for p in self.params] self.cones = dims_to_solver_dict(data['dims']) self.vars = variables
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): """Returns the result of the call to SuperSCS. Parameters ---------- data : dict Data generated via an apply call. warm_start : Bool Whether to warm_start SuperSCS. verbose : Bool Control the verbosity. solver_opts : dict SuperSCS-specific options. Returns ------- The result returned by a call to superscs.solve(). """ import superscs args = {"A": data[s.A], "b": data[s.B], "c": data[s.C]} if warm_start and solver_cache is not None and \ self.name in solver_cache: args["x"] = solver_cache[self.name()]["x"] args["y"] = solver_cache[self.name()]["y"] args["s"] = solver_cache[self.name()]["s"] cones = dims_to_solver_dict(data[ConicSolver.DIMS]) # settings user_opts = list(solver_opts.keys()) for k in list(SuperSCS.DEFAULT_SETTINGS.keys()): if k not in user_opts: solver_opts[k] = SuperSCS.DEFAULT_SETTINGS[k] results = superscs.solve(args, cones, verbose=verbose, **solver_opts) if solver_cache is not None: solver_cache[self.name()] = results return results
def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, solver_cache=None): import xpress as xp c = data[s.C] # objective coefficients dims = dims_to_solver_dict( data[s.DIMS]) # contains number of columns, rows, etc. nrowsEQ = dims[s.EQ_DIM] nrowsLEQ = dims[s.LEQ_DIM] nrows = nrowsEQ + nrowsLEQ # linear constraints b = data[s.B][:nrows] # right-hand side A = data[s.A][:nrows] # coefficient matrix # Problem self.prob_ = xp.problem() mstart = makeMstart(A, len(c), 1) varGroups = { } # If origprob is passed, used to tie IIS to original constraints transf2Orig = { } # Ties transformation constraints to originals via varGroups nOrigVar = len(c) # Uses flat naming. Warning: this mixes # original with auxiliary variables. varnames = ['x_{0:05d}'.format(i) for i in range(len(c))] linRownames = ['lc_{0:05d}'.format(i) for i in range(len(b))] if verbose: self.prob_.controls.miplog = 2 self.prob_.controls.lplog = 1 self.prob_.controls.outputlog = 1 else: self.prob_.controls.miplog = 0 self.prob_.controls.lplog = 0 self.prob_.controls.outputlog = 0 self.prob_.controls.xslp_log = -1 self.prob_.loadproblem( probname="CVX_xpress_conic", # constraint types qrtypes=['E'] * nrowsEQ + ['L'] * nrowsLEQ, rhs=b, # rhs range=None, # range obj=c, # obj coeff mstart=mstart, # mstart mnel=None, # mnel (unused) # linear coefficients mrwind=A.indices[A.data != 0], # row indices dmatval=A.data[A.data != 0], # coefficients dlb=[-xp.infinity] * len(c), # lower bound dub=[xp.infinity] * len(c), # upper bound colnames=varnames, # column names rownames=linRownames) # row names # Set variable types for discrete variables self.prob_.chgcoltype( data[s.BOOL_IDX] + data[s.INT_IDX], 'B' * len(data[s.BOOL_IDX]) + 'I' * len(data[s.INT_IDX])) currow = nrows iCone = 0 auxVars = set(range(nOrigVar, len(c))) # Conic constraints # # Quadratic objective and constraints fall in this category, # as all quadratic stuff is converted into a cone via a linear transformation for k in dims[s.SOC_DIM]: # k is the size of the i-th cone, where i is the index # within dims [s.SOC_DIM]. The cone variables in # CVXOPT, apparently, are separate variables that are # marked as conic but not shown in a cone explicitly. A = data[s.A][currow:currow + k].tocsr() b = data[s.B][currow:currow + k] currow += k # Create new (cone) variables and add them to the problem conevar = np.array([ xp.var(name='cX{0:d}_{1:d}'.format(iCone, i), lb=-xp.infinity if i > 0 else 0) for i in range(k) ]) self.prob_.addVariable(conevar) initrow = self.prob_.attributes.rows mstart = makeMstart(A, k, 0) trNames = ['linT_qc{0:d}_{1:d}'.format(iCone, i) for i in range(k)] # Linear transformation for cone variables <--> original variables self.prob_.addrows( ['E'] * k, # qrtypes b, # rhs mstart, # mstart A.indices[A.data != 0], # ind A.data[A.data != 0], # dmatval names=trNames) # row names self.prob_.chgmcoef([initrow + i for i in range(k)], conevar, [1] * k) conename = 'cone_qc{0:d}'.format(iCone) # Real cone on the cone variables (if k == 1 there's no # need for this constraint as y**2 >= 0 is redundant) if k > 1: self.prob_.addConstraint( xp.constraint(constraint=xp.Sum( conevar[i]**2 for i in range(1, k)) <= conevar[0]**2, name=conename)) auxInd = list(set(A.indices) & auxVars) if len(auxInd) > 0: group = varGroups[varnames[auxInd[0]]] for i in trNames: transf2Orig[i] = group transf2Orig[conename] = group iCone += 1 # End of the conditional (warm-start vs. no warm-start) code, # set options, solve, and report. # Set options # # The parameter solver_opts is a dictionary that contains only # one key, 'solver_opt', and its value is a dictionary # {'control': value}, matching perfectly the format used by # the Xpress Python interface. self.prob_.setControl({ i: solver_opts[i] for i in solver_opts if i in xp.controls.__dict__ }) if 'bargaptarget' not in solver_opts: self.prob_.controls.bargaptarget = 1e-30 if 'feastol' not in solver_opts: self.prob_.controls.feastol = 1e-9 # If option given, write file before solving if 'write_mps' in solver_opts: self.prob_.write(solver_opts['write_mps']) # Solve self.prob_.solve() results_dict = { 'problem': self.prob_, 'status': self.prob_.getProbStatus(), 'obj_value': self.prob_.getObjVal(), } status_map_lp, status_map_mip = get_status_maps() if 'mip_' in self.prob_.getProbStatusString(): status = status_map_mip[results_dict['status']] else: status = status_map_lp[results_dict['status']] results_dict[s.XPRESS_TROW] = transf2Orig results_dict[ s.XPRESS_IIS] = None # Return no IIS if problem is feasible if status in s.SOLUTION_PRESENT: results_dict['x'] = self.prob_.getSolution() if not (data[s.BOOL_IDX] or data[s.INT_IDX]): results_dict['y'] = -np.array(self.prob_.getDual()) elif status == s.INFEASIBLE: # Retrieve all IIS. For LPs there can be more than one, # but for QCQPs there is only support for one IIS. iisIndex = 0 self.prob_.iisfirst(0) # compute all IIS row, col, rtype, btype, duals, rdcs, isrows, icols = [], [], [], [], [], [], [], [] self.prob_.getiisdata(0, row, col, rtype, btype, duals, rdcs, isrows, icols) origrow = [] for iRow in row: if iRow.name in transf2Orig: name = transf2Orig[iRow.name] else: name = iRow.name if name not in origrow: origrow.append(name) results_dict[s.XPRESS_IIS] = [{ 'orig_row': origrow, 'row': row, 'col': col, 'rtype': rtype, 'btype': btype, 'duals': duals, 'redcost': rdcs, 'isolrow': isrows, 'isolcol': icols }] while self.prob_.iisnext() == 0: iisIndex += 1 self.prob_.getiisdata(iisIndex, row, col, rtype, btype, duals, rdcs, isrows, icols) results_dict[s.XPRESS_IIS].append( (row, col, rtype, btype, duals, rdcs, isrows, icols)) # Generate solution. solution = {} status_map_lp, status_map_mip = get_status_maps() if data[s.BOOL_IDX] or data[s.INT_IDX]: solution[s.STATUS] = status_map_mip[results_dict['status']] else: solution[s.STATUS] = status_map_lp[results_dict['status']] if solution[s.STATUS] in s.SOLUTION_PRESENT: solution[s.PRIMAL] = results_dict['x'] solution[s.VALUE] = results_dict['obj_value'] if not (data[s.BOOL_IDX] or data[s.INT_IDX]): solution[s.EQ_DUAL] = results_dict['y'][0:dims[s.EQ_DIM]] solution[s.INEQ_DUAL] = results_dict['y'][dims[s.EQ_DIM]:] solution[s.XPRESS_IIS] = results_dict[s.XPRESS_IIS] solution[s.XPRESS_TROW] = results_dict[s.XPRESS_TROW] solution['getObjVal'] = self.prob_.getObjVal() solution[s.SOLVE_TIME] = self.prob_.attributes.time del self.prob_ return solution
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): import cplex c = data[s.C] b = data[s.B] A = dok_matrix(data[s.A]) # Save the dok_matrix. data[s.A] = A dims = dims_to_solver_dict(data[s.DIMS]) n = c.shape[0] model = cplex.Cplex() variables = [] # cpx_constrs will contain CpxConstr namedtuples (see above). cpx_constrs = [] vtype = [] if data[s.BOOL_IDX] or data[s.INT_IDX]: for i in range(n): # Set variable type. if i in data[s.BOOL_IDX]: vtype.append('B') elif i in data[s.INT_IDX]: vtype.append('I') else: vtype.append('C') else: # If we specify types (even with 'C'), then the problem will # be interpreted as a MIP. Leaving vtype as an empty list # here, will ensure that the problem type remains an LP. pass # Add the variables in a batch variables = list( model.variables.add( obj=[c[i] for i in range(n)], lb=[-cplex.infinity] * n, # default LB is 0 ub=[cplex.infinity] * n, types="".join(vtype), names=["x_%d" % i for i in range(n)])) # Add equality constraints cpx_constrs += [ _CpxConstr(_LIN, x) for x in self.add_model_lin_constr( model, variables, range(dims[s.EQ_DIM]), 'E', A, b) ] # Add inequality (<=) constraints leq_start = dims[s.EQ_DIM] leq_end = dims[s.EQ_DIM] + dims[s.LEQ_DIM] cpx_constrs += [ _CpxConstr(_LIN, x) for x in self.add_model_lin_constr( model, variables, range(leq_start, leq_end), 'L', A, b) ] # Add SOC constraints soc_start = leq_end for constr_len in dims[s.SOC_DIM]: soc_end = soc_start + constr_len soc_constr, new_leq, new_vars = self.add_model_soc_constr( model, variables, range(soc_start, soc_end), A, b) cpx_constrs.append(_CpxConstr(_QUAD, soc_constr)) cpx_constrs += [_CpxConstr(_LIN, x) for x in new_leq] variables += new_vars soc_start += constr_len # Set verbosity if not verbose: model.set_results_stream(None) model.set_warning_stream(None) model.set_error_stream(None) model.set_log_stream(None) else: # By default the output will be sent to stdout. pass # TODO: user option to not compute duals. model.parameters.preprocessing.qcpduals.set( model.parameters.preprocessing.qcpduals.values.force) # TODO: Parameter support is functional, but perhaps not ideal. # The user must pass parameter names as used in the CPLEX Python # API, and raw values (i.e., no enum support). kwargs = sorted(solver_opts.keys()) if "cplex_params" in kwargs: for param, value in solver_opts["cplex_params"].items(): try: eval("model.parameters.{0}.set({1})".format(param, value)) except AttributeError: raise ValueError( "invalid CPLEX parameter, value pair ({0}, {1})". format(param, value)) kwargs.remove("cplex_params") if "cplex_filename" in kwargs: filename = solver_opts["cplex_filename"] if filename: model.write(filename) kwargs.remove("cplex_filename") if s.BOOL_IDX in kwargs: kwargs.remove(s.BOOL_IDX) if s.INT_IDX in kwargs: kwargs.remove(s.INT_IDX) if kwargs: raise ValueError("invalid keyword-argument '{0}'".format( kwargs[0])) solution = {} start_time = model.get_time() solution[s.SOLVE_TIME] = -1 try: model.solve() solution[s.SOLVE_TIME] = model.get_time() - start_time solution["value"] = model.solution.get_objective_value() solution["primal"] = np.array(model.solution.get_values(variables)) solution["status"] = self._get_status(model) # Only add duals if not a MIP. if not (data[s.BOOL_IDX] or data[s.INT_IDX]): vals = [] for con in cpx_constrs: assert con.index is not None if con.constr_type == _LIN: vals.append(model.solution.get_dual_values(con.index)) else: assert con.constr_type == _QUAD # Quadratic constraints not queried directly. vals.append(0.0) solution["y"] = -np.array(vals) solution[s.EQ_DUAL] = solution["y"][0:dims[s.EQ_DIM]] solution[s.INEQ_DUAL] = solution["y"][dims[s.EQ_DIM]:] except Exception: if solution[s.SOLVE_TIME] < 0.0: solution[s.SOLVE_TIME] = model.get_time() - start_time solution["status"] = s.SOLVER_ERROR return solution
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): """Returns the result of the call to the solver. Parameters ---------- data : dict Data used by the solver. warm_start : bool Not used. verbose : bool Should the solver print output? solver_opts : dict Additional arguments for the solver. Returns ------- tuple (status, optimal value, primal, equality dual, inequality dual) """ import gurobipy c = data[s.C] b = data[s.B] A = dok_matrix(data[s.A]) # Save the dok_matrix. data[s.A] = A dims = dims_to_solver_dict(data[s.DIMS]) n = c.shape[0] model = gurobipy.Model() variables = [] for i in range(n): # Set variable type. if i in data[s.BOOL_IDX]: vtype = gurobipy.GRB.BINARY elif i in data[s.INT_IDX]: vtype = gurobipy.GRB.INTEGER else: vtype = gurobipy.GRB.CONTINUOUS variables.append( model.addVar( obj=c[i], name="x_%d" % i, vtype=vtype, # Gurobi's default LB is 0 (WHY???) lb=-gurobipy.GRB.INFINITY, ub=gurobipy.GRB.INFINITY)) model.update() eq_constrs = self.add_model_lin_constr(model, variables, list(range(dims[s.EQ_DIM])), gurobipy.GRB.EQUAL, A, b) leq_start = dims[s.EQ_DIM] leq_end = dims[s.EQ_DIM] + dims[s.LEQ_DIM] ineq_constrs = self.add_model_lin_constr( model, variables, list(range(leq_start, leq_end)), gurobipy.GRB.LESS_EQUAL, A, b) soc_start = leq_end soc_constrs = [] new_leq_constrs = [] for constr_len in dims[s.SOC_DIM]: soc_end = soc_start + constr_len soc_constr, new_leq, new_vars = self.add_model_soc_constr( model, variables, list(range(soc_start, soc_end)), A, b) soc_constrs.append(soc_constr) new_leq_constrs += new_leq variables += new_vars soc_start += constr_len gur_constrs = eq_constrs + ineq_constrs + \ soc_constrs + new_leq_constrs model.update() # Set verbosity and other parameters model.setParam("OutputFlag", verbose) # TODO user option to not compute duals. model.setParam("QCPDual", True) for key, value in list(solver_opts.items()): model.setParam(key, value) solution = {} try: model.optimize() solution["value"] = model.ObjVal solution["primal"] = np.array([v.X for v in variables]) # Only add duals if not a MIP. # Not sure why we need to negate the following, # but need to in order to be consistent with other solvers. if not (data[s.BOOL_IDX] or data[s.INT_IDX]): vals = [] for lc in gur_constrs: if lc is not None: if isinstance(lc, gurobipy.QConstr): vals.append(lc.QCPi) else: vals.append(lc.Pi) else: vals.append(0) solution["y"] = -np.array(vals) solution[s.EQ_DUAL] = solution["y"][0:dims[s.EQ_DIM]] solution[s.INEQ_DUAL] = solution["y"][dims[s.EQ_DIM]:] except Exception: pass solution[s.SOLVE_TIME] = model.Runtime solution["status"] = self.STATUS_MAP.get(model.Status, s.SOLVER_ERROR) if solution["status"] == s.SOLVER_ERROR and model.SolCount: solution["status"] = s.OPTIMAL_INACCURATE return solution
def solve_via_data(self, data, warm_start: bool, verbose: bool, solver_opts, solver_cache=None): """Returns the result of the call to the solver. Parameters ---------- data : dict Data used by the solver. warm_start : bool Not used. verbose : bool Should the solver print output? solver_opts : dict Additional arguments for the solver. Returns ------- tuple (status, optimal value, primal, equality dual, inequality dual) """ import gurobipy c = data[s.C] b = data[s.B] A = sp.csr_matrix(data[s.A]) dims = dims_to_solver_dict(data[s.DIMS]) n = c.shape[0] # Create a new model if 'env' in solver_opts: # Specifies environment to create Gurobi model for control over licensing and parameters # https://www.gurobi.com/documentation/9.1/refman/environments.html default_env = solver_opts['env'] del solver_opts['env'] model = gurobipy.Model(env=default_env) else: # Create Gurobi model using default (unspecified) environment model = gurobipy.Model() # Pass through verbosity model.setParam("OutputFlag", verbose) variables = [] for i in range(n): # Set variable type. if i in data[s.BOOL_IDX]: vtype = gurobipy.GRB.BINARY elif i in data[s.INT_IDX]: vtype = gurobipy.GRB.INTEGER else: vtype = gurobipy.GRB.CONTINUOUS variables.append( model.addVar( obj=c[i], name="x_%d" % i, vtype=vtype, # Gurobi's default LB is 0 (WHY???) lb=-gurobipy.GRB.INFINITY, ub=gurobipy.GRB.INFINITY)) model.update() leq_start = dims[s.EQ_DIM] leq_end = dims[s.EQ_DIM] + dims[s.LEQ_DIM] if hasattr(model, 'addMConstrs'): eq_constrs = model.addMConstrs(A[:leq_start, :], None, gurobipy.GRB.EQUAL, b[:leq_start]) ineq_constrs = model.addMConstrs(A[leq_start:leq_end, :], None, gurobipy.GRB.LESS_EQUAL, b[leq_start:leq_end]) else: eq_constrs = self.add_model_lin_constr(model, variables, range(dims[s.EQ_DIM]), gurobipy.GRB.EQUAL, A, b) ineq_constrs = self.add_model_lin_constr(model, variables, range(leq_start, leq_end), gurobipy.GRB.LESS_EQUAL, A, b) # TODO: add all SOC constrs at once! Be careful with return values soc_start = leq_end soc_constrs = [] new_leq_constrs = [] for constr_len in dims[s.SOC_DIM]: soc_end = soc_start + constr_len soc_constr, new_leq, new_vars = self.add_model_soc_constr( model, variables, range(soc_start, soc_end), A, b) soc_constrs.append(soc_constr) new_leq_constrs += new_leq variables += new_vars soc_start += constr_len # Set parameters # TODO user option to not compute duals. model.setParam("QCPDual", True) for key, value in solver_opts.items(): model.setParam(key, value) solution = {} try: model.optimize() # Reoptimize if INF_OR_UNBD, to get definitive answer. if model.Status == 4: model.setParam("DualReductions", 0) model.optimize() solution["value"] = model.ObjVal solution["primal"] = np.array([v.X for v in variables]) # Only add duals if not a MIP. # Not sure why we need to negate the following, # but need to in order to be consistent with other solvers. vals = [] if not (data[s.BOOL_IDX] or data[s.INT_IDX]): lin_constrs = eq_constrs + ineq_constrs + new_leq_constrs vals += model.getAttr('Pi', lin_constrs) vals += model.getAttr('QCPi', soc_constrs) solution["y"] = -np.array(vals) solution[s.EQ_DUAL] = solution["y"][0:dims[s.EQ_DIM]] solution[s.INEQ_DUAL] = solution["y"][dims[s.EQ_DIM]:] except Exception: pass solution[s.SOLVE_TIME] = model.Runtime solution["status"] = self.STATUS_MAP.get(model.Status, s.SOLVER_ERROR) if solution["status"] == s.SOLVER_ERROR and model.SolCount: solution["status"] = s.OPTIMAL_INACCURATE if solution["status"] == s.USER_LIMIT and not model.SolCount: solution["status"] = s.INFEASIBLE_INACCURATE solution["model"] = model return solution
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): import xpress if 'no_qp_reduction' in solver_opts.keys( ) and solver_opts['no_qp_reduction'] is True: self.translate_back_QP_ = True c = data[s.C] # objective coefficients dims = dims_to_solver_dict( data[s.DIMS]) # contains number of columns, rows, etc. nrowsEQ = dims[s.EQ_DIM] nrowsLEQ = dims[s.LEQ_DIM] nrows = nrowsEQ + nrowsLEQ # linear constraints b = data[s.B][:nrows] # right-hand side A = data[s.A][:nrows] # coefficient matrix # Problem self.prob_ = xpress.problem() mstart = makeMstart(A, len(c), 1) varGroups = { } # If origprob is passed, used to tie IIS to original constraints transf2Orig = { } # Ties transformation constraints to originals via varGroups nOrigVar = len(c) # Uses flat naming. Warning: this mixes # original with auxiliary variables. varnames = ['x_{0:05d}'.format(i) for i in range(len(c))] linRownames = ['lc_{0:05d}'.format(i) for i in range(len(b))] self.prob_.loadproblem( "CVXproblem", ['E'] * nrowsEQ + ['L'] * nrowsLEQ, # qrtypes None, # range c, # obj coeff mstart, # mstart None, # mnel A.indices, # row indices A.data, # coefficients [-xpress.infinity] * len(c), # lower bound [xpress.infinity] * len(c), # upper bound colnames=varnames, # column names rownames=linRownames) # row names x = np.array(self.prob_.getVariable()) # get whole variable vector # Set variable types for discrete variables self.prob_.chgcoltype( data[s.BOOL_IDX] + data[s.INT_IDX], 'B' * len(data[s.BOOL_IDX]) + 'I' * len(data[s.INT_IDX])) currow = nrows iCone = 0 auxVars = set(range(nOrigVar, len(c))) # Conic constraints # # Quadratic objective and constraints fall in this category, # as all quadratic stuff is converted into a cone via a linear transformation for k in dims[s.SOC_DIM]: # k is the size of the i-th cone, where i is the index # within dims [s.SOC_DIM]. The cone variables in # CVXOPT, apparently, are separate variables that are # marked as conic but not shown in a cone explicitly. A = data[s.A][currow:currow + k].tocsr() b = data[s.B][currow:currow + k] currow += k if self.translate_back_QP_: # Conic problem passed by CVXPY is translated back # into a QP problem. The problem is passed to us # as follows: # # min c'x # s.t. Ax <>= b # y[i] = P[i]' * x + b[i] # ||y[i][1:]||_2 <= y[i][0] # # where P[i] is a matrix, b[i] is a vector. Get # rid of the y variables by explicitly rewriting # the conic constraint as quadratic: # # y[i][1:]' * y[i][1:] <= y[i][0]^2 # # and hence # # (P[i][1:]' * x + b[i][1:])^2 <= (P[i][0]' * x + b[i][0])^2 Plhs = A[1:] Prhs = A[0] indRowL, indColL = Plhs.nonzero() indRowR, indColR = Prhs.nonzero() coeL = Plhs.data coeR = Prhs.data lhs = list(b[1:]) rhs = b[0] for i in range(len(coeL)): lhs[indRowL[i]] -= coeL[i] * x[indColL[i]] for i in range(len(coeR)): rhs -= coeR[i] * x[indColR[i]] self.prob_.addConstraint( xpress.Sum([lhs[i]**2 for i in range(len(lhs))]) <= rhs**2) else: # Create new (cone) variables and add them to the problem conevar = np.array([ xpress.var(name='cX{0:d}_{1:d}'.format(iCone, i), lb=-xpress.infinity if i > 0 else 0) for i in range(k) ]) self.prob_.addVariable(conevar) initrow = self.prob_.attributes.rows mstart = makeMstart(A, k, 0) trNames = [ 'linT_qc{0:d}_{1:d}'.format(iCone, i) for i in range(k) ] # Linear transformation for cone variables <--> original variables self.prob_.addrows( ['E'] * k, # qrtypes b, # rhs mstart, # mstart A.indices, # ind A.data, # dmatval names=trNames) # row names self.prob_.chgmcoef([initrow + i for i in range(k)], conevar, [1] * k) conename = 'cone_qc{0:d}'.format(iCone) # Real cone on the cone variables (if k == 1 there's no # need for this constraint as y**2 >= 0 is redundant) if k > 1: self.prob_.addConstraint( xpress.constraint(constraint=xpress.Sum( conevar[i]**2 for i in range(1, k)) <= conevar[0]**2, name=conename)) auxInd = list(set(A.indices) & auxVars) if len(auxInd) > 0: group = varGroups[varnames[auxInd[0]]] for i in trNames: transf2Orig[i] = group transf2Orig[conename] = group iCone += 1 # Objective. Minimize is by default both here and in CVXOPT self.prob_.setObjective(xpress.Sum(c[i] * x[i] for i in range(len(c)))) # End of the conditional (warm-start vs. no warm-start) code, # set options, solve, and report. # Set options # # The parameter solver_opts is a dictionary that contains only # one key, 'solver_opt', and its value is a dictionary # {'control': value}, matching perfectly the format used by # the Xpress Python interface. if verbose: self.prob_.controls.miplog = 2 self.prob_.controls.lplog = 1 self.prob_.controls.outputlog = 1 else: self.prob_.controls.miplog = 0 self.prob_.controls.lplog = 0 self.prob_.controls.outputlog = 0 if 'solver_opts' in solver_opts.keys(): self.prob_.setControl(solver_opts['solver_opts']) self.prob_.setControl({ i: solver_opts[i] for i in solver_opts.keys() if i in xpress.controls.__dict__.keys() }) # Solve self.prob_.solve() results_dict = { 'problem': self.prob_, 'status': self.prob_.getProbStatus(), 'obj_value': self.prob_.getObjVal(), } status_map_lp, status_map_mip = self.get_status_maps() if self.is_mip(data): status = status_map_mip[results_dict['status']] else: status = status_map_lp[results_dict['status']] results_dict[s.XPRESS_TROW] = transf2Orig results_dict[ s.XPRESS_IIS] = None # Return no IIS if problem is feasible if status in s.SOLUTION_PRESENT: results_dict['x'] = self.prob_.getSolution() if not (data[s.BOOL_IDX] or data[s.INT_IDX]): results_dict['y'] = self.prob_.getDual() elif status == s.INFEASIBLE: # Retrieve all IIS. For LPs there can be more than one, # but for QCQPs there is only support for one IIS. iisIndex = 0 self.prob_.iisfirst(0) # compute all IIS row, col, rtype, btype, duals, rdcs, isrows, icols = [], [], [], [], [], [], [], [] self.prob_.getiisdata(0, row, col, rtype, btype, duals, rdcs, isrows, icols) origrow = [] for iRow in row: if iRow.name in transf2Orig.keys(): name = transf2Orig[iRow.name] else: name = iRow.name if name not in origrow: origrow.append(name) results_dict[s.XPRESS_IIS] = [{ 'orig_row': origrow, 'row': row, 'col': col, 'rtype': rtype, 'btype': btype, 'duals': duals, 'redcost': rdcs, 'isolrow': isrows, 'isolcol': icols }] while self.prob_.iisnext() == 0: iisIndex += 1 self.prob_.getiisdata(iisIndex, row, col, rtype, btype, duals, rdcs, isrows, icols) results_dict[s.XPRESS_IIS].append( (row, col, rtype, btype, duals, rdcs, isrows, icols)) # Generate solution. solution = {} if results_dict["status"] != s.SOLVER_ERROR: self.prob_ = results_dict['problem'] vartypes = [] self.prob_.getcoltype(vartypes, 0, len(data[s.C]) - 1) status_map_lp, status_map_mip = self.get_status_maps() if data[s.BOOL_IDX] or data[s.INT_IDX]: solution[s.STATUS] = status_map_mip[results_dict['status']] else: solution[s.STATUS] = status_map_lp[results_dict['status']] if solution[s.STATUS] in s.SOLUTION_PRESENT: solution[s.PRIMAL] = results_dict['x'] solution[s.VALUE] = results_dict['obj_value'] if not (data[s.BOOL_IDX] or data[s.INT_IDX]): solution[s.EQ_DUAL] = [-v for v in results_dict['y']] solution[s.XPRESS_IIS] = results_dict[s.XPRESS_IIS] solution[s.XPRESS_TROW] = results_dict[s.XPRESS_TROW] return solution
def cone_dims_for_scs(self): return dims_to_solver_dict(self._data['dims'])
def repair(prob, params, r=None, verbose=True, maxiter=10, maxiter_pgm=25, lam=1, lam_factor=2, lr=.1): """ Repairs prob by altering params. Minimizes r(params) subject to the constraint that prob is solvable. Args: - prob: cvxpy Problem object - params: list of cvxpy Parameters involved in prob - r (optional): callable that takes a list of cvxpy Variables as input (of the same dimension as params), and returns a cvxpy expression representing the performance metric (default=None). - verbose (optional): whether or not to print diagnostic informtion (default=True). - maxiter (optional): Maximum number of outer iterations (default=10). - maxiter_pgm (optional): Maximum number of inner iterations (default=25). - lam (optional): Starting value for 1/lambda, multiplied by lam_factor each iteration (default=1). - lam_factor (optional): Factor to multiply lambda by each iteration (default=2). - lr (optional): initial step size for proximal gradient method (default=.1). """ assert set(prob.parameters()) == set(params) assert hasattr(prob, "get_problem_data") # compile problem data, _, _ = prob.get_problem_data(solver=cp.SCS) compiler = data[cp.settings.PARAM_PROB] cone_dict = dims_to_solver_dict(data["dims"]) param_ids = [p.id for p in params] warm_start = None for k in range(maxiter): # minimize (1/lam) * r(A, b, c) + t(A, b, c) objective = float("inf") for k_pgm in range(maxiter_pgm): # compute derivative c, _, neg_A, b = compiler.apply_parameters( dict(zip(param_ids, [p.value for p in params]))) A = -neg_A dA, db, dc, t, _, _, _, warm_start = derivative( A, b, c, cone_dict, warm_start=warm_start, acceleration_lookback=0, eps=1e-8) del_param_dict = compiler.apply_param_jac(dc, -dA, db) param_derivative = [del_param_dict[i] for i in param_ids] # compute objective objective = t new_objective = float("inf") if r is not None: variable_params = [cp.Variable(p.shape) for p in params] for vp, p in zip(variable_params, params): vp.value = p.value obj, cons = r(variable_params) objective += (1 / lam) * obj.value # update step size until objective decreases old_params = [p.value.copy() for p in params] while True: lr = np.clip(lr, 1e-6, 1e6) new_params = [] for i in range(len(param_ids)): new_params += [old_params[i] - lr * param_derivative[i]] if r is not None: variable_params = [cp.Variable( p.shape) for p in params] obj, cons = r(variable_params) obj = lr * obj / lam obj += cp.sum([.5 * cp.sum_squares(p - v) for (p, v) in zip(new_params, variable_params)]) prob = cp.Problem(cp.Minimize(obj), cons) try: prob.solve(solver=cp.MOSEK) except: prob.solve(solver=cp.SCS, acceleration_lookback=0) for i in range(len(param_ids)): params[i].value = variable_params[i].value else: for i in range(len(param_ids)): params[i].value = new_params[i] # compute objective c, _, neg_A, b = compiler.apply_parameters( dict(zip(param_ids, [p.value for p in params]))) A = -neg_A _, _, _, t_new, _, _, _, warm_start = derivative( A, b, c, cone_dict, warm_start=warm_start, acceleration_lookback=0, eps=1e-8) new_objective = t_new if r is not None: variable_params = [cp.Variable(p.shape) for p in params] for vp, p in zip(variable_params, params): vp.value = p.value obj, cons = r(variable_params) new_objective += obj.value / lam if new_objective < objective: lr *= 1.2 break elif lr > 1e-6: lr /= 2.0 else: break if lr <= 1e-6: break # update lam lam *= lam_factor if verbose: print(f'Updating lambda to {lam}') r_val = 0.0 if r is not None: variable_params = [cp.Variable(p.shape) for p in params] for vp, p in zip(variable_params, params): vp.value = p.value obj, cons = r(variable_params) r_val += obj.value if verbose: print("Proximal gradient method completed in %d/%d iterations" % (k_pgm + 1, maxiter_pgm)) print("Iteration: %d, r: %3.3f, t: %3.3f" % (k, r_val, t))
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): import cplex c = data[s.C] b = data[s.B] A = dok_matrix(data[s.A]) # Save the dok_matrix. data[s.A] = A dims = dims_to_solver_dict(data[s.DIMS]) n = c.shape[0] model = cplex.Cplex() variables = [] # cpx_constrs will contain CpxConstr namedtuples (see above). cpx_constrs = [] vtype = [] if data[s.BOOL_IDX] or data[s.INT_IDX]: for i in range(n): # Set variable type. if i in data[s.BOOL_IDX]: vtype.append('B') elif i in data[s.INT_IDX]: vtype.append('I') else: vtype.append('C') else: # If we specify types (even with 'C'), then the problem will # be interpreted as a MIP. Leaving vtype as an empty list # here, will ensure that the problem type remains an LP. pass # Add the variables in a batch variables = list(model.variables.add( obj=[c[i] for i in range(n)], lb=[-cplex.infinity]*n, # default LB is 0 ub=[cplex.infinity]*n, types="".join(vtype), names=["x_%d" % i for i in range(n)])) # Add equality constraints cpx_constrs += [_CpxConstr(_LIN, x) for x in self.add_model_lin_constr( model, variables, range(dims[s.EQ_DIM]), 'E', A, b)] # Add inequality (<=) constraints leq_start = dims[s.EQ_DIM] leq_end = dims[s.EQ_DIM] + dims[s.LEQ_DIM] cpx_constrs += [_CpxConstr(_LIN, x) for x in self.add_model_lin_constr( model, variables, range(leq_start, leq_end), 'L', A, b)] # Add SOC constraints soc_start = leq_end for constr_len in dims[s.SOC_DIM]: soc_end = soc_start + constr_len soc_constr, new_leq, new_vars = self.add_model_soc_constr( model, variables, range(soc_start, soc_end), A, b) cpx_constrs.append(_CpxConstr(_QUAD, soc_constr)) cpx_constrs += [_CpxConstr(_LIN, x) for x in new_leq] variables += new_vars soc_start += constr_len # Set verbosity if not verbose: hide_solver_output(model) # For CVXPY, we set the qcpduals parameter here, but the user can # easily override it via the "cplex_params" solver option (see # set_parameters function). model.parameters.preprocessing.qcpduals.set( model.parameters.preprocessing.qcpduals.values.force) # Set parameters set_parameters(model, solver_opts) # Solve problem solution = {"model": model} try: start_time = model.get_time() model.solve() solution[s.SOLVE_TIME] = model.get_time() - start_time except Exception: pass return solution
def CvxpyLayer(problem, parameters, variables, gp=False): """Construct a CvxpyLayer Args: problem: The CVXPY problem; must be DPP. parameters: A list of CVXPY Parameters in the problem; the order of the Parameters determines the order in which parameter values must be supplied in the forward pass. Must include every parameter involved in problem. variables: A list of CVXPY Variables in the problem; the order of the Variables determines the order of the optimal variable values returned from the forward pass. gp: Whether to parse the problem using DGP (True or False). Returns: A callable that solves the problem. """ if gp: if not problem.is_dgp(dpp=True): raise ValueError('Problem must be DPP.') else: if not problem.is_dcp(dpp=True): raise ValueError('Problem must be DPP.') if not set(problem.parameters()) == set(parameters): raise ValueError("The layer's parameters must exactly match " "problem.parameters") if not set(variables).issubset(set(problem.variables())): raise ValueError("Argument variables must be a subset of " "problem.variables") if not isinstance(parameters, list) and \ not isinstance(parameters, tuple): raise ValueError("The layer's parameters must be provided as " "a list or tuple") if not isinstance(variables, list) and \ not isinstance(variables, tuple): raise ValueError("The layer's variables must be provided as " "a list or tuple") var_dict = {v.id for v in variables} # Construct compiler param_order = parameters if gp: for param in parameters: if param.value is None: raise ValueError("An initial value for each parameter is " "required when gp=True.") data, solving_chain, _ = problem.get_problem_data(solver=cp.SCS, gp=True) compiler = data[cp.settings.PARAM_PROB] dgp2dcp = solving_chain.get(cp.reductions.Dgp2Dcp) param_ids = [p.id for p in compiler.parameters] old_params_to_new_params = (dgp2dcp.canon_methods._parameters) else: data, _, _ = problem.get_problem_data(solver=cp.SCS) compiler = data[cp.settings.PARAM_PROB] param_ids = [p.id for p in param_order] dgp2dcp = None cone_dims = dims_to_solver_dict(data["dims"]) info = {} CvxpyLayerFn_p = core.Primitive("CvxpyLayerFn_" + str(hash(problem))) @partial(jax.custom_vjp, nondiff_argnums=(0, )) def CvxpyLayerFn(solver_args, *params): return CvxpyLayerFn_p.bind(solver_args, *params) def CvxpyLayerFn_impl(solver_args, *params): """Solve problem (or a batch of problems) corresponding to `params` Args: solver_args: a dict of optional arguments, to send to `diffcp`. Keys should be the names of keyword arguments. params: a sequence of JAX arrays; the n-th argument specifies the value for the n-th CVXPY Parameter. These arrays can be batched: if a array has 3 dimensions, then its first dimension is interpreted as the batch size. These arrays must all have the same dtype. Returns: a list of optimal variable values, one for each CVXPY Variable supplied to the constructor. """ if len(params) != len(param_ids): raise ValueError('An array must be provided for each CVXPY ' 'parameter; received %d arrays, expected %d' % (len(params), len(param_ids))) dtype, batch, batch_sizes, batch_size = batch_info(params, param_order) if gp: param_map = {} # construct a list of params for the DCP problem for param, value in zip(param_order, params): if param in old_params_to_new_params: new_id = old_params_to_new_params[param].id param_map[new_id] = jnp.log(value) else: new_id = param.id param_map[new_id] = value params_numpy = [np.array(param_map[pid]) for pid in param_ids] else: params_numpy = [np.array(p) for p in params] # canonicalize problem start = time.time() As, bs, cs, cone_dicts, shapes = [], [], [], [], [] for i in range(batch_size): params_numpy_i = [ p if sz == 0 else p[i] for p, sz in zip(params_numpy, batch_sizes) ] c, _, neg_A, b = compiler.apply_parameters(dict( zip(param_ids, params_numpy_i)), keep_zeros=True) A = -neg_A # cvxpy canonicalizes -A As.append(A) bs.append(b) cs.append(c) cone_dicts.append(cone_dims) shapes.append(A.shape) info['canon_time'] = time.time() - start info['shapes'] = shapes # compute solution and derivative function start = time.time() try: xs, _, _, _, DT_batch = diffcp.solve_and_derivative_batch( As, bs, cs, cone_dicts, **solver_args) info['DT_batch'] = DT_batch except diffcp.SolverError as e: print("Please consider re-formulating your problem so that " "it is always solvable or increasing the number of " "solver iterations.") raise e info['solve_time'] = time.time() - start # extract solutions and append along batch dimension start = time.time() sol = [[] for i in range(len(variables))] for i in range(batch_size): sltn_dict = compiler.split_solution(xs[i], active_vars=var_dict) for j, v in enumerate(variables): sol[j].append( jnp.expand_dims(jnp.array(sltn_dict[v.id], dtype=dtype), axis=0)) sol = [jnp.concatenate(s, axis=0) for s in sol] if not batch: sol = [jnp.squeeze(s, axis=0) for s in sol] if gp: sol = [jnp.exp(s) for s in sol] return tuple(sol) CvxpyLayerFn_p.def_impl(CvxpyLayerFn_impl) def CvxpyLayerFn_fwd_vjp(solver_args, *params): sol = CvxpyLayerFn(solver_args, *params) return sol, (params, sol) def CvxpyLayerFn_bwd_vjp(solver_args, res, dvars): params, sol = res dtype, batch, batch_sizes, batch_size = batch_info(params, param_order) # Use info here to retrieve this from the forward pass because # the residual in JAX's vjp doesn't allow non-JAX types to be # easily returned. This works when calling this serially, # but will break if this is called in parallel. shapes = info['shapes'] DT_batch = info['DT_batch'] if gp: # derivative of exponential recovery transformation dvars = [dvar * s for dvar, s in zip(dvars, sol)] dvars_numpy = [np.array(dvar) for dvar in dvars] if not batch: dvars_numpy = [np.expand_dims(dvar, 0) for dvar in dvars_numpy] # differentiate from cvxpy variables to cone problem data dxs, dys, dss = [], [], [] for i in range(batch_size): del_vars = {} for v, dv in zip(variables, [dv[i] for dv in dvars_numpy]): del_vars[v.id] = dv dxs.append(compiler.split_adjoint(del_vars)) dys.append(np.zeros(shapes[i][0])) dss.append(np.zeros(shapes[i][0])) dAs, dbs, dcs = DT_batch(dxs, dys, dss) # differentiate from cone problem data to cvxpy parameters start = time.time() grad = [[] for _ in range(len(param_ids))] for i in range(batch_size): del_param_dict = compiler.apply_param_jac(dcs[i], -dAs[i], dbs[i]) for j, pid in enumerate(param_ids): grad[j] += [ jnp.expand_dims(jnp.array(del_param_dict[pid], dtype=dtype), axis=0) ] grad = [jnp.concatenate(g, axis=0) for g in grad] if gp: # differentiate through the log transformation of params dcp_grad = grad grad = [] dparams = {pid: g for pid, g in zip(param_ids, dcp_grad)} for param, value in zip(param_order, params): v = 0.0 if param.id not in dparams else dparams[param.id] if param in old_params_to_new_params: dcp_param_id = old_params_to_new_params[param].id # new_param.value == log(param), apply chain rule v += (1.0 / value) * dparams[dcp_param_id] grad.append(v) info['dcanon_time'] = time.time() - start if not batch: grad = [jnp.squeeze(g, axis=0) for g in grad] else: for i, sz in enumerate(batch_sizes): if sz == 0: grad[i] = jnp.sum(grad[i], axis=0) return tuple(grad) CvxpyLayerFn.defvjp(CvxpyLayerFn_fwd_vjp, CvxpyLayerFn_bwd_vjp) # Default solver_args to an optional empty dict def f(*params, **kwargs): solver_args = kwargs.get('solver_args', {}) return CvxpyLayerFn(solver_args, *params) return f
def solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache=None): # Import basic modelling tools of cylp from cylp.cy import CyClpSimplex from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray c = data[s.C] b = data[s.B] A = data[s.A] dims = dims_to_solver_dict(data[s.DIMS]) n = c.shape[0] # Problem model = CyLPModel() # Variables x = model.addVariable('x', n) # Constraints # eq model += A[0:dims[s.EQ_DIM], :] * x == CyLPArray(b[0:dims[s.EQ_DIM]]) # leq leq_start = dims[s.EQ_DIM] leq_end = dims[s.EQ_DIM] + dims[s.LEQ_DIM] model += A[leq_start:leq_end, :] * x <= CyLPArray(b[leq_start:leq_end]) # Objective model.objective = c # Convert model model = CyClpSimplex(model) # No boolean vars available in Cbc -> model as int + restrict to [0,1] if data[s.BOOL_IDX] or data[s.INT_IDX]: # Mark integer- and binary-vars as "integer" model.setInteger(x[data[s.BOOL_IDX]]) model.setInteger(x[data[s.INT_IDX]]) # Restrict binary vars only idxs = data[s.BOOL_IDX] n_idxs = len(idxs) model.setColumnLowerSubset(np.arange(n_idxs, dtype=np.int32), np.array(idxs, np.int32), np.zeros(n_idxs)) model.setColumnUpperSubset(np.arange(n_idxs, dtype=np.int32), np.array(idxs, np.int32), np.ones(n_idxs)) # Verbosity Clp if not verbose: model.logLevel = 0 # Build model & solve status = None if data[s.BOOL_IDX] or data[s.INT_IDX]: # Convert model cbcModel = model.getCbcModel() for key, value in solver_opts.items(): setattr(cbcModel, key, value) # Verbosity Cbc if not verbose: cbcModel.logLevel = 0 # cylp: /cylp/cy/CyCbcModel.pyx#L134 # Call CbcMain. Solve the problem using the same parameters used by # CbcSolver. Equivalent to solving the model from the command line # using cbc's binary. cbcModel.solve() status = cbcModel.status else: # cylp: /cylp/cy/CyClpSimplex.pyx # Run CLP's initialSolve. It does a presolve and uses primal or dual # Simplex to solve a problem. status = model.initialSolve() solution = {} if data[s.BOOL_IDX] or data[s.INT_IDX]: solution["status"] = self.STATUS_MAP_MIP[status] solution["primal"] = cbcModel.primalVariableSolution['x'] solution["value"] = cbcModel.objectiveValue else: solution["status"] = self.STATUS_MAP_LP[status] solution["primal"] = model.primalVariableSolution['x'] solution["value"] = model.objectiveValue return solution