def get_lp_params(alternate_lp_params=False): 'get the lp params object' if not hasattr(get_lp_params, 'obj'): params = glpk.glp_smcp() glpk.glp_init_smcp(params) #params.msg_lev = glpk.GLP_MSG_ERR params.msg_lev = glpk.GLP_MSG_ERR params.meth = glpk.GLP_PRIMAL if Settings.GLPK_FIRST_PRIMAL else glpk.GLP_DUAL params.tm_lim = int(Settings.GLPK_TIMEOUT * 1000) params.out_dly = 2 * 1000 # start printing to terminal delay get_lp_params.obj = params # make alternative params params2 = glpk.glp_smcp() glpk.glp_init_smcp(params2) params2.meth = glpk.GLP_DUAL if Settings.GLPK_FIRST_PRIMAL else glpk.GLP_PRIMAL params2.msg_lev = glpk.GLP_MSG_ON params2.tm_lim = int(Settings.GLPK_TIMEOUT * 1000) params2.out_dly = 1 * 1000 # start printing to terminal status after 1 secs get_lp_params.alt_obj = params2 if alternate_lp_params: #glpk.glp_term_out(glpk.GLP_ON) rv = get_lp_params.alt_obj else: #glpk.glp_term_out(glpk.GLP_OFF) rv = get_lp_params.obj return rv
def __init__(self, quadratic_objective=False): if quadratic_objective: raise Exception('Quadratic objective not supported for GLPK') self._lp = glp.glp_create_prob() self._smcp = glp.glp_smcp() # simplex solver control parameters glp.glp_init_smcp(self._smcp) self._smcp.msg_lev = glp.GLP_MSG_ERR self.simplex_iteration_limit = 10000 self._n_vars = 0 self._n_eq_constraints = 0 self._flows = {} self._lb = {} self._ub = {} self._objective = {} self._materialCoeffs = defaultdict(list) self._materialIdxLookup = {} self._eqConstBuilt = False self._solved = False self.inf = np.inf self._lowerBoundDefault = 0 self._upperBoundDefault = self.inf
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 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 __init__(self, presolve="auto", verbosity=0, timeout=None, *args, **kwargs): super(Configuration, self).__init__(*args, **kwargs) self._smcp = glp_smcp() self._iocp = glp_iocp() glp_init_smcp(self._smcp) glp_init_iocp(self._iocp) self._max_time = min(self._smcp.tm_lim, self._iocp.tm_lim) self.presolve = presolve self.verbosity = verbosity self.timeout = timeout
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 _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 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 __init__(self, presolve="auto", verbosity=0, timeout=None, *args, **kwargs): super(Configuration, self).__init__(*args, **kwargs) self._smcp = glp_smcp() self._iocp = glp_iocp() glp_init_smcp(self._smcp) glp_init_iocp(self._iocp) self._max_time = min(self._smcp.tm_lim, self._iocp.tm_lim) self.presolve = presolve self.verbosity = verbosity self.timeout = timeout if "tolerances" in kwargs: for key, val in six.iteritems(kwargs["tolerances"]): try: setattr(self.tolerances, key, val) except AttributeError: pass
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 __init__(self, **kwargs): self._p = swiglpk.glp_create_prob() self._variables = {} self._constraints = {} self._do_presolve = True # Initialize simplex tolerances to default values parm = swiglpk.glp_smcp() swiglpk.glp_init_smcp(parm) self._feasibility_tolerance = parm.tol_bnd self._optimality_tolerance = parm.tol_dj # Initialize mip tolerance to default value parm = swiglpk.glp_iocp() swiglpk.glp_init_iocp(parm) self._integrality_tolerance = parm.tol_int self._result = None
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=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