def test_swiglpk(self): """Test the underlying GLPK lib and its SWIG interface based on the example from https://github.com/biosustain/swiglpk """ ia = glp.intArray(1 + 1000) ja = glp.intArray(1 + 1000) ar = glp.doubleArray(1 + 1000) lp = glp.glp_create_prob() smcp = glp.glp_smcp() glp.glp_init_smcp(smcp) smcp.msg_lev = glp.GLP_MSG_ALL # use GLP_MSG_ERR? glp.glp_set_prob_name(lp, "sample") glp.glp_set_obj_dir(lp, glp.GLP_MAX) glp.glp_add_rows(lp, 3) glp.glp_set_row_name(lp, 1, "p") glp.glp_set_row_bnds(lp, 1, glp.GLP_UP, 0.0, 100.0) glp.glp_set_row_name(lp, 2, "q") glp.glp_set_row_bnds(lp, 2, glp.GLP_UP, 0.0, 600.0) glp.glp_set_row_name(lp, 3, "r") glp.glp_set_row_bnds(lp, 3, glp.GLP_UP, 0.0, 300.0) glp.glp_add_cols(lp, 3) glp.glp_set_col_name(lp, 1, "x1") glp.glp_set_col_bnds(lp, 1, glp.GLP_LO, 0.0, 0.0) glp.glp_set_obj_coef(lp, 1, 10.0) glp.glp_set_col_name(lp, 2, "x2") glp.glp_set_col_bnds(lp, 2, glp.GLP_LO, 0.0, 0.0) glp.glp_set_obj_coef(lp, 2, 6.0) glp.glp_set_col_name(lp, 3, "x3") glp.glp_set_col_bnds(lp, 3, glp.GLP_LO, 0.0, 0.0) glp.glp_set_obj_coef(lp, 3, 4.0) ia[1] = 1; ja[1] = 1; ar[1] = 1.0 # a[1,1] = 1 ia[2] = 1; ja[2] = 2; ar[2] = 1.0 # a[1,2] = 1 ia[3] = 1; ja[3] = 3; ar[3] = 1.0 # a[1,3] = 1 ia[4] = 2; ja[4] = 1; ar[4] = 10.0 # a[2,1] = 10 ia[5] = 3; ja[5] = 1; ar[5] = 2.0 # a[3,1] = 2 ia[6] = 2; ja[6] = 2; ar[6] = 4.0 # a[2,2] = 4 ia[7] = 3; ja[7] = 2; ar[7] = 2.0 # a[3,2] = 2 ia[8] = 2; ja[8] = 3; ar[8] = 5.0 # a[2,3] = 5 ia[9] = 3; ja[9] = 3; ar[9] = 6.0 # a[3,3] = 6 glp.glp_load_matrix(lp, 9, ia, ja, ar) glp.glp_simplex(lp, smcp) Z = glp.glp_get_obj_val(lp) x1 = glp.glp_get_col_prim(lp, 1) x2 = glp.glp_get_col_prim(lp, 2) x3 = glp.glp_get_col_prim(lp, 3) self.assertAlmostEqual(Z, 733.3333, 4) self.assertAlmostEqual(x1, 33.3333, 4) self.assertAlmostEqual(x2, 66.6667, 4) self.assertAlmostEqual(x3, 0) glp.glp_delete_prob(lp)
def _get_primal(self): if self.type == "continuous": primal_from_solver = glp_get_col_prim(self.problem.problem, self.index) elif self.type in ("binary", "integer"): primal_from_solver = glp_mip_col_val(self.problem.problem, self.index) else: raise TypeError("Unknown variable type") return primal_from_solver
def _get_primal(self): if self.problem._glpk_is_mip(): primal_from_solver = glp_mip_col_val(self.problem.problem, self._index) else: primal_from_solver = glp_get_col_prim(self.problem.problem, self._index) return primal_from_solver
def _process_simplex_result(self, simplex_res, columns): '''process the result of a glp_simplex call returns None on UNSAT, otherwise the optimization result with the requested columns if columns is None, will return full result ''' rv = None if simplex_res == glpk.GLP_ENOPFS: # no primal feasible w/ presolver rv = None elif simplex_res != 0: # simplex failed, report the error raise RuntimeError("glp_simplex returned nonzero status ({}): {}".format( simplex_res, LpInstance.get_simplex_error_string(simplex_res))) else: status = glpk.glp_get_status(self.lp) if status == glpk.GLP_NOFEAS: # infeasible rv = None elif status == glpk.GLP_OPT: # optimal lp_cols = self.get_num_cols() if columns is None: rv = np.zeros(lp_cols) else: rv = np.zeros(len(columns)) # copy the output vars rv_len = len(rv) for i in range(rv_len): col = i if columns is None else columns[i] assert 0 <= col < lp_cols, "out of bounds column requested in LP solution: {}".format(col) rv[i] = glpk.glp_get_col_prim(self.lp, int(col + 1)) else: # neither infeasible nor optimal (for example, unbounded) codes = [glpk.GLP_OPT, glpk.GLP_FEAS, glpk.GLP_INFEAS, glpk.GLP_NOFEAS, glpk.GLP_UNBND, glpk.GLP_UNDEF] msgs = ["solution is optimal", "solution is feasible", "solution is infeasible", "problem has no feasible solution", "problem has unbounded solution", "solution is undefined"] if status == glpk.GLP_UNBND: ray = glpk.glp_get_unbnd_ray(self.lp) raise RuntimeError(f"LP had unbounded solution in minimize(). Unbounded ray was variable #{ray}") for code, message in zip(codes, msgs): if status == code: raise RuntimeError("LP status after solving in minimize() was '{}': {}".format(message, code)) raise RuntimeError("LP status after solving in minimize() was <Unknown>: {}".format(status)) return rv
def getFlowRates(self, flows): if isinstance(flows, basestring): flows = (flows, ) self._solve() return np.array([ glp.glp_get_col_prim(self._lp, 1 + self._flows[flow]) if flow in self._flows else None for flow in flows ])
def _get_primal(self): if self.type == "continuous": primal_from_solver = glp_get_col_prim(self.problem.problem, self._index) elif self.type in ("binary", "integer"): primal_from_solver = glp_mip_col_val(self.problem.problem, self._index) else: raise AssertionError("Unknown variable type") return primal_from_solver
def _process_simplex_result(self, simplex_res): '''process the result of a glp_simplex call returns None on UNSAT, otherwise the optimization result with the requested columns if columns is None, will return full result ''' rv = None if simplex_res != glpk.GLP_ENOPFS: # skip if no primal feasible w/ presolver if simplex_res != 0: # simplex failed, report the error raise RuntimeError( "glp_simplex returned nonzero status ({}): {}".format( simplex_res, LpInstance.get_simplex_error_string(simplex_res))) status = glpk.glp_get_status(self.lp) if status == glpk.GLP_NOFEAS: # infeasible rv = None elif status == glpk.GLP_OPT: # optimal lp_cols = self.get_num_cols() rv = np.zeros(lp_cols) for col in range(lp_cols): rv[col] = glpk.glp_get_col_prim(self.lp, int(1 + col)) else: # neither infeasible nor optimal (for example, unbounded) error_msg = "<Unknown Status>" codes = [ glpk.GLP_OPT, glpk.GLP_FEAS, glpk.GLP_INFEAS, glpk.GLP_NOFEAS, glpk.GLP_UNBND, glpk.GLP_UNDEF ] msgs = [ "solution is optimal", "solution is feasible", "solution is infeasible", "problem has no feasible solution", "problem has unbounded solution", "solution is undefined" ] for code, message in zip(codes, msgs): if status == code: error_msg = message break if status == glpk.GLP_UNBND: ray = glpk.glp_get_unbnd_ray(self.lp) error_msg += f"; unbounded ray was variable #{ray}" raise RuntimeError( f"LP status after minimize() was {status}: {error_msg}") return rv
def primal_values(self): primal_values = collections.OrderedDict() is_mip = self._glpk_is_mip() for index, variable in enumerate(self.variables): if is_mip: value = glp_mip_col_val(self.problem, index + 1) else: value = glp_get_col_prim(self.problem, index + 1) primal_values[variable.name] = variable._round_primal_to_bounds( value) return primal_values
def primal_values(self): primal_values = collections.OrderedDict() for index, variable in enumerate(self.variables): if variable.type == "continuous": value = glp_get_col_prim(self.problem, index + 1) elif variable.type in ["binary", "integer"]: value = glp_mip_col_val(self.problem, index + 1) else: raise AssertionError("Unknown variable type") primal_values[variable.name] = variable._round_primal_to_bounds(value) return primal_values
def primal_values(self): primal_values = collections.OrderedDict() for index, variable in enumerate(self.variables): if variable.type == "continuous": value = glp_get_col_prim(self.problem, index + 1) elif variable.type in ["binary", "integer"]: value = glp_mip_col_val(self.problem, index + 1) else: raise AssertionError("Unknown variable type") primal_values[variable.name] = variable._round_primal_to_bounds( value) return primal_values
def _linprog(c, A, b, obj): lp = glpk.glp_create_prob() glpk.glp_set_obj_dir(lp, obj) params = glpk.glp_smcp() glpk.glp_init_smcp(params) params.msg_lev = glpk.GLP_MSG_OFF #Only print error messages from GLPK num_rows = A.shape[0] num_cols = A.shape[1] mat_size = num_rows * num_cols glpk.glp_add_rows(lp, num_rows) for row_ind in range(num_rows): glpk.glp_set_row_bnds(lp, row_ind + 1, glpk.GLP_UP, 0.0, float(b[row_ind])) glpk.glp_add_cols(lp, num_cols) for col_ind in range(num_cols): glpk.glp_set_col_bnds(lp, col_ind + 1, glpk.GLP_FR, 0.0, 0.0) glpk.glp_set_obj_coef(lp, col_ind + 1, c[col_ind]) 'Swig arrays are used for feeding constraints in GLPK' ia, ja, ar = [], [], [] for i, j in product(range(num_rows), range(num_cols)): ia.append(i + 1) ja.append(j + 1) ar.append(float(A[i][j])) ia = glpk.as_intArray(ia) ja = glpk.as_intArray(ja) ar = glpk.as_doubleArray(ar) glpk.glp_load_matrix(lp, mat_size, ia, ja, ar) glpk.glp_simplex(lp, params) fun = glpk.glp_get_obj_val(lp) x = [ i for i in map(lambda x: glpk.glp_get_col_prim(lp, x + 1), range(num_cols)) ] glpk.glp_delete_prob(lp) glpk.glp_free_env() return LPSolution(x, fun)
def _get_value(self, variable): """Return value of variable in solution.""" return swiglpk.glp_get_col_prim(self._problem._p, self._problem._variables[variable])
def _solve(self): import swiglpk as glpk # An alias to the internal problem instance. p = self.int continuous = self.ext.is_continuous() # Select LP solver (Simplex or Interior Point Method). if continuous: if self.ext.options["lp_root_method"] == "interior": interior = True else: # Default to Simplex. interior = False simplex = not interior else: simplex = interior = False # Select appropriate options container. if simplex: options = glpk.glp_smcp() glpk.glp_init_smcp(options) elif interior: options = glpk.glp_iptcp() glpk.glp_init_iptcp(options) else: options = glpk.glp_iocp() glpk.glp_init_iocp(options) # Handle "verbose" option. verbosity = self.verbosity() if verbosity < 0: options.msg_lev = glpk.GLP_MSG_OFF elif verbosity == 0: options.msg_lev = glpk.GLP_MSG_ERR elif verbosity == 1: options.msg_lev = glpk.GLP_MSG_ON elif verbosity >= 2: options.msg_lev = glpk.GLP_MSG_ALL # Handle "tol" option. # Note that GLPK knows three different tolerances for Simplex but none # for the Interior Point Method, while PICOS states that "tol" is meant # only for the IPM. # XXX: The option is unsupported but does not default to None, so we # cannot warn the user. pass # Handle "maxit" option. if not simplex: self._handle_unsupported_option("maxit", "GLPK supports the 'maxit' option only with Simplex.") elif self.ext.options["maxit"] is not None: options.it_lim = int(self.ext.options["maxit"]) # Handle "lp_root_method" option. # Note that the PICOS option is explicitly also meant for the MIP # preprocessing step but GLPK does not support it in that scenario. if not continuous: self._handle_unsupported_option("lp_root_method", "GLPK supports the 'lp_root_method' option only for LPs.") elif self.ext.options["lp_root_method"] is not None: if self.ext.options["lp_root_method"] == "interior": # Handled above. pass elif self.ext.options["lp_root_method"] == "psimplex": assert simplex options.meth = glpk.GLP_PRIMAL elif self.ext.options["lp_root_method"] == "dsimplex": assert simplex options.meth = glpk.GLP_DUAL else: self._handle_bad_option_value("lp_root_method") # Handle "timelimit" option. if interior: self._handle_unsupported_option("timelimit", "GLPK does not support the 'timelimit' option with the " "Interior Point Method.") elif self.ext.options["timelimit"] is not None: options.tm_lim = 1000 * int(self.ext.options["timelimit"]) # Handle "gaplim" option. # Note that the option is silently ignored if passed alongside an LP; # while the solver does not allow us to pass the option in that case, it # is still technically a valid option as every LP is also a MIP. # TODO: Find out if "mip_gap" is really equivalent to "gaplim". if self.ext.options["gaplim"] is not None: if not continuous: options.mip_gap = float(self.ext.options["gaplim"]) # Handle unsupported options. self._handle_unsupported_options( "lp_node_method", "treememory", "nbsol", "hotstart") # TODO: Add GLPK-sepcific options. Candidates are: # For both Simplex and MIPs: # tol_*, out_* # For Simplex: # pricing, r_test, obj_* # For the Interior Point Method: # ord_alg # For MIPs: # *_tech, *_heur, ps_tm_lim, *_cuts, cb_size, binarize # Attempt to solve the problem. with self._header(): with self._stopwatch(): if simplex: # TODO: Support glp_exact. error = glpk.glp_simplex(p, options) elif interior: error = glpk.glp_interior(p, options) else: options.presolve = glpk.GLP_ON error = glpk.glp_intopt(p, options) # Conert error codes to text output. # Note that by printing it above the footer, this output is made to # look like it's coming from GLPK, which is technically wrong but # semantically correct. if error == glpk.GLP_EBADB: self._warn("Unable to start the search, because the initial " "basis specified in the problem object is invalid.") elif error == glpk.GLP_ESING: self._warn("Unable to start the search, because the basis " "matrix corresponding to the initial basis is singular " "within the working precision.") elif error == glpk.GLP_ECOND: self._warn("Unable to start the search, because the basis " "matrix corresponding to the initial basis is " "ill-conditioned.") elif error == glpk.GLP_EBOUND: self._warn("Unable to start the search, because some double-" "bounded variables have incorrect bounds.") elif error == glpk.GLP_EFAIL: self._warn("The search was prematurely terminated due to a " "solver failure.") elif error == glpk.GLP_EOBJLL: self._warn("The search was prematurely terminated, because the " "objective function being maximized has reached its lower " "limit and continues decreasing.") elif error == glpk.GLP_EOBJUL: self._warn("The search was prematurely terminated, because the " "objective function being minimized has reached its upper " "limit and continues increasing.") elif error == glpk.GLP_EITLIM: self._warn("The search was prematurely terminated, because the " "simplex iteration limit has been exceeded.") elif error == glpk.GLP_ETMLIM: self._warn("The search was prematurely terminated, because the " "time limit has been exceeded.") elif error == glpk.GLP_ENOPFS: self._verbose("The LP has no primal feasible solution.") elif error == glpk.GLP_ENODFS: self._verbose("The LP has no dual feasible solution.") elif error != 0: self._warn("GLPK error {:d}.".format(error)) # Retrieve primals. if self.ext.options["noprimals"]: primals = None else: primals = {} for variable in self.ext.variables.values(): value = [] for localIndex, picosIndex \ in enumerate(range(variable.startIndex, variable.endIndex)): glpkIndex = self._picos2glpk_variable_index(picosIndex) if simplex: localValue = glpk.glp_get_col_prim(p, glpkIndex); elif interior: localValue = glpk.glp_ipt_col_prim(p, glpkIndex); else: localValue = glpk.glp_mip_col_val(p, glpkIndex); value.append(localValue) primals[variable.name] = value # Retrieve duals. # XXX: Returns the duals as a flat cvx.matrix to be consistent with # other solvers. This feels incorrect when the constraint was given # as a proper two dimensional matrix. if self.ext.options["noduals"] or not continuous: duals = None else: duals = [] rowOffset = 1 for constraintNum, constraint in enumerate(self.ext.constraints): assert isinstance(constraint, AffineConstraint) numRows = len(constraint) values = [] for localConIndex in range(numRows): glpkConIndex = rowOffset + localConIndex if simplex: localValue = glpk.glp_get_row_dual(p, glpkConIndex); elif interior: localValue = glpk.glp_ipt_row_dual(p, glpkConIndex); else: assert False values.append(localValue) if constraint.is_decreasing(): duals.append(-cvxopt.matrix(values)) else: duals.append(cvxopt.matrix(values)) rowOffset += numRows if glpk.glp_get_obj_dir(p) == glpk.GLP_MIN: duals = [-d for d in duals] # Retrieve objective value. if simplex: objectiveValue = glpk.glp_get_obj_val(p) elif interior: objectiveValue = glpk.glp_ipt_obj_val(p) else: objectiveValue = glpk.glp_mip_obj_val(p) # Retrieve solution metadata. meta = {} if simplex: # Set common entry "status". status = glpk.glp_get_status(p) if status is glpk.GLP_OPT: meta["status"] = "optimal" elif status is glpk.GLP_FEAS: meta["status"] = "feasible" elif status in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS): meta["status"] = "infeasible" elif status is glpk.GLP_UNBND: meta["status"] = "unbounded" elif status is glpk.GLP_UNDEF: meta["status"] = "undefined" else: meta["status"] = "unknown" # Set GLPK-specific entry "primal_status". primalStatus = glpk.glp_get_prim_stat(p) if primalStatus is glpk.GLP_FEAS: meta["primal_status"] = "feasible" elif primalStatus in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS): meta["primal_status"] = "infeasible" elif primalStatus is glpk.GLP_UNDEF: meta["primal_status"] = "undefined" else: meta["primal_status"] = "unknown" # Set GLPK-specific entry "dual_status". dualStatus = glpk.glp_get_dual_stat(p) if dualStatus is glpk.GLP_FEAS: meta["dual_status"] = "feasible" elif dualStatus in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS): meta["dual_status"] = "infeasible" elif dualStatus is glpk.GLP_UNDEF: meta["dual_status"] = "undefined" else: meta["dual_status"] = "unknown" elif interior: # Set common entry "status". status = glpk.glp_ipt_status(p) if status is glpk.GLP_OPT: meta["status"] = "optimal" elif status in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS): meta["status"] = "infeasible" elif status is glpk.GLP_UNDEF: meta["status"] = "undefined" else: meta["status"] = "unknown" else: # Set common entry "status". status = glpk.glp_mip_status(p) if status is glpk.GLP_OPT: meta["status"] = "optimal" elif status is glpk.GLP_FEAS: meta["status"] = "feasible" elif status is glpk.GLP_NOFEAS: meta["status"] = "infeasible" elif status is glpk.GLP_UNDEF: meta["status"] = "undefined" else: meta["status"] = "unknown" return (primals, duals, objectiveValue, meta)
def _get_var_value(self, variable): self._check_valid() if variable not in self._problem._variables: raise ValueError('Unknown variable: {}'.format(variable)) return swiglpk.glp_get_col_prim(self._problem._p, self._problem._variables[variable])
def columnPrimalValue(self, j): """Return the primal value of the structural variable for j-th column.""" return glp.glp_get_col_prim(self._lp, j)
def _get_var_value(self, variable): self._check_valid() if variable not in self._problem._variables: raise ValueError('Unknown variable: {}'.format(variable)) return swiglpk.glp_get_col_prim( self._problem._p, self._problem._variables[variable])