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 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() if model.Status == 4 and solver_opts.get('reoptimize', False): # INF_OR_UNBD. Solve again to get a definitive answer. 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: bool, verbose: bool, 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 reoptimize = solver_opts.pop('reoptimize', False) 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 ambiguous_status = get_status(model) == s.INFEASIBLE_OR_UNBOUNDED if ambiguous_status and reoptimize: model.parameters.preprocessing.presolve.set(0) start_time = model.get_time() model.solve() solution[s.SOLVE_TIME] += model.get_time() - start_time except Exception: pass return solution
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: bool, verbose: bool, solver_opts, solver_cache=None): # Import basic modelling tools of cylp from cylp.cy import CyClpSimplex from cylp.py.modeling.CyLPModel import CyLPArray, CyLPModel 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