def _solve(self): if self._solved: return if self._maximize: glp.glp_set_obj_dir(self._lp, glp.GLP_MAX) else: glp.glp_set_obj_dir(self._lp, glp.GLP_MIN) result = glp.glp_simplex(self._lp, self._smcp) # If no solution within iteration limit, switch to dual method to find solution if result == glp.GLP_EITLIM: print( 'Warning: could not find solution with primal method, switching to dual' ) self.simplex_method = SimplexMethod.DUALP result = glp.glp_simplex(self._lp, self._smcp) self.simplex_method = SimplexMethod.PRIMAL if result != 0: raise RuntimeError( SIMPLEX_RETURN_CODE_TO_STRING.get( result, "GLP_?: UNKNOWN SOLVER RETURN VALUE")) if self.status_code != glp.GLP_OPT: raise RuntimeError(self.status_string) self._solved = True
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 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 _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 solve(self, sense=None): """Solve problem.""" if sense is not None: self.set_objective_sense(sense) parm = swiglpk.glp_smcp() swiglpk.glp_init_smcp(parm) parm.presolve = swiglpk.GLP_ON logger.debug('Solving using glp_simplex()') r = swiglpk.glp_simplex(self._p, parm) if r in (swiglpk.GLP_ENOPFS, swiglpk.GLP_ENODFS): self._result = Result(self, r) return self._result elif r != 0: raise GLPKError('glp_simplex: {}'.format(r)) if swiglpk.glp_get_num_int(self._p) == 0: self._result = Result(self) else: # The intopt MILP solver need an optimal solution to the LP # relaxation. Therefore, glp_simplex has to run before glp_intopt # for MILP problems. logger.debug('Solving using glp_intopt()') r = swiglpk.glp_intopt(self._p, None) if r != 0: raise GLPKError('glp_intopt: {}'.format(r)) self._result = MIPResult(self) return self._result
def solve(self, sense=None): """Solve problem.""" if sense is not None: self.set_objective_sense(sense) parm = swiglpk.glp_smcp() swiglpk.glp_init_smcp(parm) if self._do_presolve: parm.presolve = swiglpk.GLP_ON self._do_presolve = False else: parm.presolve = swiglpk.GLP_OFF logger.debug('Solving using glp_simplex()') r = swiglpk.glp_simplex(self._p, parm) if r in (swiglpk.GLP_ENOPFS, swiglpk.GLP_ENODFS): self._result = Result(self, r) return self._result elif r != 0: raise GLPKError('glp_simplex: {}'.format(r)) if swiglpk.glp_get_num_int(self._p) == 0: self._result = Result(self) else: # The intopt MILP solver need an optimal solution to the LP # relaxation. Therefore, glp_simplex has to run before glp_intopt # for MILP problems. logger.debug('Solving using glp_intopt()') r = swiglpk.glp_intopt(self._p, None) if r != 0: raise GLPKError('glp_intopt: {}'.format(r)) self._result = MIPResult(self) return self._result
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 _solve(self): if self._solved: return if self._maximize: glp.glp_set_obj_dir(self._lp, glp.GLP_MAX) else: glp.glp_set_obj_dir(self._lp, glp.GLP_MIN) result = glp.glp_simplex(self._lp, self._smcp) # Adjust solver options for robustness ## If no solution within iteration limit, switch to dual method to find solution if result == glp.GLP_EITLIM: print( 'Warning: could not find solution with primal method, switching to dual' ) self.simplex_method = SimplexMethod.DUALP result = glp.glp_simplex(self._lp, self._smcp) self.simplex_method = SimplexMethod.PRIMAL ## If still no solution, presolve the problem to reduce complexity ## (also prevents warm start) if self.status_code != glp.GLP_OPT: print( 'Warning: could not find solution with dual method, using primal with presolve' ) self._smcp.presolve = glp.GLP_ON result = glp.glp_simplex(self._lp, self._smcp) self._smcp.presolve = glp.GLP_OFF if result != 0: raise RuntimeError( SIMPLEX_RETURN_CODE_TO_STRING.get( result, "GLP_?: UNKNOWN SOLVER RETURN VALUE")) if self.status_code != glp.GLP_OPT: raise RuntimeError(self.status_string) self._solved = True
def solve_linprog(problem: SwigPyObject): level = level_for_parm() parm = lp.glp_smcp() lp.glp_init_smcp(parm) parm.msg_lev = level check_lp(problem, lp.glp_simplex(problem, parm)) log_soln(problem, 'sol') parm = lp.glp_iocp() lp.glp_init_iocp(parm) parm.msg_lev = level check_lp(problem, lp.glp_intopt(problem, parm)) log_soln(problem, 'mip')
def minimize(self, direction_vec=None, columns=None, fail_on_unsat=True): '''minimize the lp, returning a list of assigments to each of the variables if direction_vec is not None, this will first assign the optimization direction (note: relative to cur_vars) if columns is not None, will only return the requested columns (default: all columns) if fail_on_unsat is True and the LP is infeasible, an UnsatError is raised unsat (sometimes happens in GLPK due to likely bug, see space station model) returns None if UNSAT, otherwise the optimization result. Use columns=[] if you're not interested in the result ''' if direction_vec is not None: self.set_minimize_direction(direction_vec) Timers.tic('setup') # setup lp params params = glpk.glp_smcp() glpk.glp_init_smcp(params) params.meth = glpk.GLP_DUALP # use dual simplex since we're reoptimizing often params.msg_lev = glpk.GLP_MSG_OFF params.tm_lim = 1000 # 1000 ms time limit Timers.toc('setup') Timers.tic('glp_simplex') simplex_res = glpk.glp_simplex(self.lp, params) Timers.toc('glp_simplex') # process simplex result Timers.tic('process_simplex_result') rv = self._process_simplex_result(simplex_res, columns) Timers.toc('process_simplex_result') if rv is None and fail_on_unsat: print( "Note: minimize failed with fail_on_unsat was true, resetting and retrying..." ) glpk.glp_cpx_basis(self.lp) # resets the initial basis rv = self.minimize(direction_vec, columns, False) #LpInstance.print_verbose("Note: LP was infeasible, but then feasible after resetting statuses") if rv is None and fail_on_unsat: raise UnsatError( "minimize returned UNSAT and fail_on_unsafe was True") return rv
def solve_unchecked(self, sense=None): """Solve problem and return result. The user must manually check the status of the result to determine whether an optimal solution was found. A :class:`SolverError` may still be raised if the underlying solver raises an exception. """ if sense is not None: self.set_objective_sense(sense) parm = swiglpk.glp_smcp() swiglpk.glp_init_smcp(parm) if self._do_presolve: parm.presolve = swiglpk.GLP_ON self._do_presolve = False else: parm.presolve = swiglpk.GLP_OFF parm.tol_bnd = self._feasibility_tolerance parm.tol_dj = self._optimality_tolerance logger.debug('Solving using glp_simplex()') r = swiglpk.glp_simplex(self._p, parm) if r in (swiglpk.GLP_ENOPFS, swiglpk.GLP_ENODFS): self._result = Result(self, r) return self._result elif r != 0: raise GLPKError('glp_simplex: {}'.format(r)) if swiglpk.glp_get_num_int(self._p) == 0: self._result = Result(self) else: # The intopt MILP solver needs an optimal solution to the LP # relaxation. Therefore, glp_simplex has to run before glp_intopt # for MILP problems. logger.debug('Solving using glp_intopt()') parm = swiglpk.glp_iocp() swiglpk.glp_init_iocp(parm) parm.tol_int = self._integrality_tolerance r = swiglpk.glp_intopt(self._p, parm) if r != 0: raise GLPKError('glp_intopt: {}'.format(r)) self._result = MIPResult(self) return self._result
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 minimize(self, direction_vec, fail_on_unsat=True): '''minimize the lp, returning a list of assigments to each of the variables if direction_vec is not None, this will first assign the optimization direction returns None if UNSAT, otherwise the optimization result. ''' assert not isinstance( self.lp, tuple), "self.lp was tuple. Did you call lpi.deserialize()?" if direction_vec is None: direction_vec = [0] * self.get_num_cols() self.set_minimize_direction(direction_vec) if Settings.GLPK_RESET_BEFORE_MINIMIZE: self.reset_basis() start = time.perf_counter() simplex_res = glpk.glp_simplex(self.lp, get_lp_params()) if simplex_res != 0: # solver failure (possibly timeout) r = self.get_num_rows() c = self.get_num_cols() diff = time.perf_counter() - start print(f"GLPK timed out / failed ({simplex_res}) after {round(diff, 3)} sec with primary " + \ f"settings with {r} rows and {c} cols") print("Retrying with reset") self.reset_basis() start = time.perf_counter() simplex_res = glpk.glp_simplex(self.lp, get_lp_params()) diff = time.perf_counter() - start print(f"result with reset ({simplex_res}) {round(diff, 3)} sec") print("Retrying with reset + alternate GLPK settings") # retry with alternate params params = get_lp_params(alternate_lp_params=True) self.reset_basis() start = time.perf_counter() simplex_res = glpk.glp_simplex(self.lp, params) diff = time.perf_counter() - start print( f"result with reset & alternate settings ({simplex_res}) {round(diff, 3)} sec" ) rv = self._process_simplex_result(simplex_res) if rv is None and fail_on_unsat: # extra logic to try harder if fail_on_unsafe is True # glpk can sometimes be cajoled into providing a solution print( "Note: minimize failed with fail_on_unsat was true, trying to reset basis..." ) self.reset_basis() rv = self.minimize(direction_vec, fail_on_unsat=False) if rv is None: print( "still unsat after reset basis, trying no-dir optimization" ) self.reset_basis() result_nodir = self.minimize(None, fail_on_unsat=False) # lp became infeasible when I picked an optimization direction if result_nodir is not None: print("Using result from no-direction optimization") rv = result_nodir else: print("Error: No-dir result was also infeasible!") else: print( "Using result after reset basis (soltion was now feasible)" ) if rv is None and fail_on_unsat: raise UnsatError( "minimize returned UNSAT and fail_on_unsat was True") return rv
def minimize(self, direction_vec=None, columns=None, fail_on_unsat=True, print_on=False): '''minimize the lp, returning a list of assigments to each of the variables if direction_vec is not None, this will first assign the optimization direction (note: relative to cur_vars) if columns is not None, will only return the requested columns (default: all columns) if fail_on_unsat is True and the LP is infeasible, an UnsatError is raised unsat (sometimes happens in GLPK due to likely bug, see space station model) returns None if UNSAT, otherwise the optimization result. Use columns=[] if you're not interested in the result ''' Timers.tic('minimize') if direction_vec is not None: self.set_minimize_direction(direction_vec) # setup lp params params = glpk.glp_smcp() glpk.glp_init_smcp(params) params.meth = glpk.GLP_DUALP # use dual simplex since we're reoptimizing often params.msg_lev = glpk.GLP_MSG_ALL if print_on else glpk.GLP_MSG_OFF params.tm_lim = 1000 # 1000 ms time limit Timers.tic('glp_simplex') simplex_res = glpk.glp_simplex(self.lp, params) Timers.toc('glp_simplex') if simplex_res != 0: # this can happen when you replace constraints after already solving once LpInstance.print_normal('Note: glp_simplex() failed ({}: {}), resetting and retrying'.format( simplex_res, LpInstance.get_simplex_error_string(simplex_res))) if simplex_res == glpk.GLP_ESING: # singular matrix, can happen after replacing constraints glpk.glp_std_basis(self.lp) simplex_res = glpk.glp_simplex(self.lp, params) if simplex_res != 0: glpk.glp_cpx_basis(self.lp) # resets the initial basis params.msg_lev = glpk.GLP_MSG_ON # turn printing on params.tm_lim = 30 * 1000 # second try: 30 second time limit simplex_res = glpk.glp_simplex(self.lp, params) # process simplex result rv = self._process_simplex_result(simplex_res, columns) Timers.toc('minimize') if rv is None and fail_on_unsat: LpInstance.print_normal("Note: minimize failed with fail_on_unsat was true, resetting and retrying...") glpk.glp_cpx_basis(self.lp) # resets the initial basis rv = self.minimize(direction_vec, columns, False, print_on=True) if rv is not None: LpInstance.print_verbose("Note: LP was infeasible, but then feasible after resetting statuses") if rv is None and fail_on_unsat: raise UnsatError("minimize returned UNSAT and fail_on_unsafe was True") return rv