def _postsolve(self): # take care of the annoying (and empty) CPLEX temporary files in the current directory. # this approach doesn't seem overly efficient, but python os module functions don't # accept regular expression directly. filename_list = os.listdir(".") for filename in filename_list: # CPLEX temporary files come in two flavors - cplex.log and clone*.log. # the latter is the case for multi-processor environments. # IMPT: trap the possible exception raised by the file not existing. # this can occur in pyro environments where > 1 workers are # running CPLEX, and were started from the same directory. # these logs don't matter anyway (we redirect everything), # and are largely an annoyance. try: if re.match('cplex\.log', filename) != None: os.remove(filename) elif re.match('clone\d+\.log', filename) != None: os.remove(filename) except OSError: pass # let the base class deal with returning results. results = ILMLicensedSystemCallSolver._postsolve(self) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. does not # include, for example, the execution script. but does include # the warm-start file. TempfileManager.pop(remove=not self._keepfiles) return results
def solve(self, model, timer: HierarchicalTimer = None): self.available(exception_flag=True) if timer is None: timer = HierarchicalTimer() try: TempfileManager.push() if self.config.filename is None: self._filename = TempfileManager.create_tempfile() else: self._filename = self.config.filename TempfileManager.add_tempfile(self._filename + '.lp', exists=False) TempfileManager.add_tempfile(self._filename + '.soln', exists=False) TempfileManager.add_tempfile(self._filename + '.log', exists=False) timer.start('write lp file') self._writer.write(model, self._filename+'.lp', timer=timer) timer.stop('write lp file') res = self._apply_solver(timer) if self.config.report_timing: logger.info('\n' + str(timer)) return res finally: # finally, clean any temporary files registered with the # temp file manager, created/populated *directly* by this # plugin. TempfileManager.pop(remove=not self.config.keepfiles) if not self.config.keepfiles: self._filename = None if self.config.report_timing: print(timer)
def _postsolve(self): # let the base class deal with returning results. results = super(CBCSHELL, self)._postsolve() # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. does not # include, for example, the execution script. but does include # the warm-start file. TempfileManager.pop(remove=not self._keepfiles) return results
def _postsolve(self): if self._log_file is not None: OUTPUT = open(self._log_file, "w") OUTPUT.write("Solver command line: " + str(self._command.cmd) + '\n') OUTPUT.write("\n") OUTPUT.write(self._log + '\n') OUTPUT.close() # JPW: The cleanup of the problem file probably shouldn't be here, but # rather in the base OptSolver class. That would require movement of # the keepfiles attribute and associated cleanup logic to the base # class, which I didn't feel like doing at this present time. the # base class remove_files method should clean up the problem file. if (self._log_file is not None) and \ (not os.path.exists(self._log_file)): msg = "File '%s' not generated while executing %s" raise IOError(msg % (self._log_file, self.path)) results = None if self._results_format is not None: results = self.process_output(self._rc) # # If keepfiles is true, then we pop the # TempfileManager context while telling it to # _not_ remove the files. # if not self._keepfiles: # in some cases, the solution filename is # not generated via the temp-file mechanism, # instead being automatically derived from # the input lp/nl filename. so, we may have # to clean it up manually. if (not self._soln_file is None) and \ os.path.exists(self._soln_file): os.remove(self._soln_file) TempfileManager.pop(remove=not self._keepfiles) return results
def _postsolve(self): if self._log_file is not None: OUTPUT=open(self._log_file,"w") OUTPUT.write("Solver command line: "+str(self._command.cmd)+'\n') OUTPUT.write("\n") OUTPUT.write(self._log+'\n') OUTPUT.close() # JPW: The cleanup of the problem file probably shouldn't be here, but # rather in the base OptSolver class. That would require movement of # the keepfiles attribute and associated cleanup logic to the base # class, which I didn't feel like doing at this present time. the # base class remove_files method should clean up the problem file. if (self._log_file is not None) and \ (not os.path.exists(self._log_file)): msg = "File '%s' not generated while executing %s" raise IOError(msg % (self._log_file, self.path)) results = None if self._results_format is not None: results = self.process_output(self._rc) # # If keepfiles is true, then we pop the # TempfileManager context while telling it to # _not_ remove the files. # if not self._keepfiles: # in some cases, the solution filename is # not generated via the temp-file mechanism, # instead being automatically derived from # the input lp/nl filename. so, we may have # to clean it up manually. if (not self._soln_file is None) and \ os.path.exists(self._soln_file): os.remove(self._soln_file) TempfileManager.pop(remove=not self._keepfiles) return results
def run_command(command=None, parser=None, args=None, name='unknown', data=None, options=None): """ Execute a function that processes command-line arguments and then calls a command-line driver. This function provides a generic facility for executing a command function is rather generic. This function is segregated from the driver to enable profiling of the command-line execution. Required: command: The name of a function that will be executed to perform process the command-line options with a parser object. parser: The parser object that is used by the command-line function. Optional: options: If this is not None, then ignore the args option and use this to specify command options. args: Command-line arguments that are parsed. If this value is `None`, then the arguments in `sys.argv` are used to parse the command-line. name: Specifying the name of the command-line (for error messages). data: A container of labeled data. Returned: retval: Return values from the command-line execution. errorcode: 0 if Pyomo ran successfully """ # # # Parse command-line options # # retval = None errorcode = 0 if options is None: try: if type(args) is argparse.Namespace: _options = args else: _options = parser.parse_args(args=args) # Replace the parser options object with a pyutilib.misc.Options object options = pyutilib.misc.Options() for key in dir(_options): if key[0] != '_': val = getattr(_options, key) if not isinstance(val, types.MethodType): options[key] = val except SystemExit: # the parser throws a system exit if "-h" is specified - catch # it to exit gracefully. return Container(retval=retval, errorcode=errorcode) # # Configure loggers # configure_loggers(options=options) # # Setup I/O redirect to a file # logfile = options.runtime.logfile if not logfile is None: pyutilib.misc.setup_redirect(logfile) # # Call the main Pyomo runner with profiling # TempfileManager.push() pcount = options.runtime.profile_count if pcount > 0: if not pstats_available: if not logfile is None: pyutilib.misc.reset_redirect() msg = "Cannot use the 'profile' option. The Python 'pstats' " \ 'package cannot be imported!' raise ValueError(msg) tfile = TempfileManager.create_tempfile(suffix=".profile") tmp = profile.runctx( command.__name__ + '(options=options,parser=parser)', command.__globals__, locals(), tfile) p = pstats.Stats(tfile).strip_dirs() p.sort_stats('time', 'cumulative') p = p.print_stats(pcount) p.print_callers(pcount) p.print_callees(pcount) p = p.sort_stats('cumulative', 'calls') p.print_stats(pcount) p.print_callers(pcount) p.print_callees(pcount) p = p.sort_stats('calls') p.print_stats(pcount) p.print_callers(pcount) p.print_callees(pcount) retval = tmp else: # # Call the main Pyomo runner without profiling # TempfileManager.push() try: retval = command(options=options, parser=parser) except SystemExit: err = sys.exc_info()[1] # # If debugging is enabled or the 'catch' option is specified, then # exit. Otherwise, print an "Exiting..." message. # if __debug__ and (options.runtime.logging == 'debug' or options.runtime.catch_errors): sys.exit(0) print('Exiting %s: %s' % (name, str(err))) errorcode = err.code except Exception: err = sys.exc_info()[1] # # If debugging is enabled or the 'catch' option is specified, then # pass the exception up the chain (to pyomo_excepthook) # if __debug__ and (options.runtime.logging == 'debug' or options.runtime.catch_errors): if not logfile is None: pyutilib.misc.reset_redirect() TempfileManager.pop(remove=not options.runtime.keep_files) raise if not options.model is None and not options.model.save_file is None: model = "model " + options.model.save_file else: model = "model" global filter_excepthook if filter_excepthook: action = "loading" else: action = "running" msg = "Unexpected exception while %s %s:\n" % (action, model) # # This handles the case where the error is propagated by a KeyError. # KeyError likes to pass raw strings that don't handle newlines # (they translate "\n" to "\\n"), as well as tacking on single # quotes at either end of the error message. This undoes all that. # errStr = str(err) if type(err) == KeyError and errStr != "None": errStr = str(err).replace(r"\n", "\n")[1:-1] logging.getLogger('pyomo.core').error(msg + errStr) errorcode = 1 if not logfile is None: pyutilib.misc.reset_redirect() if options.runtime.disable_gc: gc.enable() TempfileManager.pop(remove=not options.runtime.keep_files) return Container(retval=retval, errorcode=errorcode)
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, }: 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, }: 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, }: # 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}: 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.solution_limit, rtn_codes.node_limit_feasible, rtn_codes.mem_limit_feasible }: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_condition = TerminationCondition.unknown 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, }: 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, } 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) 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() 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. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
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 # note that USER_OBJ_LIMIT was added in Gurobi 7.0, so it may not be present elif (status is not None) and \ (status == getattr(grb,'USER_OBJ_LIMIT',None)): self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "User specified an objective limit " \ "(a bound on either the best objective " \ "or the best bound), and that limit has " \ "been reached. Solution is available." self.results.solver.termination_condition = TerminationCondition.other soln.status = SolutionStatus.stoppedByLimit else: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = \ ("Unhandled Gurobi solve status " "("+str(status)+")") 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 = minimize elif gprob.ModelSense == -1: self.results.problem.sense = 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. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def _solve_impl(self, sp, keep_solver_files=False, output_solver_log=False, symbolic_solver_labels=False): if len(sp.scenario_tree.stages) > 2: raise ValueError("SD solver does not handle more " "than 2 time-stages") if sp.objective_sense == maximize: raise ValueError("SD solver does not yet handle " "maximization problems") TempfileManager.push() try: # # Setup the SD working directory # working_directory = TempfileManager.create_tempdir() config_filename = os.path.join(working_directory, "config.sd") sdinput_directory = os.path.join(working_directory, "sdinput", "pysp_model") sdoutput_directory = os.path.join(working_directory, "sdoutput", "pysp_model") logfile = os.path.join(working_directory, "sd.log") os.makedirs(sdinput_directory) assert os.path.exists(sdinput_directory) assert not os.path.exists(sdoutput_directory) self._write_config(config_filename) if self.get_option('single_replication'): solution_filename = os.path.join( sdoutput_directory, "pysp_model.detailed_rep_soln.out") else: solution_filename = os.path.join( sdoutput_directory, "pysp_model.detailed_soln.out") # # Create the SD input files # io_options = {'symbolic_solver_labels': symbolic_solver_labels} symbol_map = None if isinstance(sp, ImplicitSP): symbol_map = pyomo.pysp.smps.smpsutils.\ convert_implicit( sdinput_directory, "pysp_model", sp, core_format='mps', io_options=io_options) else: pyomo.pysp.smps.smpsutils.\ convert_explicit( sdinput_directory, "pysp_model", sp, core_format='mps', io_options=io_options) # # Launch SD # if keep_solver_files: print("Solver working directory: '%s'" % (working_directory)) print("Solver log file: '%s'" % (logfile)) start = time.time() rc, log = pyutilib.subprocess.run( self.get_option("sd_executable"), cwd=working_directory, stdin="pysp_model", outfile=logfile, tee=output_solver_log) stop = time.time() assert os.path.exists(sdoutput_directory) # # Parse the SD solution # xhat, results = self._read_solution(solution_filename) results.solver_time = stop - start if symbol_map is not None: # load the first stage variable solution into # the reference model for symbol, varvalue in xhat.items(): symbol_map.bySymbol[symbol]().value = varvalue else: # TODO: this is a hack for the non-implicit SP case # so that this solver can still be run using # the explicit scenario intput representation results.xhat = xhat finally: # # cleanup # TempfileManager.pop( remove=not keep_solver_files) return results
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 = self._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 = self._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 (self._XpressException, AttributeError): pass elif xprob_attrs.objsense == 1.0: # minimizing MIP try: self.results.problem.upper_bound = xprob_attrs.mipbestobjval except (self._XpressException, AttributeError): pass try: self.results.problem.lower_bound = xprob_attrs.bestbound except (self._XpressException, AttributeError): pass elif xprob_attrs.objsense == -1.0: # maximizing MIP try: self.results.problem.upper_bound = xprob_attrs.bestbound except (self._XpressException, AttributeError): pass try: self.results.problem.lower_bound = xprob_attrs.mipbestobjval except (self._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 _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( "***The mosek solver plugin cannot extract solution suffix=" + suffix) msk_task = self._solver_model msk = self._mosek itr_soltypes = [msk.problemtype.qo, msk.problemtype.qcqo] 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_feas: 'pd_infeas', msk.prosta.ill_posed: 'illposed', msk.prosta.prim_infeas_or_unbounded: 'p_inf_unb' } if self._version_major < 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 = "Optimization terminated because the total number " \ "iterations performed exceeded the value specified in the " \ "IterationLimit parameter." 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 = "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 self._termcode == msk.rescode.trm_user_callback: self.results.solver.status = SolverStatus.Aborted self.results.solver.termination_message = "Optimization terminated because of the user callback " 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 = "Optimization terminated because maximum number of relaxations" \ " / branches / integer solutions exceeded " \ "the value specified in the TimeLimit parameter." self.results.solver.termination_condition = TerminationCondition.maxEvaluations soln.status = SolutionStatus.stoppedByLimit else: self.results.solver.termination_message = " Optimization terminated %s response code." \ "Check Mosek response code documentation for further explanation." % 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 += " Unknown solution status." 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 proven to be 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 proven to be 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 proven to be 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 proven to be infeasible or unbounded." 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 += " Primal feasible solution is available." 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 += " Dual feasible solution is available." 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 dual infeasible." 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 primal infeasible." 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 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 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 = [] for i in mosek_vars: names.append(msk_task.getvarname(i)) 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: pyomo_var.stale = False 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 = [] for con in mosek_cons: con_names.append(msk_task.getconname(con)) for name in con_names: soln_constraints[name] = {} if extract_duals: vals = [0.0] * msk_task.getnumcon() msk_task.gety(whichsol, vals) for val, name in zip(vals, con_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 run_command(command=None, parser=None, args=None, name='unknown', data=None, options=None): """ Execute a function that processes command-line arguments and then calls a command-line driver. This function provides a generic facility for executing a command function is rather generic. This function is segregated from the driver to enable profiling of the command-line execution. Required: command: The name of a function that will be executed to perform process the command-line options with a parser object. parser: The parser object that is used by the command-line function. Optional: options: If this is not None, then ignore the args option and use this to specify command options. args: Command-line arguments that are parsed. If this value is `None`, then the arguments in `sys.argv` are used to parse the command-line. name: Specifying the name of the command-line (for error messages). data: A container of labeled data. Returned: retval: Return values from the command-line execution. errorcode: 0 if Pyomo ran successfully """ # # # Parse command-line options # # retval = None errorcode = 0 if options is None: try: if type(args) is argparse.Namespace: _options = args else: _options = parser.parse_args(args=args) # Replace the parser options object with a pyutilib.misc.Options object options = pyutilib.misc.Options() for key in dir(_options): if key[0] != '_': val = getattr(_options, key) if not isinstance(val, types.MethodType): options[key] = val except SystemExit: # the parser throws a system exit if "-h" is specified - catch # it to exit gracefully. return Container(retval=retval, errorcode=errorcode) # # Configure loggers # configure_loggers(options=options) # # Setup I/O redirect to a file # logfile = options.runtime.logfile if not logfile is None: pyutilib.misc.setup_redirect(logfile) # # Call the main Pyomo runner with profiling # TempfileManager.push() pcount = options.runtime.profile_count if pcount > 0: if not pstats_available: if not logfile is None: pyutilib.misc.reset_redirect() msg = "Cannot use the 'profile' option. The Python 'pstats' " \ 'package cannot be imported!' raise ValueError(msg) tfile = TempfileManager.create_tempfile(suffix=".profile") tmp = profile.runctx( command.__name__ + '(options=options,parser=parser)', command.__globals__, locals(), tfile ) p = pstats.Stats(tfile).strip_dirs() p.sort_stats('time', 'cumulative') p = p.print_stats(pcount) p.print_callers(pcount) p.print_callees(pcount) p = p.sort_stats('cumulative','calls') p.print_stats(pcount) p.print_callers(pcount) p.print_callees(pcount) p = p.sort_stats('calls') p.print_stats(pcount) p.print_callers(pcount) p.print_callees(pcount) retval = tmp else: # # Call the main Pyomo runner without profiling # TempfileManager.push() try: retval = command(options=options, parser=parser) except SystemExit: err = sys.exc_info()[1] # # If debugging is enabled or the 'catch' option is specified, then # exit. Otherwise, print an "Exiting..." message. # if __debug__ and (options.runtime.logging == 'debug' or options.runtime.catch_errors): sys.exit(0) print('Exiting %s: %s' % (name, str(err))) errorcode = err.code except Exception: err = sys.exc_info()[1] # # If debugging is enabled or the 'catch' option is specified, then # pass the exception up the chain (to pyomo_excepthook) # if __debug__ and (options.runtime.logging == 'debug' or options.runtime.catch_errors): if not logfile is None: pyutilib.misc.reset_redirect() TempfileManager.pop(remove=not options.runtime.keep_files) raise if not options.model is None and not options.model.save_file is None: model = "model " + options.model.save_file else: model = "model" global filter_excepthook if filter_excepthook: action = "loading" else: action = "running" msg = "Unexpected exception while %s %s:\n" % (action, model) # # This handles the case where the error is propagated by a KeyError. # KeyError likes to pass raw strings that don't handle newlines # (they translate "\n" to "\\n"), as well as tacking on single # quotes at either end of the error message. This undoes all that. # errStr = str(err) if type(err) == KeyError and errStr != "None": errStr = str(err).replace(r"\n","\n")[1:-1] logging.getLogger('pyomo.core').error(msg+errStr) errorcode = 1 if not logfile is None: pyutilib.misc.reset_redirect() if options.runtime.disable_gc: gc.enable() TempfileManager.pop(remove=not options.runtime.keep_files) return Container(retval=retval, errorcode=errorcode)
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 = 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.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. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self)
def _solve_impl(self, sp, keep_solver_files=False, output_solver_log=False, symbolic_solver_labels=False): if len(sp.scenario_tree.stages) > 2: raise ValueError("SD solver does not handle more " "than 2 time-stages") if sp.objective_sense == maximize: raise ValueError("SD solver does not yet handle " "maximization problems") TempfileManager.push() try: # # Setup the SD working directory # working_directory = TempfileManager.create_tempdir() config_filename = os.path.join(working_directory, "config.sd") sdinput_directory = os.path.join(working_directory, "sdinput", "pysp_model") sdoutput_directory = os.path.join(working_directory, "sdoutput", "pysp_model") logfile = os.path.join(working_directory, "sd.log") os.makedirs(sdinput_directory) assert os.path.exists(sdinput_directory) assert not os.path.exists(sdoutput_directory) self._write_config(config_filename) if self.get_option('single_replication'): solution_filename = os.path.join( sdoutput_directory, "pysp_model.detailed_rep_soln.out") else: solution_filename = os.path.join( sdoutput_directory, "pysp_model.detailed_soln.out") # # Create the SD input files # io_options = {'symbolic_solver_labels': symbolic_solver_labels} symbol_map = None if isinstance(sp, ImplicitSP): symbol_map = pyomo.pysp.smps.smpsutils.\ convert_implicit( sdinput_directory, "pysp_model", sp, core_format='mps', io_options=io_options) else: pyomo.pysp.smps.smpsutils.\ convert_explicit( sdinput_directory, "pysp_model", sp, core_format='mps', io_options=io_options) # # Launch SD # if keep_solver_files: print("Solver working directory: '%s'" % (working_directory)) print("Solver log file: '%s'" % (logfile)) start = time.time() rc, log = pyutilib.subprocess.run(self.get_option("sd_executable"), cwd=working_directory, stdin="pysp_model", outfile=logfile, tee=output_solver_log) stop = time.time() assert os.path.exists(sdoutput_directory) # # Parse the SD solution # xhat, results = self._read_solution(solution_filename) results.solver_time = stop - start if symbol_map is not None: # load the first stage variable solution into # the reference model for symbol, varvalue in xhat.items(): symbol_map.bySymbol[symbol]().value = varvalue else: # TODO: this is a hack for the non-implicit SP case # so that this solver can still be run using # the explicit scenario intput representation results.xhat = xhat finally: # # cleanup # TempfileManager.pop(remove=not keep_solver_files) return results
def __init__(self, pyomo_model): """ Pyomo nonlinear program interface Parameters ---------- pyomo_model: pyomo.environ.ConcreteModel Pyomo concrete model """ TempfileManager.push() try: # get the temp file names for the nl file nl_file = TempfileManager.create_tempfile(suffix='pynumero.nl') # The current AmplInterface code only supports a single # objective function Therefore, we throw an error if there # is not one (and only one) active objective function. This # is better than adding a dummy objective that the user does # not know about (since we do not have a good place to # remove this objective later) # # TODO: extend the AmplInterface and the AslNLP to correctly # handle this # # This currently addresses issue #1217 objectives = list( pyomo_model.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) if len(objectives) != 1: raise NotImplementedError( 'The ASL interface and PyomoNLP in PyNumero currently ' 'only support single objective problems. Deactivate ' 'any extra objectives you may have, or add a dummy ' 'objective (f(x)=0) if you have a square problem.') self._objective = objectives[0] # write the nl file for the Pyomo model and get the symbolMap fname, symbolMap = WriterFactory('nl')(pyomo_model, nl_file, lambda x: True, {}) # create component maps from vardata to idx and condata to idx self._vardata_to_idx = vdidx = ComponentMap() self._condata_to_idx = cdidx = ComponentMap() # TODO: Are these names totally consistent? for name, obj in six.iteritems(symbolMap.bySymbol): if name[0] == 'v': vdidx[obj()] = int(name[1:]) elif name[0] == 'c': cdidx[obj()] = int(name[1:]) # The NL writer advertises the external function libraries # through the PYOMO_AMPLFUNC environment variable; merge it # with any preexisting AMPLFUNC definitions amplfunc = "\n".join(val for val in ( os.environ.get('AMPLFUNC', ''), os.environ.get('PYOMO_AMPLFUNC', ''), ) if val) with CtypesEnviron(AMPLFUNC=amplfunc): super(PyomoNLP, self).__init__(nl_file) # keep pyomo model in cache self._pyomo_model = pyomo_model finally: # delete the nl file TempfileManager.pop()