def _postsolve(self): # the only suffixes that we extract from GUROBI are # constraint duals, constraint slacks, and variable # reduced-costs. scan through the solver suffix list # and throw an exception if the user has specified # any others. extract_duals = False extract_slacks = False extract_reduced_costs = False for suffix in self._suffixes: flag = False if re.match(suffix, "dual"): extract_duals = True flag = True if re.match(suffix, "slack"): extract_slacks = True flag = True if re.match(suffix, "rc"): extract_reduced_costs = True flag = True if not flag: raise RuntimeError( "***The gurobi_direct solver plugin cannot extract solution suffix=" + suffix) gprob = self._solver_model grb = self._gurobipy.GRB status = gprob.Status if gprob.getAttr(self._gurobipy.GRB.Attr.IsMIP): if extract_reduced_costs: logger.warning("Cannot get reduced costs for MIP.") if extract_duals: logger.warning("Cannot get duals for MIP.") extract_reduced_costs = False extract_duals = False self.results = SolverResults() soln = Solution() self.results.solver.name = self._name self.results.solver.wallclock_time = gprob.Runtime if status == grb.LOADED: # problem is loaded, but no solution self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Model is loaded, but no solution information is available." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.unknown elif status == grb.OPTIMAL: # optimal self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \ "and an optimal solution is available." self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif status == grb.INFEASIBLE: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be infeasible" self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif status == grb.INF_OR_UNBD: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Problem proven to be infeasible or unbounded." self.results.solver.termination_condition = TerminationCondition.infeasibleOrUnbounded soln.status = SolutionStatus.unsure elif status == grb.UNBOUNDED: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be unbounded." self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif status == grb.CUTOFF: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimal objective for model was proven to be worse than the " \ "value specified in the Cutoff parameter. No solution " \ "information is available." self.results.solver.termination_condition = TerminationCondition.minFunctionValue soln.status = SolutionStatus.unknown elif status == grb.ITERATION_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the total number of simplex " \ "iterations performed exceeded the value specified in the " \ "IterationLimit parameter." self.results.solver.termination_condition = TerminationCondition.maxIterations soln.status = SolutionStatus.stoppedByLimit elif status == grb.NODE_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the total number of " \ "branch-and-cut nodes explored exceeded the value specified " \ "in the NodeLimit parameter" self.results.solver.termination_condition = TerminationCondition.maxEvaluations soln.status = SolutionStatus.stoppedByLimit elif status == grb.TIME_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the time expended exceeded " \ "the value specified in the TimeLimit parameter." self.results.solver.termination_condition = TerminationCondition.maxTimeLimit soln.status = SolutionStatus.stoppedByLimit elif status == grb.SOLUTION_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the number of solutions found " \ "reached the value specified in the SolutionLimit parameter." self.results.solver.termination_condition = TerminationCondition.unknown soln.status = SolutionStatus.stoppedByLimit elif status == grb.INTERRUPTED: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization was terminated by the user." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif status == grb.NUMERIC: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = "Optimization was terminated due to unrecoverable numerical " \ "difficulties." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif status == grb.SUBOPTIMAL: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Unable to satisfy optimality tolerances; a sub-optimal " \ "solution is available." self.results.solver.termination_condition = TerminationCondition.other soln.status = SolutionStatus.feasible else: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = "Unknown return code from GUROBI." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error self.results.problem.name = gprob.ModelName if gprob.ModelSense == 1: self.results.problem.sense = pyomo.core.kernel.minimize elif gprob.ModelSense == -1: self.results.problem.sense = pyomo.core.kernel.maximize else: raise RuntimeError( 'Unrecognized gurobi objective sense: {0}'.format( gprob.ModelSense)) self.results.problem.upper_bound = None self.results.problem.lower_bound = None if (gprob.NumBinVars + gprob.NumIntVars) == 0: try: self.results.problem.upper_bound = gprob.ObjVal self.results.problem.lower_bound = gprob.ObjVal except (self._gurobipy.GurobiError, AttributeError): pass elif gprob.ModelSense == 1: # minimizing try: self.results.problem.upper_bound = gprob.ObjVal except (self._gurobipy.GurobiError, AttributeError): pass try: self.results.problem.lower_bound = gprob.ObjBound except (self._gurobipy.GurobiError, AttributeError): pass elif gprob.ModelSense == -1: # maximizing try: self.results.problem.upper_bound = gprob.ObjBound except (self._gurobipy.GurobiError, AttributeError): pass try: self.results.problem.lower_bound = gprob.ObjVal except (self._gurobipy.GurobiError, AttributeError): pass else: raise RuntimeError( 'Unrecognized gurobi objective sense: {0}'.format( gprob.ModelSense)) try: soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound except TypeError: soln.gap = None self.results.problem.number_of_constraints = gprob.NumConstrs + gprob.NumQConstrs + gprob.NumSOS self.results.problem.number_of_nonzeros = gprob.NumNZs self.results.problem.number_of_variables = gprob.NumVars self.results.problem.number_of_binary_variables = gprob.NumBinVars self.results.problem.number_of_integer_variables = gprob.NumIntVars self.results.problem.number_of_continuous_variables = gprob.NumVars - gprob.NumIntVars - gprob.NumBinVars self.results.problem.number_of_objectives = 1 self.results.problem.number_of_solutions = gprob.SolCount # if a solve was stopped by a limit, we still need to check to # see if there is a solution available - this may not always # be the case, both in LP and MIP contexts. if self._save_results: """ This code in this if statement is only needed for backwards compatability. It is more efficient to set _save_results to False and use load_vars, load_duals, etc. """ if gprob.SolCount > 0: soln_variables = soln.variable soln_constraints = soln.constraint gurobi_vars = self._solver_model.getVars() gurobi_vars = list( set(gurobi_vars).intersection( set(self._pyomo_var_to_solver_var_map.values()))) var_vals = self._solver_model.getAttr("X", gurobi_vars) names = self._solver_model.getAttr("VarName", gurobi_vars) for gurobi_var, val, name in zip(gurobi_vars, var_vals, names): pyomo_var = self._solver_var_to_pyomo_var_map[gurobi_var] if self._referenced_variables[pyomo_var] > 0: pyomo_var.stale = False soln_variables[name] = {"Value": val} if extract_reduced_costs: vals = self._solver_model.getAttr("Rc", gurobi_vars) for gurobi_var, val, name in zip(gurobi_vars, vals, names): pyomo_var = self._solver_var_to_pyomo_var_map[ gurobi_var] if self._referenced_variables[pyomo_var] > 0: soln_variables[name]["Rc"] = val if extract_duals or extract_slacks: gurobi_cons = self._solver_model.getConstrs() con_names = self._solver_model.getAttr( "ConstrName", gurobi_cons) for name in con_names: soln_constraints[name] = {} if self._version_major >= 5: gurobi_q_cons = self._solver_model.getQConstrs() q_con_names = self._solver_model.getAttr( "QCName", gurobi_q_cons) for name in q_con_names: soln_constraints[name] = {} if extract_duals: vals = self._solver_model.getAttr("Pi", gurobi_cons) for val, name in zip(vals, con_names): soln_constraints[name]["Dual"] = val if self._version_major >= 5: q_vals = self._solver_model.getAttr( "QCPi", gurobi_q_cons) for val, name in zip(q_vals, q_con_names): soln_constraints[name]["Dual"] = val if extract_slacks: gurobi_range_con_vars = set( self._solver_model.getVars()) - set( self._pyomo_var_to_solver_var_map.values()) vals = self._solver_model.getAttr("Slack", gurobi_cons) for gurobi_con, val, name in zip(gurobi_cons, vals, con_names): pyomo_con = self._solver_con_to_pyomo_con_map[ gurobi_con] if pyomo_con in self._range_constraints: lin_expr = self._solver_model.getRow(gurobi_con) for i in reversed(range(lin_expr.size())): v = lin_expr.getVar(i) if v in gurobi_range_con_vars: Us_ = v.X Ls_ = v.UB - v.X if Us_ > Ls_: soln_constraints[name]["Slack"] = Us_ else: soln_constraints[name]["Slack"] = -Ls_ break else: soln_constraints[name]["Slack"] = val if self._version_major >= 5: q_vals = self._solver_model.getAttr( "QCSlack", gurobi_q_cons) for val, name in zip(q_vals, q_con_names): soln_constraints[name]["Slack"] = val elif self._load_solutions: if gprob.SolCount > 0: self._load_vars() if extract_reduced_costs: self._load_rc() if extract_duals: self._load_duals() if extract_slacks: self._load_slacks() self.results.solution.insert(soln) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. pyutilib.services.TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def _postsolve(self): extract_duals = False extract_slacks = False extract_reduced_costs = False for suffix in self._suffixes: flag = False if re.match(suffix, "dual"): extract_duals = True flag = True if re.match(suffix, "slack"): extract_slacks = True flag = True if re.match(suffix, "rc"): extract_reduced_costs = True flag = True if not flag: raise RuntimeError( "***MOSEK solver plugin cannot extract solution suffix = " + suffix) msk_task = self._solver_model msk = self._mosek itr_soltypes = [ msk.problemtype.qo, msk.problemtype.qcqo, msk.problemtype.conic ] if (msk_task.getnumintvar() >= 1): self._whichsol = msk.soltype.itg if extract_reduced_costs: logger.warning("Cannot get reduced costs for MIP.") if extract_duals: logger.warning("Cannot get duals for MIP.") extract_reduced_costs = False extract_duals = False elif (msk_task.getprobtype() in itr_soltypes): self._whichsol = msk.soltype.itr whichsol = self._whichsol sol_status = msk_task.getsolsta(whichsol) pro_status = msk_task.getprosta(whichsol) self.results = SolverResults() soln = Solution() self.results.solver.name = self._name self.results.solver.wallclock_time = msk_task.getdouinf( msk.dinfitem.optimizer_time) SOLSTA_MAP = { msk.solsta.unknown: 'unknown', msk.solsta.optimal: 'optimal', msk.solsta.prim_and_dual_feas: 'pd_feas', msk.solsta.prim_feas: 'p_feas', msk.solsta.dual_feas: 'd_feas', msk.solsta.prim_infeas_cer: 'p_infeas', msk.solsta.dual_infeas_cer: 'd_infeas', msk.solsta.prim_illposed_cer: 'p_illposed', msk.solsta.dual_illposed_cer: 'd_illposed', msk.solsta.integer_optimal: 'optimal' } PROSTA_MAP = { msk.prosta.unknown: 'unknown', msk.prosta.prim_and_dual_feas: 'pd_feas', msk.prosta.prim_feas: 'p_feas', msk.prosta.dual_feas: 'd_feas', msk.prosta.prim_infeas: 'p_infeas', msk.prosta.dual_infeas: 'd_infeas', msk.prosta.prim_and_dual_infeas: 'pd_infeas', msk.prosta.ill_posed: 'illposed', msk.prosta.prim_infeas_or_unbounded: 'p_inf_unb' } if self._version[0] < 9: SOLSTA_OLD = { msk.solsta.near_optimal: 'optimal', msk.solsta.near_integer_optimal: 'optimal', msk.solsta.near_prim_feas: 'p_feas', msk.solsta.near_dual_feas: 'd_feas', msk.solsta.near_prim_and_dual_feas: 'pd_feas', msk.solsta.near_prim_infeas_cer: 'p_infeas', msk.solsta.near_dual_infeas_cer: 'd_infeas' } PROSTA_OLD = { msk.prosta.near_prim_and_dual_feas: 'pd_feas', msk.prosta.near_prim_feas: 'p_feas', msk.prosta.near_dual_feas: 'd_feas' } SOLSTA_MAP.update(SOLSTA_OLD) PROSTA_MAP.update(PROSTA_OLD) if self._termcode == msk.rescode.ok: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "" elif self._termcode == msk.rescode.trm_max_iterations: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Optimizer terminated at the maximum number of iterations." self.results.solver.termination_condition = TerminationCondition.maxIterations soln.status = SolutionStatus.stoppedByLimit elif self._termcode == msk.rescode.trm_max_time: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Optimizer terminated at the maximum amount of time." self.results.solver.termination_condition = TerminationCondition.maxTimeLimit soln.status = SolutionStatus.stoppedByLimit elif self._termcode == msk.rescode.trm_user_callback: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimizer terminated due to the return of the "\ "user-defined callback function." self.results.solver.termination_condition = TerminationCondition.userInterrupt soln.status = SolutionStatus.unknown elif self._termcode in [ msk.rescode.trm_mio_num_relaxs, msk.rescode.trm_mio_num_branches, msk.rescode.trm_num_max_num_int_solutions ]: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "The mixed-integer optimizer terminated as the maximum number "\ "of relaxations/branches/feasible solutions was reached." self.results.solver.termination_condition = TerminationCondition.maxEvaluations soln.status = SolutionStatus.stoppedByLimit else: self.results.solver.termination_message = " Optimization terminated with {} response code." \ "Check MOSEK response code documentation for more information.".format( self._termcode) self.results.solver.termination_condition = TerminationCondition.unknown if SOLSTA_MAP[sol_status] == 'unknown': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " The solution status is unknown." self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.unknown soln.status = SolutionStatus.unknown if PROSTA_MAP[pro_status] == 'd_infeas': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " Problem is dual infeasible" self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif PROSTA_MAP[pro_status] == 'p_infeas': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " Problem is primal infeasible." self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif PROSTA_MAP[pro_status] == 'pd_infeas': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " Problem is primal and dual infeasible." self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif PROSTA_MAP[pro_status] == 'p_inf_unb': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " Problem is either primal infeasible or unbounded."\ " This may happen for MIPs." self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.infeasibleOrUnbounded soln.status = SolutionStatus.unsure if SOLSTA_MAP[sol_status] == 'optimal': self.results.solver.status = SolverStatus.ok self.results.solver.termination_message += " Model was solved to optimality and an optimal solution is available." self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif SOLSTA_MAP[sol_status] == 'pd_feas': self.results.solver.status = SolverStatus.ok self.results.solver.termination_message += " The solution is both primal and dual feasible." self.results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif SOLSTA_MAP[sol_status] == 'p_feas': self.results.solver.status = SolverStatus.ok self.results.solver.termination_message += " The solution is primal feasible." self.results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif SOLSTA_MAP[sol_status] == 'd_feas': self.results.solver.status = SolverStatus.ok self.results.solver.termination_message += " The solution is dual feasible." self.results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif SOLSTA_MAP[sol_status] == 'd_infeas': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " The solution is a certificate of dual infeasibility." self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.infeasible elif SOLSTA_MAP[sol_status] == 'p_infeas': self.results.solver.status = SolverStatus.warning self.results.solver.termination_message += " The solution is a certificate of primal infeasibility." self.results.solver.Message = self.results.solver.termination_message self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible self.results.problem.name = msk_task.gettaskname() if msk_task.getobjsense() == msk.objsense.minimize: self.results.problem.sense = minimize elif msk_task.getobjsense() == msk.objsense.maximize: self.results.problem.sense = maximize else: raise RuntimeError( 'Unrecognized Mosek objective sense: {0}'.format( msk_task.getobjname())) self.results.problem.upper_bound = None self.results.problem.lower_bound = None if msk_task.getnumintvar() == 0: try: if msk_task.getobjsense() == msk.objsense.minimize: self.results.problem.upper_bound = msk_task.getprimalobj( whichsol) self.results.problem.lower_bound = msk_task.getdualobj( whichsol) elif msk_task.getobjsense() == msk.objsense.maximize: self.results.problem.upper_bound = msk_task.getprimalobj( whichsol) self.results.problem.lower_bound = msk_task.getdualobj( whichsol) except (msk.MosekException, AttributeError): pass elif msk_task.getobjsense() == msk.objsense.minimize: # minimizing try: self.results.problem.upper_bound = msk_task.getprimalobj( whichsol) except (msk.MosekException, AttributeError): pass try: self.results.problem.lower_bound = msk_task.getdouinf( msk.dinfitem.mio_obj_bound) except (msk.MosekException, AttributeError): pass elif msk_task.getobjsense() == msk.objsense.maximize: # maximizing try: self.results.problem.upper_bound = msk_task.getdouinf( msk.dinfitem.mio_obj_bound) except (msk.MosekException, AttributeError): pass try: self.results.problem.lower_bound = msk_task.getprimalobj( whichsol) except (msk.MosekException, AttributeError): pass else: raise RuntimeError( 'Unrecognized Mosek objective sense: {0}'.format( msk_task.getobjsense())) try: soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound except TypeError: soln.gap = None self.results.problem.number_of_constraints = msk_task.getnumcon() self.results.problem.number_of_nonzeros = msk_task.getnumanz() self.results.problem.number_of_variables = msk_task.getnumvar() self.results.problem.number_of_integer_variables = msk_task.getnumintvar( ) self.results.problem.number_of_continuous_variables = msk_task.getnumvar() - \ msk_task.getnumintvar() self.results.problem.number_of_objectives = 1 self.results.problem.number_of_solutions = 1 if self._save_results: """ This code in this if statement is only needed for backwards compatability. It is more efficient to set _save_results to False and use load_vars, load_duals, etc. """ if self.results.problem.number_of_solutions > 0: soln_variables = soln.variable soln_constraints = soln.constraint mosek_vars = list(range(msk_task.getnumvar())) mosek_vars = list( set(mosek_vars).intersection( set(self._pyomo_var_to_solver_var_map.values()))) var_vals = [0.0] * len(mosek_vars) self._solver_model.getxx(whichsol, var_vals) names = list(map(msk_task.getvarname, mosek_vars)) for mosek_var, val, name in zip(mosek_vars, var_vals, names): pyomo_var = self._solver_var_to_pyomo_var_map[mosek_var] if self._referenced_variables[pyomo_var] > 0: soln_variables[name] = {"Value": val} if extract_reduced_costs: vals = [0.0] * len(mosek_vars) msk_task.getreducedcosts(whichsol, 0, len(mosek_vars), vals) for mosek_var, val, name in zip(mosek_vars, vals, names): pyomo_var = self._solver_var_to_pyomo_var_map[ mosek_var] if self._referenced_variables[pyomo_var] > 0: soln_variables[name]["Rc"] = val if extract_duals or extract_slacks: mosek_cons = list(range(msk_task.getnumcon())) con_names = list(map(msk_task.getconname, mosek_cons)) for name in con_names: soln_constraints[name] = {} """TODO wrong length, needs to be getnumvars() mosek_cones = list(range(msk_task.getnumcone())) cone_names = [] for cone in mosek_cones: cone_names.append(msk_task.getconename(cone)) for name in cone_names: soln_constraints[name] = {} """ if extract_duals: ncon = msk_task.getnumcon() if ncon > 0: vals = [0.0] * ncon msk_task.gety(whichsol, vals) for val, name in zip(vals, con_names): soln_constraints[name]["Dual"] = val """TODO: wrong length, needs to be getnumvars() ncone = msk_task.getnumcone() if ncone > 0: vals = [0.0]*ncone msk_task.getsnx(whichsol, vals) for val, name in zip(vals, cone_names): soln_constraints[name]["Dual"] = val """ if extract_slacks: Ax = [0] * len(mosek_cons) msk_task.getxc(self._whichsol, Ax) for con, name in zip(mosek_cons, con_names): Us = Ls = 0 bk, lb, ub = msk_task.getconbound(con) if bk in [ msk.boundkey.fx, msk.boundkey.ra, msk.boundkey.up ]: Us = ub - Ax[con] if bk in [ msk.boundkey.fx, msk.boundkey.ra, msk.boundkey.lo ]: Ls = Ax[con] - lb if Us > Ls: soln_constraints[name]["Slack"] = Us else: soln_constraints[name]["Slack"] = -Ls elif self._load_solutions: if self.results.problem.number_of_solutions > 0: self.load_vars() if extract_reduced_costs: self._load_rc() if extract_duals: self._load_duals() if extract_slacks: self._load_slacks() self.results.solution.insert(soln) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def _postsolve(self): # the only suffixes that we extract from CPLEX are # constraint duals, constraint slacks, and variable # reduced-costs. scan through the solver suffix list # and throw an exception if the user has specified # any others. extract_duals = False extract_slacks = False extract_reduced_costs = False for suffix in self._suffixes: flag = False if re.match(suffix, "dual"): extract_duals = True flag = True if re.match(suffix, "slack"): extract_slacks = True flag = True if re.match(suffix, "rc"): extract_reduced_costs = True flag = True if not flag: raise RuntimeError( "***The cplex_direct solver plugin cannot extract solution suffix=" + suffix) cpxprob = self._solver_model status = cpxprob.solution.get_status() if cpxprob.get_problem_type() in [ cpxprob.problem_type.MILP, cpxprob.problem_type.MIQP, cpxprob.problem_type.MIQCP ]: if extract_reduced_costs: logger.warning("Cannot get reduced costs for MIP.") if extract_duals: logger.warning("Cannot get duals for MIP.") extract_reduced_costs = False extract_duals = False self.results = SolverResults() soln = Solution() self.results.solver.name = ("CPLEX {0}".format(cpxprob.get_version())) self.results.solver.wallclock_time = self._wallclock_time if status in [1, 101, 102]: self.results.solver.status = SolverStatus.ok self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif status in [2, 40, 118, 133, 134]: self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif status in [4, 119, 134]: # Note: status of 4 means infeasible or unbounded # and 119 means MIP infeasible or unbounded self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = \ TerminationCondition.infeasibleOrUnbounded soln.status = SolutionStatus.unsure elif status in [3, 103]: self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif status in [10]: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.maxIterations soln.status = SolutionStatus.stoppedByLimit elif status in [11, 25, 107, 131]: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.maxTimeLimit soln.status = SolutionStatus.stoppedByLimit else: self.results.solver.status = SolverStatus.error self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize: self.results.problem.sense = pyomo.core.kernel.minimize elif cpxprob.objective.get_sense() == cpxprob.objective.sense.maximize: self.results.problem.sense = pyomo.core.kernel.maximize else: raise RuntimeError('Unrecognized cplex objective sense: {0}'.\ format(cpxprob.objective.get_sense())) self.results.problem.upper_bound = None self.results.problem.lower_bound = None if (cpxprob.variables.get_num_binary() + cpxprob.variables.get_num_integer()) == 0: try: self.results.problem.upper_bound = cpxprob.solution.get_objective_value( ) self.results.problem.lower_bound = cpxprob.solution.get_objective_value( ) except self._cplex.exceptions.CplexError: pass elif cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize: try: self.results.problem.upper_bound = cpxprob.solution.get_objective_value( ) except self._cplex.exceptions.CplexError: pass try: self.results.problem.lower_bound = cpxprob.solution.MIP.get_best_objective( ) except self._cplex.exceptions.CplexError: pass elif cpxprob.objective.get_sense() == cpxprob.objective.sense.maximize: try: self.results.problem.upper_bound = cpxprob.solution.MIP.get_best_objective( ) except self._cplex.exceptions.CplexError: pass try: self.results.problem.lower_bound = cpxprob.solution.get_objective_value( ) except self._cplex.exceptions.CplexError: pass else: raise RuntimeError('Unrecognized cplex objective sense: {0}'.\ format(cpxprob.objective.get_sense())) try: soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound except TypeError: soln.gap = None self.results.problem.name = cpxprob.get_problem_name() assert cpxprob.indicator_constraints.get_num() == 0 self.results.problem.number_of_constraints = \ (cpxprob.linear_constraints.get_num() + cpxprob.quadratic_constraints.get_num() + cpxprob.SOS.get_num()) self.results.problem.number_of_nonzeros = None self.results.problem.number_of_variables = cpxprob.variables.get_num() self.results.problem.number_of_binary_variables = cpxprob.variables.get_num_binary( ) self.results.problem.number_of_integer_variables = cpxprob.variables.get_num_integer( ) assert cpxprob.variables.get_num_semiinteger() == 0 assert cpxprob.variables.get_num_semicontinuous() == 0 self.results.problem.number_of_continuous_variables = \ (cpxprob.variables.get_num() - cpxprob.variables.get_num_binary() - cpxprob.variables.get_num_integer()) self.results.problem.number_of_objectives = 1 # only try to get objective and variable values if a solution exists if self._save_results: """ This code in this if statement is only needed for backwards compatability. It is more efficient to set _save_results to False and use load_vars, load_duals, etc. """ if cpxprob.solution.get_solution_type() > 0: soln_variables = soln.variable soln_constraints = soln.constraint var_names = self._solver_model.variables.get_names() var_names = list( set(var_names).intersection( set(self._pyomo_var_to_solver_var_map.values()))) var_vals = self._solver_model.solution.get_values(var_names) for i, name in enumerate(var_names): pyomo_var = self._solver_var_to_pyomo_var_map[name] if self._referenced_variables[pyomo_var] > 0: pyomo_var.stale = False soln_variables[name] = {"Value": var_vals[i]} if extract_reduced_costs: reduced_costs = self._solver_model.solution.get_reduced_costs( var_names) for i, name in enumerate(var_names): pyomo_var = self._solver_var_to_pyomo_var_map[name] if self._referenced_variables[pyomo_var] > 0: soln_variables[name]["Rc"] = reduced_costs[i] if extract_slacks: for con_name in self._solver_model.linear_constraints.get_names( ): soln_constraints[con_name] = {} for con_name in self._solver_model.quadratic_constraints.get_names( ): soln_constraints[con_name] = {} elif extract_duals: # CPLEX PYTHON API DOES NOT SUPPORT QUADRATIC DUAL COLLECTION for con_name in self._solver_model.linear_constraints.get_names( ): soln_constraints[con_name] = {} if extract_duals: dual_values = self._solver_model.solution.get_dual_values() for i, con_name in enumerate( self._solver_model.linear_constraints.get_names()): soln_constraints[con_name]["Dual"] = dual_values[i] if extract_slacks: linear_slacks = self._solver_model.solution.get_linear_slacks( ) qudratic_slacks = self._solver_model.solution.get_quadratic_slacks( ) for i, con_name in enumerate( self._solver_model.linear_constraints.get_names()): pyomo_con = self._solver_con_to_pyomo_con_map[con_name] if pyomo_con in self._range_constraints: R_ = self._solver_model.linear_constraints.get_range_values( con_name) if R_ == 0: soln_constraints[con_name][ "Slack"] = linear_slacks[i] else: Ls_ = linear_slacks[i] Us_ = R_ - Ls_ if abs(Us_) > abs(Ls_): soln_constraints[con_name]["Slack"] = Us_ else: soln_constraints[con_name]["Slack"] = -Ls_ else: soln_constraints[con_name][ "Slack"] = linear_slacks[i] for i, con_name in enumerate( self._solver_model.quadratic_constraints.get_names( )): soln_constraints[con_name]["Slack"] = qudratic_slacks[ i] elif self._load_solutions: if cpxprob.solution.get_solution_type() > 0: self._load_vars() if extract_reduced_costs: self._load_rc() if extract_duals: self._load_duals() if extract_slacks: self._load_slacks() self.results.solution.insert(soln) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. pyutilib.services.TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def _postsolve(self): # the only suffixes that we extract from CPLEX are # constraint duals, constraint slacks, and variable # reduced-costs. scan through the solver suffix list # and throw an exception if the user has specified # any others. extract_duals = False extract_slacks = False extract_reduced_costs = False for suffix in self._suffixes: flag = False if re.match(suffix, "dual"): extract_duals = True flag = True if re.match(suffix, "slack"): extract_slacks = True flag = True if re.match(suffix, "rc"): extract_reduced_costs = True flag = True if not flag: raise RuntimeError( "***The cplex_direct solver plugin cannot extract solution suffix=" + suffix) cpxprob = self._solver_model status = cpxprob.solution.get_status() rtn_codes = cpxprob.solution.status if cpxprob.get_problem_type() in [ cpxprob.problem_type.MILP, cpxprob.problem_type.MIQP, cpxprob.problem_type.MIQCP ]: if extract_reduced_costs: logger.warning("Cannot get reduced costs for MIP.") if extract_duals: logger.warning("Cannot get duals for MIP.") extract_reduced_costs = False extract_duals = False self.results = SolverResults() soln = Solution() self.results.solver.name = ("CPLEX {0}".format(cpxprob.get_version())) self.results.solver.wallclock_time = self._wallclock_time self.results.solver.deterministic_time = self._deterministic_time if status in { rtn_codes.optimal, rtn_codes.MIP_optimal, rtn_codes.optimal_tolerance, rtn_codes.multiobj_optimal, }: self.results.solver.status = SolverStatus.ok self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif status in { rtn_codes.unbounded, 40, rtn_codes.MIP_unbounded, rtn_codes.relaxation_unbounded, 134, rtn_codes.multiobj_unbounded, }: self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif status in { rtn_codes.infeasible_or_unbounded, rtn_codes.MIP_infeasible_or_unbounded, 134, rtn_codes.multiobj_inforunbd, }: # Note: status of 4 means infeasible or unbounded # and 119 means MIP infeasible or unbounded self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = \ TerminationCondition.infeasibleOrUnbounded soln.status = SolutionStatus.unsure elif status in { rtn_codes.infeasible, rtn_codes.MIP_infeasible, rtn_codes.multiobj_infeasible }: self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif status in {rtn_codes.abort_iteration_limit}: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.maxIterations soln.status = SolutionStatus.stoppedByLimit elif status in {rtn_codes.MIP_abort_feasible}: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.userInterrupt soln.status = SolutionStatus.feasible elif status in { rtn_codes.solution_limit, rtn_codes.node_limit_feasible, rtn_codes.mem_limit_feasible }: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.maxEvaluations soln.status = SolutionStatus.stoppedByLimit elif status in { rtn_codes.abort_time_limit, rtn_codes.abort_dettime_limit, rtn_codes.MIP_time_limit_feasible, rtn_codes.MIP_dettime_limit_feasible, rtn_codes.multiobj_stopped, rtn_codes.multiobj_non_optimal, } and cpxprob.solution.get_solution_type( ) != cpxprob.solution.type.none: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.maxTimeLimit soln.status = SolutionStatus.stoppedByLimit elif status in { rtn_codes.MIP_time_limit_infeasible, rtn_codes.MIP_dettime_limit_infeasible, rtn_codes.node_limit_infeasible, rtn_codes.mem_limit_infeasible, rtn_codes.MIP_abort_infeasible, rtn_codes.multiobj_stopped, } or self._error_code == self._cplex.exceptions.error_codes.CPXERR_NO_SOLN: # CPLEX doesn't have a solution status for `noSolution` so we assume this from the combination of # maxTimeLimit + infeasible (instead of a generic `TerminationCondition.error`). self.results.solver.status = SolverStatus.warning self.results.solver.termination_condition = TerminationCondition.noSolution soln.status = SolutionStatus.unknown else: self.results.solver.status = SolverStatus.error self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error self.results.solver.return_code = self._error_code self.results.solver.termination_message = cpxprob.solution.get_status_string( status) # Get additional solver output from log file if self.version() >= (12, 5, 1) and isinstance(self._log_file, str): _log_file = open(self._log_file, 'r') _close_log_file = True log_output: str = "".join(_log_file.readlines()) else: _log_file = self._log_file _close_log_file = False log_output: str = "" # use regular expressions to use multi-line match patterns: self.results.solver.root_node_processing_time = get_root_node_processing_time( log_output=log_output) self.results.solver.tree_processing_time = get_tree_processing_time( log_output=log_output) mip_start_warning = False self.results.solver.n_solutions_found = 0 for line in log_output.split("\n"): if (line.startswith('Warning') and re.search( r'No solution found from \d+ MIP starts', line)): mip_start_warning = True tokens = re.split('[ \t]+', line.strip()) if len(tokens) >= 9 and tokens[0] == "MIP" and tokens[ 1] == "start" and tokens[7] == "objective": self.results.solver.warm_start_objective_value = float( tokens[8].rstrip('.')) elif line.startswith("Found incumbent of value"): self.results.solver.n_solutions_found += 1 if _close_log_file: _log_file.close() self.results.solver.mip_start_failed = mip_start_warning if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize: self.results.problem.sense = minimize elif cpxprob.objective.get_sense() == cpxprob.objective.sense.maximize: self.results.problem.sense = maximize else: raise RuntimeError('Unrecognized cplex objective sense: {0}'.\ format(cpxprob.objective.get_sense())) self.results.problem.upper_bound = None self.results.problem.lower_bound = None if cpxprob.solution.get_solution_type() != cpxprob.solution.type.none: if (cpxprob.variables.get_num_binary() + cpxprob.variables.get_num_integer()) == 0: self.results.problem.upper_bound = cpxprob.solution.get_objective_value( ) self.results.problem.lower_bound = cpxprob.solution.get_objective_value( ) elif cpxprob.objective.get_sense( ) == cpxprob.objective.sense.minimize: self.results.problem.upper_bound = cpxprob.solution.get_objective_value( ) self.results.problem.lower_bound = cpxprob.solution.MIP.get_best_objective( ) else: assert cpxprob.objective.get_sense( ) == cpxprob.objective.sense.maximize self.results.problem.upper_bound = cpxprob.solution.MIP.get_best_objective( ) self.results.problem.lower_bound = cpxprob.solution.get_objective_value( ) try: soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound except TypeError: soln.gap = None self.results.problem.name = cpxprob.get_problem_name() assert cpxprob.indicator_constraints.get_num() == 0 self.results.problem.number_of_constraints = \ (cpxprob.linear_constraints.get_num() + cpxprob.quadratic_constraints.get_num() + cpxprob.SOS.get_num()) self.results.problem.number_of_nonzeros = None self.results.problem.number_of_variables = cpxprob.variables.get_num() self.results.problem.number_of_binary_variables = cpxprob.variables.get_num_binary( ) self.results.problem.number_of_integer_variables = cpxprob.variables.get_num_integer( ) assert cpxprob.variables.get_num_semiinteger() == 0 assert cpxprob.variables.get_num_semicontinuous() == 0 self.results.problem.number_of_continuous_variables = \ (cpxprob.variables.get_num() - cpxprob.variables.get_num_binary() - cpxprob.variables.get_num_integer()) self.results.problem.number_of_objectives = 1 # only try to get objective and variable values if a solution exists if self._save_results: """ This code in this if statement is only needed for backwards compatability. It is more efficient to set _save_results to False and use load_vars, load_duals, etc. """ if cpxprob.solution.get_solution_type() > 0: soln_variables = soln.variable soln_constraints = soln.constraint var_names = self._solver_model.variables.get_names() assert set(var_names) == set( self._pyomo_var_to_solver_var_map.values()) var_vals = self._solver_model.solution.get_values() for name, val in zip(var_names, var_vals): pyomo_var = self._solver_var_to_pyomo_var_map[name] if self._referenced_variables[pyomo_var] > 0: pyomo_var.stale = False soln_variables[name] = {"Value": val} if extract_reduced_costs: reduced_costs = self._solver_model.solution.get_reduced_costs( var_names) for i, name in enumerate(var_names): pyomo_var = self._solver_var_to_pyomo_var_map[name] if self._referenced_variables[pyomo_var] > 0: soln_variables[name]["Rc"] = reduced_costs[i] if extract_slacks: for con_name in self._solver_model.linear_constraints.get_names( ): soln_constraints[con_name] = {} for con_name in self._solver_model.quadratic_constraints.get_names( ): soln_constraints[con_name] = {} elif extract_duals: # CPLEX PYTHON API DOES NOT SUPPORT QUADRATIC DUAL COLLECTION for con_name in self._solver_model.linear_constraints.get_names( ): soln_constraints[con_name] = {} if extract_duals: dual_values = self._solver_model.solution.get_dual_values() for i, con_name in enumerate( self._solver_model.linear_constraints.get_names()): soln_constraints[con_name]["Dual"] = dual_values[i] if extract_slacks: linear_slacks = self._solver_model.solution.get_linear_slacks( ) qudratic_slacks = self._solver_model.solution.get_quadratic_slacks( ) for i, con_name in enumerate( self._solver_model.linear_constraints.get_names()): pyomo_con = self._solver_con_to_pyomo_con_map[con_name] if pyomo_con in self._range_constraints: R_ = self._solver_model.linear_constraints.get_range_values( con_name) if R_ == 0: soln_constraints[con_name][ "Slack"] = linear_slacks[i] else: Ls_ = linear_slacks[i] Us_ = R_ - Ls_ if abs(Us_) > abs(Ls_): soln_constraints[con_name]["Slack"] = Us_ else: soln_constraints[con_name]["Slack"] = -Ls_ else: soln_constraints[con_name][ "Slack"] = linear_slacks[i] for i, con_name in enumerate( self._solver_model.quadratic_constraints.get_names( )): soln_constraints[con_name]["Slack"] = qudratic_slacks[ i] elif self._load_solutions: if cpxprob.solution.get_solution_type() > 0: self._load_vars() if extract_reduced_costs: self._load_rc() if extract_duals: self._load_duals() if extract_slacks: self._load_slacks() self.results.solution.insert(soln) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def _postsolve(self): # the only suffixes that we extract from XPRESS are # constraint duals, constraint slacks, and variable # reduced-costs. scan through the solver suffix list # and throw an exception if the user has specified # any others. extract_duals = False extract_slacks = False extract_reduced_costs = False for suffix in self._suffixes: flag = False if re.match(suffix, "dual"): extract_duals = True flag = True if re.match(suffix, "slack"): extract_slacks = True flag = True if re.match(suffix, "rc"): extract_reduced_costs = True flag = True if not flag: raise RuntimeError( "***The xpress_direct solver plugin cannot extract solution suffix=" + suffix) xprob = self._solver_model xp = xpress xprob_attrs = xprob.attributes ## XPRESS's status codes depend on this ## (number of integer vars > 0) or (number of special order sets > 0) is_mip = (xprob_attrs.mipents > 0) or (xprob_attrs.sets > 0) if is_mip: if extract_reduced_costs: logger.warning("Cannot get reduced costs for MIP.") if extract_duals: logger.warning("Cannot get duals for MIP.") extract_reduced_costs = False extract_duals = False self.results = SolverResults() soln = Solution() self.results.solver.name = XpressDirect._name self.results.solver.wallclock_time = self._opt_time if is_mip: status = xprob_attrs.mipstatus mip_sols = xprob_attrs.mipsols if status == xp.mip_not_loaded: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Model is not loaded; no solution information is available." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.unknown #no MIP solution, first LP did not solve, second LP did, third search started but incomplete elif status == xp.mip_lp_not_optimal \ or status == xp.mip_lp_optimal \ or status == xp.mip_no_sol_found: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Model is loaded, but no solution information is available." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.unknown elif status == xp.mip_solution: # some solution available self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Unable to satisfy optimality tolerances; a sub-optimal " \ "solution is available." self.results.solver.termination_condition = TerminationCondition.other soln.status = SolutionStatus.feasible elif status == xp.mip_infeas: # MIP proven infeasible self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be infeasible" self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif status == xp.mip_optimal: # optimal self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \ "and an optimal solution is available." self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif status == xp.mip_unbounded and mip_sols > 0: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "LP relaxation was proven to be unbounded, " \ "but a solution is available." self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif status == xp.mip_unbounded and mip_sols <= 0: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "LP relaxation was proven to be unbounded." self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded else: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = \ ("Unhandled Xpress solve status " "("+str(status)+")") self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error else: ## an LP, we'll check the lpstatus status = xprob_attrs.lpstatus if status == xp.lp_unstarted: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Model is not loaded; no solution information is available." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.unknown elif status == xp.lp_optimal: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \ "and an optimal solution is available." self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif status == xp.lp_infeas: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be infeasible" self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif status == xp.lp_cutoff: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Optimal objective for model was proven to be worse than the " \ "cutoff value specified; a solution is available." self.results.solver.termination_condition = TerminationCondition.minFunctionValue soln.status = SolutionStatus.optimal elif status == xp.lp_unfinished: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization was terminated by the user." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif status == xp.lp_unbounded: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be unbounded." self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif status == xp.lp_cutoff_in_dual: self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Xpress reported the LP was cutoff in the dual." self.results.solver.termination_condition = TerminationCondition.minFunctionValue soln.status = SolutionStatus.optimal elif status == xp.lp_unsolved: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = "Optimization was terminated due to unrecoverable numerical " \ "difficulties." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif status == xp.lp_nonconvex: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = "Optimization was terminated because nonconvex quadratic data " \ "were found." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error else: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = \ ("Unhandled Xpress solve status " "("+str(status)+")") self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error self.results.problem.name = xprob_attrs.matrixname if xprob_attrs.objsense == 1.0: self.results.problem.sense = minimize elif xprob_attrs.objsense == -1.0: self.results.problem.sense = maximize else: raise RuntimeError( 'Unrecognized Xpress objective sense: {0}'.format( xprob_attrs.objsense)) self.results.problem.upper_bound = None self.results.problem.lower_bound = None if not is_mip: #LP or continuous problem try: self.results.problem.upper_bound = xprob_attrs.lpobjval self.results.problem.lower_bound = xprob_attrs.lpobjval except (XpressDirect.XpressException, AttributeError): pass elif xprob_attrs.objsense == 1.0: # minimizing MIP try: self.results.problem.upper_bound = xprob_attrs.mipbestobjval except (XpressDirect.XpressException, AttributeError): pass try: self.results.problem.lower_bound = xprob_attrs.bestbound except (XpressDirect.XpressException, AttributeError): pass elif xprob_attrs.objsense == -1.0: # maximizing MIP try: self.results.problem.upper_bound = xprob_attrs.bestbound except (XpressDirect.XpressException, AttributeError): pass try: self.results.problem.lower_bound = xprob_attrs.mipbestobjval except (XpressDirect.XpressException, AttributeError): pass else: raise RuntimeError( 'Unrecognized xpress objective sense: {0}'.format( xprob_attrs.objsense)) try: soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound except TypeError: soln.gap = None self.results.problem.number_of_constraints = xprob_attrs.rows + xprob_attrs.sets + xprob_attrs.qconstraints self.results.problem.number_of_nonzeros = xprob_attrs.elems self.results.problem.number_of_variables = xprob_attrs.cols self.results.problem.number_of_integer_variables = xprob_attrs.mipents self.results.problem.number_of_continuous_variables = xprob_attrs.cols - xprob_attrs.mipents self.results.problem.number_of_objectives = 1 self.results.problem.number_of_solutions = xprob_attrs.mipsols if is_mip else 1 # if a solve was stopped by a limit, we still need to check to # see if there is a solution available - this may not always # be the case, both in LP and MIP contexts. if self._save_results: """ This code in this if statement is only needed for backwards compatability. It is more efficient to set _save_results to False and use load_vars, load_duals, etc. """ if xprob_attrs.lpstatus in \ [xp.lp_optimal, xp.lp_cutoff, xp.lp_cutoff_in_dual] or \ xprob_attrs.mipsols > 0: soln_variables = soln.variable soln_constraints = soln.constraint xpress_vars = list(self._solver_var_to_pyomo_var_map.keys()) var_vals = xprob.getSolution(xpress_vars) for xpress_var, val in zip(xpress_vars, var_vals): pyomo_var = self._solver_var_to_pyomo_var_map[xpress_var] if self._referenced_variables[pyomo_var] > 0: pyomo_var.stale = False soln_variables[xpress_var.name] = {"Value": val} if extract_reduced_costs: vals = xprob.getRCost(xpress_vars) for xpress_var, val in zip(xpress_vars, vals): pyomo_var = self._solver_var_to_pyomo_var_map[ xpress_var] if self._referenced_variables[pyomo_var] > 0: soln_variables[xpress_var.name]["Rc"] = val if extract_duals or extract_slacks: xpress_cons = list( self._solver_con_to_pyomo_con_map.keys()) for con in xpress_cons: soln_constraints[con.name] = {} if extract_duals: vals = xprob.getDual(xpress_cons) for val, con in zip(vals, xpress_cons): soln_constraints[con.name]["Dual"] = val if extract_slacks: vals = xprob.getSlack(xpress_cons) for con, val in zip(xpress_cons, vals): if con in self._range_constraints: ## for xpress, the slack on a range constraint ## is based on the upper bound lb = con.lb ub = con.ub ub_s = val expr_val = ub - ub_s lb_s = lb - expr_val if abs(ub_s) > abs(lb_s): soln_constraints[con.name]["Slack"] = ub_s else: soln_constraints[con.name]["Slack"] = lb_s else: soln_constraints[con.name]["Slack"] = val elif self._load_solutions: if xprob_attrs.lpstatus == xp.lp_optimal and \ ((not is_mip) or (xprob_attrs.mipsols > 0)): self._load_vars() if extract_reduced_costs: self._load_rc() if extract_duals: self._load_duals() if extract_slacks: self._load_slacks() self.results.solution.insert(soln) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def solve(self, *args, **kwargs): t0 = time.time() logger.info('{0:<10}{1:<12}{2:<12}{3:<12}{4:<12}'.format( 'Iter', 'objective', 'max_viol', 'time', '# cuts')) if not self._using_persistent_solver: self._pyomo_model = args[0] options = self.options(kwargs.pop('options', dict())) subproblem_solver_options = self.subproblem_solver_options( kwargs.pop('subproblem_solver_options', dict())) obj = None for _obj in self._pyomo_model.component_data_objects(pe.Objective, descend_into=True, active=True, sort=True): if obj is not None: raise ValueError('Found multiple active objectives') obj = _obj if obj is None: raise ValueError('Could not find any active objectives') final_res = SolverResults() self._relaxations = ComponentSet() self._relaxations_not_tracking_solver = ComponentSet() self._relaxations_with_added_cuts = ComponentSet() for b in self._pyomo_model.component_data_objects(pe.Block, descend_into=True, active=True, sort=True): if isinstance(b, (BaseRelaxationData, BaseRelaxation)): self._relaxations.add(b) if self._using_persistent_solver: if self not in b._persistent_solvers: b.add_persistent_solver(self) self._relaxations_not_tracking_solver.add(b) for _iter in range(options.max_iter): if time.time() - t0 > options.time_limit: final_res.solver.termination_condition = pe.TerminationCondition.maxTimeLimit final_res.solver.status = pe.SolverStatus.aborted logger.warning('ECPBounder: time limit reached.') break if self._using_persistent_solver: res = self._subproblem_solver.solve( save_results=False, options=subproblem_solver_options) else: res = self._subproblem_solver.solve( self._pyomo_model, options=subproblem_solver_options) if res.solver.termination_condition != pe.TerminationCondition.optimal: final_res.solver.termination_condition = pe.TerminationCondition.other final_res.solver.status = pe.SolverStatus.aborted logger.warning( 'ECPBounder: subproblem did not terminate optimally') break num_cuts_added = 0 max_viol = 0 for b in self._relaxations: viol = None if b.is_rhs_convex(): viol = pe.value(b.get_rhs_expr()) - b.get_aux_var().value elif b.is_rhs_concave(): viol = b.get_aux_var().value - pe.value(b.get_rhs_expr()) if viol is not None: if viol > max_viol: max_viol = viol if viol > options.feasibility_tol: b.add_cut(keep_cut=options.keep_cuts) self._relaxations_with_added_cuts.add(b) num_cuts_added += 1 if obj.sense == pe.minimize: obj_val = res.problem.lower_bound final_res.problem.sense = pe.minimize final_res.problem.upper_bound = None final_res.problem.lower_bound = obj_val else: obj_val = res.problem.upper_bound final_res.problem.sense = pe.maximize final_res.problem.lower_bound = None final_res.problem.upper_bound = obj_val elapsed_time = time.time() - t0 logger.info( '{0:<10d}{1:<12.3e}{2:<12.3e}{3:<12.3e}{4:<12d}'.format( _iter, obj_val, max_viol, elapsed_time, num_cuts_added)) if num_cuts_added == 0: final_res.solver.termination_condition = pe.TerminationCondition.optimal final_res.solver.status = pe.SolverStatus.ok logger.info('ECPBounder: converged!') break if _iter == options.max_iter - 1: final_res.solver.termination_condition = pe.TerminationCondition.maxIterations final_res.solver.status = pe.SolverStatus.aborted logger.warning( 'ECPBounder: reached maximum number of iterations') if not options.keep_cuts: for b in self._relaxations_with_added_cuts: b.rebuild() if self._using_persistent_solver: for b in self._relaxations_not_tracking_solver: b.remove_persistent_solver(self) final_res.solver.wallclock_time = time.time() - t0 return final_res
def solve(self, model: _BlockData, tee: bool = False, load_solutions: bool = True, logfile: Optional[str] = None, solnfile: Optional[str] = None, timelimit: Optional[float] = None, report_timing: bool = False, solver_io: Optional[str] = None, suffixes: Optional[Sequence] = None, options: Optional[Dict] = None, keepfiles: bool = False, symbolic_solver_labels: bool = False): original_config = self.config self.config = self.config() self.config.stream_solver = tee self.config.load_solution = load_solutions self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit self.config.report_timing = report_timing if solver_io is not None: raise NotImplementedError('Still working on this') if suffixes is not None: raise NotImplementedError('Still working on this') if logfile is not None: raise NotImplementedError('Still working on this') if 'keepfiles' in self.config: self.config.keepfiles = keepfiles if solnfile is not None: if 'filename' in self.config: filename = os.path.splitext(solnfile)[0] self.config.filename = filename original_options = self.options if options is not None: self.options = options results: Results = super(LegacySolverInterface, self).solve(model) legacy_results = LegacySolverResults() legacy_soln = LegacySolution() legacy_results.solver.status = legacy_solver_status_map[results.termination_condition] legacy_results.solver.termination_condition = legacy_termination_condition_map[results.termination_condition] legacy_soln.status = legacy_solution_status_map[results.termination_condition] legacy_results.solver.termination_message = str(results.termination_condition) obj = get_objective(model) legacy_results.problem.sense = obj.sense if obj.sense == minimize: legacy_results.problem.lower_bound = results.best_objective_bound legacy_results.problem.upper_bound = results.best_feasible_objective else: legacy_results.problem.upper_bound = results.best_objective_bound legacy_results.problem.lower_bound = results.best_feasible_objective if results.best_feasible_objective is not None and results.best_objective_bound is not None: legacy_soln.gap = abs(results.best_feasible_objective - results.best_objective_bound) else: legacy_soln.gap = None symbol_map = SymbolMap() symbol_map.byObject = dict(self.symbol_map.byObject) symbol_map.bySymbol = {symb: weakref.ref(obj()) for symb, obj in self.symbol_map.bySymbol.items()} symbol_map.aliases = {symb: weakref.ref(obj()) for symb, obj in self.symbol_map.aliases.items()} symbol_map.default_labeler = self.symbol_map.default_labeler model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True if load_solutions: if hasattr(model, 'dual') and model.dual.import_enabled(): for c, val in results.solution_loader.get_duals().items(): model.dual[c] = val if hasattr(model, 'slack') and model.slack.import_enabled(): for c, val in results.solution_loader.get_slacks().items(): model.slack[c] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): model.rc[v] = val elif results.best_feasible_objective is not None: delete_legacy_soln = False for v, val in results.solution_loader.get_primals().items(): legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val} if hasattr(model, 'dual') and model.dual.import_enabled(): for c, val in results.solution_loader.get_duals().items(): legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val} if hasattr(model, 'slack') and model.slack.import_enabled(): for c, val in results.solution_loader.get_slacks().items(): symbol = symbol_map.getSymbol(c) if symbol in legacy_soln.constraint: legacy_soln.constraint[symbol]['Slack'] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): legacy_soln.variable['Rc'] = val legacy_results.solution.insert(legacy_soln) if delete_legacy_soln: legacy_results.solution.delete(0) self.config = original_config self.options = original_options return legacy_results
def _postprocess(self, result): """Create a SolverResult object, save it as _result and write solution data onto the user given Pyomo object (_py_model). :param result: The BnBResult indicating the result of the branch and bound process. """ assert self._stage == SolvingStage.DONE assert type(result) is BnBResult # Copy solution to model. optinst = self._bnb_tree.best_instance() if optinst is not None: optinst.write_solution(self._py_model) # Create result object. self._result = SolverResults() soln = Solution() # Set some result meta data. self._result.solver.name = ('PyMINLP') self._result.solver.wallclock_time = Stats.get_solver_time() # Set solver status. if result == BnBResult.INFEASIBLE: self._result.solver.status = SolverStatus.ok self._result.solver.termination_condition \ = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif result == BnBResult.OPTIMAL: self._result.solver.status = SolverStatus.ok self._result.solver.termination_condition \ = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif result == BnBResult.TIMEOUT: self._result.solver.status = SolverStatus.ok self._result.solver.termination_condition \ = TerminationCondition.maxTimeLimit soln.status = SolutionStatus.stoppedByLimit if result == BnBResult.OPTIMAL or result == BnBResult.TIMEOUT: # Set solution data. self._result.problem.sense = pyomo.core.kernel.minimize self._result.problem.lower_bound = self._bnb_tree.lower_bound() self._result.problem.upper_bound = self._bnb_tree.upper_bound() soln.gap = self._bnb_tree.upper_bound() \ - self._bnb_tree.lower_bound() if optinst is not None: # Set objective data. obj = self._py_model.component_objects(Objective) for o in obj: obj_name = o.name obj_val = value(o) break soln.objective[obj_name] = {'Value': obj_val} # Set variable data. #for vartype in self._py_model.component_objects(Var): # for v in vartype: # name = vartype[v].name # val = value(vartype[v]) # soln.variable[name] = {'Value': val} # Set problem instance data. self._result.problem.name = self._py_model.name self._result.problem.number_of_constraints = \ self._py_model.nconstraints() # self._result.problem.number_of_nonzeros = None self._result.problem.number_of_variables = self._py_model.nvariables() # self._result.problem.number_of_binary_variables = None # self._result.problem.number_of_integer_variables = None # self._result.problem.number_of_continuous_variables = None self._result.problem.number_of_objectives = \ self._py_model.nobjectives() # Set branch and bound data. nsubprob = 'Number of created subproblems' self._result.solver.statistics.branch_and_bound[nsubprob] = \ self._bnb_tree.nopennodes() + self._bnb_tree.nconsiderednodes() nsubprob = 'Number of considered subproblems' self._result.solver.statistics.branch_and_bound[nsubprob] = \ self._bnb_tree.nconsiderednodes() self._result.solution.insert(soln)