def solve_cylp(self, verbose: bool = True, **kwargs): """ Inspired by the cvxpy cbc layer, ty :) """ from cylp.cy import CyClpSimplex from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray cons = self.combine_cons() n = int(self.next_var_idx - 1) # Number of variables. # Maximize c@x s.t. A@x <= b (variable bounds done by constraints) c = self.objective.rawdense().squeeze()[ 1:n + 1] # Objective coefficients, no constants. A = cons.raw()[:, 1:n + 1] # Constraint coefficients. b = cons[:, 0].rawdense().squeeze() * -1.0 # Constraint constants. model = CyLPModel() x = model.addVariable("x", n) # Variables model.objective = c # I hate this so much. Casting A to a matrix causes A.__mul__ to be # be called, which raises NotImplemented, so then x.__rmul__ is called # which gets the job done. Using np.matmul or np.dot doesn't # trigger x.__rmul__ # model.addConstraint(np.matrix(A) * x <= CyLPArray(b)) # Works model.addConstraint(x.__rmul__(A) <= CyLPArray(b)) model = CyClpSimplex(model) # Convert model model.logLevel = 0 if not verbose else model.logLevel is_integer_model = not np.isclose(self.int_vars.sum(), 0) if is_integer_model: model.setInteger(x[np.argwhere(self.int_vars)]) cbcModel = model.getCbcModel() cbcModel.solve() status = cbcModel.status solmodel = cbcModel else: status = model.initialSolve() solmodel = model sol_x = np.hstack( # Pad back to original shape ( [1.0], # Special constant 1.0 variable. solmodel.primalVariableSolution["x"], # Actual solution np.zeros(self.max_vars - n - 1), # Unused vars )) solution = { "status": status, "primal": sol_x, "value": solmodel.objectiveValue } return solution, solution["primal"]
def solve(self, objective, constraints, cached_data, warm_start, verbose, solver_opts): """Returns the result of the call to the solver. Parameters ---------- objective : LinOp The canonicalized objective. constraints : list The list of canonicalized cosntraints. cached_data : dict A map of solver name to cached problem data. 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 basic modelling tools of cylp from cylp.cy import CyClpSimplex from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray # Get problem data data = self.get_problem_data(objective, constraints, cached_data) c = data[s.C] b = data[s.B] A = data[s.A] dims = data[s.DIMS] data[s.BOOL_IDX] = solver_opts[s.BOOL_IDX] data[s.INT_IDX] = solver_opts[s.INT_IDX] 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 self.is_mip(data): # 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 self.is_mip(data): # Convert model cbcModel = model.getCbcModel() # 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() results_dict = {} results_dict["status"] = status if self.is_mip(data): results_dict["x"] = cbcModel.primalVariableSolution['x'] results_dict["obj_value"] = cbcModel.objectiveValue else: results_dict["x"] = model.primalVariableSolution['x'] results_dict["obj_value"] = model.objectiveValue return self.format_results(results_dict, data, cached_data)
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