def status(self): """Return string indicating the error encountered on failure.""" self._check_valid() if self._ret_val == swiglpk.GLP_ENOPFS: return 'No primal feasible solution' elif self._ret_val == swiglpk.GLP_ENODFS: return 'No dual feasible solution' return str(swiglpk.glp_get_status(self._problem._p))
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 _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 _run_glp_simplex(self): return_value = glp_simplex(self.problem, self.configuration._smcp) glpk_status = glp_get_status(self.problem) if return_value == 0: status = _GLPK_STATUS_TO_STATUS[glpk_status] elif return_value == GLP_ETMLIM: status = interface.TIME_LIMIT else: status = _GLPK_STATUS_TO_STATUS[glpk_status] if status == interface.UNDEFINED: log.debug("Status undefined. GLPK status code returned by glp_simplex was %d" % return_value) return status
def check_objval(glpk_problem, model_objval): """ Check that ... """ smcp = glp_smcp() smcp.presolve = True glp_simplex(glpk_problem, None) status = glp_get_status(glpk_problem) if status == GLP_OPT: glpk_problem_objval = glp_get_obj_val(glpk_problem) else: glpk_problem_objval = None nose.tools.assert_almost_equal(glpk_problem_objval, model_objval, places=4)
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 unbounded(self): """Whether solution is unbounded""" self._check_valid() if self._ret_val != 0: return self._ret_val == swiglpk.GLP_ENODFS return swiglpk.glp_get_status(self._problem._p) == swiglpk.GLP_UNBND
def success(self): """Return boolean indicating whether a solution was found.""" self._check_valid() if self._ret_val != 0: return False return swiglpk.glp_get_status(self._problem._p) == swiglpk.GLP_OPT
def status_code(self): """The generic status code for the current basic solution.""" return glp.glp_get_status(self._lp)