def process_logfile(self): results = SolverResults() # # Process logfile # OUTPUT = open(self._log_file) # Collect cut-generation statistics from the log file for line in OUTPUT: if 'Bilinear' in line: results.solver.statistics['Bilinear_cuts'] = int( line.split()[1]) elif 'LD-Envelopes' in line: results.solver.statistics['LD-Envelopes_cuts'] = int( line.split()[1]) elif 'Multilinears' in line: results.solver.statistics['Multilinears_cuts'] = int( line.split()[1]) elif 'Convexity' in line: results.solver.statistics['Convexity_cuts'] = int( line.split()[1]) elif 'Integrality' in line: results.solver.statistics['Integrality_cuts'] = int( line.split()[1]) OUTPUT.close() return results
def process_logfile(self): """ Process logfile """ results = SolverResults() # For the lazy programmer, handle long variable names prob = results.problem solv = results.solver solv.termination_condition = TerminationCondition.unknown stats = results.solver.statistics bbound = stats.branch_and_bound prob.upper_bound = float('inf') prob.lower_bound = float('-inf') bbound.number_of_created_subproblems = 0 bbound.number_of_bounded_subproblems = 0 with open(self._log_file, 'r') as output: for line in output: toks = line.split() if 'tree is empty' in line: bbound.number_of_created_subproblems = toks[-1][:-1] bbound.number_of_bounded_subproblems = toks[-1][:-1] elif len(toks) == 2 and toks[0] == "sys": solv.system_time = toks[1] elif len(toks) == 2 and toks[0] == "user": solv.user_time = toks[1] elif len(toks) > 2 and (toks[0], toks[2]) == ("TIME", "EXCEEDED;"): solv.termination_condition = TerminationCondition.maxTimeLimit elif len(toks) > 5 and (toks[:6] == [ 'PROBLEM', 'HAS', 'NO', 'DUAL', 'FEASIBLE', 'SOLUTION' ]): solv.termination_condition = TerminationCondition.unbounded elif len(toks) > 5 and (toks[:6] == [ 'PROBLEM', 'HAS', 'NO', 'PRIMAL', 'FEASIBLE', 'SOLUTION' ]): solv.termination_condition = TerminationCondition.infeasible elif len(toks) > 4 and (toks[:5] == [ 'PROBLEM', 'HAS', 'NO', 'FEASIBLE', 'SOLUTION' ]): solv.termination_condition = TerminationCondition.infeasible elif len(toks) > 6 and (toks[:7] == [ 'LP', 'RELAXATION', 'HAS', 'NO', 'DUAL', 'FEASIBLE', 'SOLUTION' ]): solv.termination_condition = TerminationCondition.unbounded return results
def process_output(self, rc): if os.path.exists(self._results_file): return super(IPOPT, self).process_output(rc) else: res = SolverResults() res.solver.status = SolverStatus.warning res.solver.termination_condition = TerminationCondition.other if os.path.exists(self._log_file): with open(self._log_file) as f: for line in f: if "TOO_FEW_DEGREES_OF_FREEDOM" in line: res.solver.message = line.split(':')[2].strip() assert "degrees of freedom" in res.solver.message return res
def record_original_model_statistics(solve_data, config): """Record problem statistics for original model and setup SolverResults.""" # Create the solver results object res = solve_data.results = SolverResults() prob = res.problem origGDPopt = solve_data.original_model.GDPopt_utils res.problem.name = solve_data.working_model.name res.problem.number_of_nonzeros = None # TODO # TODO work on termination condition and message res.solver.termination_condition = None res.solver.message = None # TODO add some kind of timing res.solver.user_time = None res.solver.system_time = None res.solver.wallclock_time = None res.solver.termination_message = None # Classify the variables orig_binary = sum(1 for v in origGDPopt.orig_var_list if v.is_binary()) orig_continuous = sum( 1 for v in origGDPopt.orig_var_list if v.is_continuous()) orig_integer = sum(1 for v in origGDPopt.orig_var_list if v.is_integer()) # Get count of constraints and variables prob.number_of_constraints = len(origGDPopt.orig_constraints_list) prob.number_of_disjunctions = len(origGDPopt.orig_disjunctions_list) prob.number_of_variables = len(origGDPopt.orig_var_list) prob.number_of_binary_variables = orig_binary prob.number_of_continuous_variables = orig_continuous prob.number_of_integer_variables = orig_integer config.logger.info( "Original model has %s constraints (%s nonlinear) " "and %s disjunctions, " "with %s variables, of which %s are binary, %s are integer, " "and %s are continuous." % (prob.number_of_constraints, len(origGDPopt.orig_nonlinear_constraints), prob.number_of_disjunctions, prob.number_of_variables, orig_binary, orig_integer, orig_continuous))
def process_logfile(self): results = SolverResults() # # Process logfile # cuts = [ 'Bilinear', 'LD-Envelopes', 'Multilinears', 'Convexity', 'Integrality' ] # Collect cut-generation statistics from the log file with open(self._log_file) as OUTPUT: for line in OUTPUT: for field in cuts: if field in line: try: results.solver.statistics[field + '_cuts'] = int( line.split()[1]) except: pass return results
def solve(self, *args, **kwds): """ Solve a model via the GAMS Python API. Keyword Arguments ----------------- tee=False: bool Output GAMS log to stdout. logfile=None: str Filename to output GAMS log to a file. load_solutions=True: bool Load solution into model. If False, the results object will contain the solution data. keepfiles=False: bool Keep temporary files. Equivalent of DebugLevel.KeepFiles. Summary of temp files can be found in _gams_py_gjo0.pf tmpdir=None: str Specify directory path for storing temporary files. A directory will be created if one of this name doesn't exist. By default uses the system default temporary path. report_timing=False: bool Print timing reports for presolve, solver, postsolve, etc. io_options: dict Options that get passed to the writer. See writer in pyomo.repn.plugins.gams_writer for details. Updated with any other keywords passed to solve method. """ # Make sure available() doesn't crash self.available() from gams import GamsWorkspace, DebugLevel from gams.workspace import GamsExceptionExecution if len(args) != 1: raise ValueError('Exactly one model must be passed ' 'to solve method of GAMSSolver.') model = args[0] # self.options are default for each run, overwritten by kwds options = dict() options.update(self.options) options.update(kwds) load_solutions = options.pop("load_solutions", True) tee = options.pop("tee", False) logfile = options.pop("logfile", None) keepfiles = options.pop("keepfiles", False) tmpdir = options.pop("tmpdir", None) report_timing = options.pop("report_timing", False) io_options = options.pop("io_options", {}) # Pass remaining keywords to writer, which will handle # any unrecognized arguments io_options.update(options) initial_time = time.time() #################################################################### # Presolve #################################################################### # Create StringIO stream to pass to gams_writer, on which the # model file will be written. The writer also passes this StringIO # back, but output_file is defined in advance for clarity. output_file = StringIO() if isinstance(model, IBlock): # Kernel blocks have slightly different write method smap_id = model.write(filename=output_file, format=ProblemFormat.gams, _called_by_solver=True, **io_options) symbolMap = getattr(model, "._symbol_maps")[smap_id] else: (_, smap_id) = model.write(filename=output_file, format=ProblemFormat.gams, io_options=io_options) symbolMap = model.solutions.symbol_map[smap_id] presolve_completion_time = time.time() if report_timing: print(" %6.2f seconds required for presolve" % (presolve_completion_time - initial_time)) #################################################################### # Apply solver #################################################################### # IMPORTANT - only delete the whole tmpdir if the solver was the one # that made the directory. Otherwise, just delete the files the solver # made, if not keepfiles. That way the user can select a directory # they already have, like the current directory, without having to # worry about the rest of the contents of that directory being deleted. newdir = True if tmpdir is not None and os.path.exists(tmpdir): newdir = False ws = GamsWorkspace( debug=DebugLevel.KeepFiles if keepfiles else DebugLevel.Off, working_directory=tmpdir) t1 = ws.add_job_from_string(output_file.getvalue()) try: with OutputStream(tee=tee, logfile=logfile) as output_stream: t1.run(output=output_stream) except GamsExceptionExecution as e: try: if e.rc == 3: # Execution Error check_expr_evaluation(model, symbolMap, 'direct') finally: # Always name working directory or delete files, # regardless of any errors. if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted t1 = rec = rec_lo = rec_hi = None file_removal_gams_direct(tmpdir, newdir) raise except: # Catch other errors and remove files first if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted t1 = rec = rec_lo = rec_hi = None file_removal_gams_direct(tmpdir, newdir) raise solve_completion_time = time.time() if report_timing: print(" %6.2f seconds required for solver" % (solve_completion_time - presolve_completion_time)) #################################################################### # Postsolve #################################################################### # import suffixes must be on the top-level model if isinstance(model, IBlock): model_suffixes = list(comp.storage_key for comp \ in pyomo.core.kernel.suffix.\ import_suffix_generator(model, active=True, descend_into=False)) else: model_suffixes = list(name for (name,comp) \ in pyomo.core.base.suffix.\ active_import_suffix_generator(model)) extract_dual = ('dual' in model_suffixes) extract_rc = ('rc' in model_suffixes) results = SolverResults() results.problem.name = t1.name results.problem.lower_bound = t1.out_db["OBJEST"].find_record().value results.problem.upper_bound = t1.out_db["OBJEST"].find_record().value results.problem.number_of_variables = \ t1.out_db["NUMVAR"].find_record().value results.problem.number_of_constraints = \ t1.out_db["NUMEQU"].find_record().value results.problem.number_of_nonzeros = \ t1.out_db["NUMNZ"].find_record().value results.problem.number_of_binary_variables = None # Includes binary vars: results.problem.number_of_integer_variables = \ t1.out_db["NUMDVAR"].find_record().value results.problem.number_of_continuous_variables = \ t1.out_db["NUMVAR"].find_record().value \ - t1.out_db["NUMDVAR"].find_record().value results.problem.number_of_objectives = 1 # required by GAMS writer obj = list(model.component_data_objects(Objective, active=True)) assert len(obj) == 1, 'Only one objective is allowed.' obj = obj[0] objctvval = t1.out_db["OBJVAL"].find_record().value if obj.is_minimizing(): results.problem.sense = ProblemSense.minimize results.problem.upper_bound = objctvval else: results.problem.sense = ProblemSense.maximize results.problem.lower_bound = objctvval results.solver.name = "GAMS " + str(self.version()) # Init termination condition to None to give preference to this first # block of code, only set certain TC's below if it's still None results.solver.termination_condition = None results.solver.message = None solvestat = t1.out_db["SOLVESTAT"].find_record().value if solvestat == 1: results.solver.status = SolverStatus.ok elif solvestat == 2: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxIterations elif solvestat == 3: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit elif solvestat == 5: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations elif solvestat == 7: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.licensingProblems elif solvestat == 8: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.userInterrupt elif solvestat == 10: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.solverFailure elif solvestat == 11: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.internalSolverError elif solvestat == 4: results.solver.status = SolverStatus.warning results.solver.message = "Solver quit with a problem (see LST file)" elif solvestat in (9, 12, 13): results.solver.status = SolverStatus.error elif solvestat == 6: results.solver.status = SolverStatus.unknown results.solver.return_code = 0 # Not sure if this value is actually user time # "the elapsed time it took to execute a solve statement in total" results.solver.user_time = t1.out_db["ETSOLVE"].find_record().value results.solver.system_time = None results.solver.wallclock_time = None results.solver.termination_message = None soln = Solution() modelstat = t1.out_db["MODELSTAT"].find_record().value if modelstat == 1: results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 2: results.solver.termination_condition = TerminationCondition.locallyOptimal soln.status = SolutionStatus.locallyOptimal elif modelstat in [3, 18]: results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif modelstat in [4, 5, 6, 10, 19]: results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif modelstat == 7: results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif modelstat == 8: # 'Integer solution model found' results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 9: results.solver.termination_condition = TerminationCondition.intermediateNonInteger soln.status = SolutionStatus.other elif modelstat == 11: # Should be handled above, if modelstat and solvestat both # indicate a licensing problem if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.licensingProblems soln.status = SolutionStatus.error elif modelstat in [12, 13]: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif modelstat == 14: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.noSolution soln.status = SolutionStatus.unknown elif modelstat in [15, 16, 17]: # Having to do with CNS models, # not sure what to make of status descriptions results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.unsure else: # This is just a backup catch, all cases are handled above soln.status = SolutionStatus.error soln.gap = abs(results.problem.upper_bound \ - results.problem.lower_bound) for sym, ref in iteritems(symbolMap.bySymbol): obj = ref() if isinstance(model, IBlock): # Kernel variables have no 'parent_component' if obj.ctype is IObjective: soln.objective[sym] = {'Value': objctvval} if obj.ctype is not IVariable: continue else: if obj.parent_component().type() is Objective: soln.objective[sym] = {'Value': objctvval} if obj.parent_component().type() is not Var: continue rec = t1.out_db[sym].find_record() # obj.value = rec.level soln.variable[sym] = {"Value": rec.level} if extract_rc and not math.isnan(rec.marginal): # Do not set marginals to nan # model.rc[obj] = rec.marginal soln.variable[sym]['rc'] = rec.marginal if extract_dual: for c in model.component_data_objects(Constraint, active=True): if c.body.is_fixed() or \ (not (c.has_lb() or c.has_ub())): # the constraint was not sent to GAMS continue sym = symbolMap.getSymbol(c) if c.equality: rec = t1.out_db[sym].find_record() if not math.isnan(rec.marginal): # model.dual[c] = rec.marginal soln.constraint[sym] = {'dual': rec.marginal} else: # Solver didn't provide marginals, # nothing else to do here break else: # Inequality, assume if 2-sided that only # one side's marginal is nonzero # Negate marginal for _lo equations marg = 0 if c.lower is not None: rec_lo = t1.out_db[sym + '_lo'].find_record() marg -= rec_lo.marginal if c.upper is not None: rec_hi = t1.out_db[sym + '_hi'].find_record() marg += rec_hi.marginal if not math.isnan(marg): # model.dual[c] = marg soln.constraint[sym] = {'dual': marg} else: # Solver didn't provide marginals, # nothing else to do here break results.solution.insert(soln) if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted t1 = rec = rec_lo = rec_hi = None file_removal_gams_direct(tmpdir, newdir) #################################################################### # Finish with results #################################################################### results._smap_id = smap_id results._smap = None if isinstance(model, IBlock): if len(results.solution) == 1: results.solution(0).symbol_map = \ getattr(model, "._symbol_maps")[results._smap_id] results.solution(0).default_variable_value = \ self._default_variable_value if load_solutions: model.load_solution(results.solution(0)) else: assert len(results.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(model, "._symbol_maps")) == 1 delattr(model, "._symbol_maps") del results._smap_id if load_solutions and \ (len(results.solution) == 0): logger.error("No solution is available") else: if load_solutions: model.solutions.load_from(results) results._smap_id = None results.solution.clear() else: results._smap = model.solutions.symbol_map[smap_id] model.solutions.delete_symbol_map(smap_id) postsolve_completion_time = time.time() if report_timing: print(" %6.2f seconds required for postsolve" % (postsolve_completion_time - solve_completion_time)) print(" %6.2f seconds required total" % (postsolve_completion_time - initial_time)) return results
def process_logfile(self): """ Process logfile """ results = SolverResults() # # Initial values # # results.solver.statistics.branch_and_bound. # number_of_created_subproblems=0 # results.solver.statistics.branch_and_bound. # number_of_bounded_subproblems=0 soln = results.solution.add() # # Process logfile # OUTPUT = open(self._log_file) output = "".join(OUTPUT.readlines()) OUTPUT.close() # # Parse logfile lines # # Handle long variable names stats = results.solver.statistics for line in output.split("\n"): tokens = re.split('[ \t]+', line.strip()) if len(tokens) > 4 and tokens[0] == "+" and \ tokens[2] == "mip" and tokens[4] == "not": results.problem.lower_bound = tokens[8] elif len(tokens) > 4 and tokens[0] == "+" and \ tokens[1] == "mip" and tokens[4] == "not": results.problem.lower_bound = tokens[7] elif len(tokens) > 4 and tokens[0] == "+" and \ tokens[2] == "mip" and tokens[4] != "not": if tokens[6] != "tree": results.problem.lower_bound = tokens[6] elif len(tokens) > 4 and tokens[0] == "+" and \ tokens[1] == "mip" and tokens[4] != "not": results.problem.lower_bound = tokens[5] elif len(tokens) == 6 and tokens[0] == "OPTIMAL" and \ tokens[1] == "SOLUTION" and tokens[5] == "PRESOLVER": stats.branch_and_bound.number_of_created_subproblems = 0 stats.branch_and_bound.number_of_bounded_subproblems = 0 soln.status = SolutionStatus.optimal elif len(tokens) == 7 and tokens[1] == "OPTIMAL" and \ tokens[2] == "SOLUTION" and tokens[6] == "PRESOLVER": stats.branch_and_bound.number_of_created_subproblems = 0 stats.branch_and_bound.number_of_bounded_subproblems = 0 soln.status = SolutionStatus.optimal elif len(tokens) > 10 and tokens[0] == "+" and \ tokens[8] == "empty": stats.branch_and_bound.number_of_created_subproblems = tokens[11][:-1] stats.branch_and_bound.number_of_bounded_subproblems = tokens[11][:-1] elif len(tokens) > 9 and tokens[0] == "+" and \ tokens[7] == "empty": stats.branch_and_bound.number_of_created_subproblems = tokens[10][:-1] stats.branch_and_bound.number_of_bounded_subproblems = tokens[10][:-1] elif len(tokens) == 2 and tokens[0] == "sys": results.solver.system_time = tokens[1] elif len(tokens) == 2 and tokens[0] == "user": results.solver.user_time = tokens[1] elif len(tokens) > 2 and tokens[0] == "OPTIMAL" and \ tokens[1] == "SOLUTION": soln.status = SolutionStatus.optimal elif len(tokens) > 2 and tokens[0] == "INTEGER" and \ tokens[1] == "OPTIMAL": soln.status = SolutionStatus.optimal stats.branch_and_bound.number_of_created_subproblems = 0 stats.branch_and_bound.number_of_bounded_subproblems = 0 elif len(tokens) > 2 and tokens[0] == "TIME" and \ tokens[2] == "EXCEEDED;": soln.status = SolutionStatus.stoppedByLimit if soln.status == SolutionStatus.optimal: results.solver.termination_condition = TerminationCondition.optimal elif soln.status == SolutionStatus.infeasible: results.solver.termination_condition = TerminationCondition.infeasible if results.problem.upper_bound == "inf": results.problem.upper_bound = 'Infinity' if results.problem.lower_bound == "-inf": results.problem.lower_bound = "-Infinity" try: val = results.problem.upper_bound tmp = eval(val.strip()) results.problem.upper_bound = str(tmp) except: pass try: val = results.problem.lower_bound tmp = eval(val.strip()) results.problem.lower_bound = str(tmp) except: pass if results.solver.status is SolverStatus.error: results.solution.delete(0) return results
def process_logfile(self): """ Process a logfile """ results = SolverResults() # # Initial values # #results.solver.statistics.branch_and_bound.number_of_created_subproblems=0 #results.solver.statistics.branch_and_bound.number_of_bounded_subproblems=0 soln = Solution() soln.objective['__default_objective__'] = {'Value': None} # # Process logfile # OUTPUT = open(self._log_file) output = "".join(OUTPUT.readlines()) OUTPUT.close() # # Parse logfile lines # for line in output.split("\n"): tokens = re.split('[ \t]+', line.strip()) if len(tokens) > 3 and tokens[0] == "ABORTED:": results.solver.status = SolverStatus.aborted elif len(tokens) > 1 and tokens[0].startswith("ERROR"): results.solver.status = SolverStatus.error elif len(tokens) == 3 and tokens[0] == 'Problem' and tokens[ 2].startswith('infeasible'): results.solver.termination_condition = TerminationCondition.infeasible elif len(tokens) == 2 and tokens[0] == 'Integer' and tokens[ 1] == 'Infeasible': results.solver.termination_condition = TerminationCondition.infeasible elif len(tokens) == 5 and tokens[0] == "Final" and tokens[ 1] == "Solution:": soln.objective['__default_objective__']['Value'] = eval( tokens[4]) soln.status = SolutionStatus.optimal elif len(tokens ) == 3 and tokens[0] == "LP" and tokens[1] == "value=": soln.objective['__default_objective__']['Value'] = eval( tokens[2]) soln.status = SolutionStatus.optimal if results.problem.sense == ProblemSense.minimize: results.problem.lower_bound = eval(tokens[2]) else: results.problem.upper_bound = eval(tokens[2]) elif len(tokens) == 2 and tokens[0] == "Bound:": if results.problem.sense == ProblemSense.minimize: results.problem.lower_bound = eval(tokens[1]) else: results.problem.upper_bound = eval(tokens[1]) elif len(tokens) == 3 and tokens[0] == "Created": results.solver.statistics.branch_and_bound.number_of_created_subproblems = eval( tokens[1]) elif len(tokens) == 3 and tokens[0] == "Bounded": results.solver.statistics.branch_and_bound.number_of_bounded_subproblems = eval( tokens[1]) elif len(tokens) == 2 and tokens[0] == "sys": results.solver.system_time = eval(tokens[1]) elif len(tokens) == 2 and tokens[0] == "user": results.solver.user_time = eval(tokens[1]) elif len(tokens) == 3 and tokens[0] == "Solving" and tokens[ 1] == "problem:": results.problem.name = tokens[2] elif len(tokens) == 4 and tokens[2] == "constraints:": results.problem.number_of_constraints = eval(tokens[3]) elif len(tokens) == 4 and tokens[2] == "variables:": results.problem.number_of_variables = eval(tokens[3]) elif len(tokens) == 4 and tokens[2] == "nonzeros:": results.problem.number_of_nonzeros = eval(tokens[3]) elif len(tokens) == 3 and tokens[1] == "Sense:": if tokens[2] == "minimization": results.problem.sense = ProblemSense.minimize else: results.problem.sense = ProblemSense.maximize if results.solver.status is SolverStatus.aborted: soln.optimality = SolutionStatus.unsure if soln.status is SolutionStatus.optimal: soln.gap = 0.0 results.problem.lower_bound = soln.objective[ '__default_objective__']['Value'] results.problem.upper_bound = soln.objective[ '__default_objective__']['Value'] if soln.status == SolutionStatus.optimal: results.solver.termination_condition = TerminationCondition.optimal if not results.solver.status is SolverStatus.error and \ results.solver.termination_condition in [TerminationCondition.unknown, #TerminationCondition.maxIterations, #TerminationCondition.minFunctionValue, #TerminationCondition.minStepLength, TerminationCondition.globallyOptimal, TerminationCondition.locallyOptimal, TerminationCondition.optimal, #TerminationCondition.maxEvaluations, TerminationCondition.other]: results.solution.insert(soln) return results
def solve(self, *args, **kwds): """ Uses GAMS Python API. For installation help visit: https://www.gams.com/latest/docs/apis/examples_python/index.html tee=False: Output GAMS log to stdout. load_solutions=True: Does not support load_solutions=False. keepfiles=False: Keep temporary files. Equivalent of DebugLevel.KeepFiles. Summary of temp files can be found in _gams_py_gjo0.pf tmpdir=None: Specify directory path for storing temporary files. A directory will be created if one of this name doesn't exist. io_options: Updated with additional keywords passed to solve() warmstart=False: Warmstart by initializing model's variables to their values. symbolic_solver_labels=False: Use full Pyomo component names rather than shortened symbols (slower, but useful for debugging). labeler=None: Custom labeler option. Incompatible with symbolic_solver_labels. solver=None: If None, GAMS will use default solver for model type. mtype=None: Model type. If None, will chose from lp, nlp, mip, and minlp. add_options=None: List of additional lines to write directly into model file before the solve statement. For model attributes, <model name> is GAMS_MODEL. skip_trivial_constraints=False: Skip writing constraints whose body section is fixed file_determinism=1: How much effort do we want to put into ensuring the LP file is written deterministically for a Pyomo model: 0 : None 1 : sort keys of indexed components (default) 2 : sort keys AND sort names (over declaration order) put_results=None: Filename for optionally writing solution values and marginals to (put_results).dat, and solver statuses to (put_results + 'stat').dat. """ # Make sure available() doesn't crash self.available() from gams import GamsWorkspace, DebugLevel from gams.workspace import GamsExceptionExecution if len(args) != 1: raise ValueError('Exactly one model must be passed ' 'to solve method of GAMSSolver.') model = args[0] load_solutions = kwds.pop("load_solutions", True) tee = kwds.pop("tee", False) keepfiles = kwds.pop("keepfiles", False) tmpdir = kwds.pop("tmpdir", None) io_options = kwds.pop("io_options", {}) if len(kwds): # Pass remaining keywords to writer, which will handle # any unrecognized arguments io_options.update(kwds) #################################################################### # Presolve #################################################################### # Create StringIO stream to pass to gams_writer, on which the # model file will be written. The writer also passes this StringIO # back, but output_file is defined in advance for clarity. output_file = StringIO() if isinstance(model, IBlockStorage): # Kernel blocks have slightly different write method smap_id = model.write(filename=output_file, format=ProblemFormat.gams, _called_by_solver=True, **io_options) symbolMap = getattr(model, "._symbol_maps")[smap_id] else: (_, smap_id) = model.write(filename=output_file, format=ProblemFormat.gams, io_options=io_options) symbolMap = model.solutions.symbol_map[smap_id] #################################################################### # Apply solver #################################################################### # IMPORTANT - only delete the whole tmpdir if the solver was the one # that made the directory. Otherwise, just delete the files the solver # made, if not keepfiles. That way the user can select a directory # they already have, like the current directory, without having to # worry about the rest of the contents of that directory being deleted. newdir = True if tmpdir is not None and os.path.exists(tmpdir): newdir = False ws = GamsWorkspace( debug=DebugLevel.KeepFiles if keepfiles else DebugLevel.Off, working_directory=tmpdir) t1 = ws.add_job_from_string(output_file.getvalue()) try: t1.run(output=sys.stdout if tee else None) except GamsExceptionExecution: try: check_expr_evaluation(model, symbolMap, 'direct') finally: # Always name working directory or delete files, # regardless of any errors. if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted t1 = rec = rec_lo = rec_hi = None file_removal_gams_direct(tmpdir, newdir) raise except: # Catch other errors and remove files first if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted t1 = rec = rec_lo = rec_hi = None file_removal_gams_direct(tmpdir, newdir) raise #################################################################### # Postsolve #################################################################### # import suffixes must be on the top-level model if isinstance(model, IBlockStorage): model_suffixes = list(name for (name,comp) \ in pyomo.core.kernel.component_suffix.\ import_suffix_generator(model, active=True, descend_into=False, return_key=True)) else: model_suffixes = list(name for (name,comp) \ in pyomo.core.base.suffix.\ active_import_suffix_generator(model)) extract_dual = ('dual' in model_suffixes) extract_rc = ('rc' in model_suffixes) results = SolverResults() results.problem.name = t1.name results.problem.lower_bound = t1.out_db["OBJEST"].find_record().value results.problem.upper_bound = t1.out_db["OBJEST"].find_record().value results.problem.number_of_variables = \ t1.out_db["NUMVAR"].find_record().value results.problem.number_of_constraints = \ t1.out_db["NUMEQU"].find_record().value results.problem.number_of_nonzeros = \ t1.out_db["NUMNZ"].find_record().value results.problem.number_of_binary_variables = None # Includes binary vars: results.problem.number_of_integer_variables = \ t1.out_db["NUMDVAR"].find_record().value results.problem.number_of_continuous_variables = \ t1.out_db["NUMVAR"].find_record().value \ - t1.out_db["NUMDVAR"].find_record().value results.problem.number_of_objectives = 1 # required by GAMS writer obj = list(model.component_data_objects(Objective, active=True)) assert len(obj) == 1, 'Only one objective is allowed.' obj = obj[0] objctvval = t1.out_db["OBJVAL"].find_record().value if obj.is_minimizing(): results.problem.sense = ProblemSense.minimize results.problem.upper_bound = objctvval else: results.problem.sense = ProblemSense.maximize results.problem.lower_bound = objctvval results.solver.name = "GAMS " + str(self.version()) # Init termination condition to None to give preference to this first # block of code, only set certain TC's below if it's still None results.solver.termination_condition = None results.solver.message = None solvestat = t1.out_db["SOLVESTAT"].find_record().value if solvestat == 1: results.solver.status = SolverStatus.ok elif solvestat == 2: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxIterations elif solvestat == 3: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit elif solvestat == 5: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations elif solvestat == 7: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.licensingProblems elif solvestat == 8: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.userInterrupt elif solvestat == 10: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.solverFailure elif solvestat == 11: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.internalSolverError elif solvestat == 4: results.solver.status = SolverStatus.warning results.solver.message = "Solver quit with a problem (see LST file)" elif solvestat in (9, 12, 13): results.solver.status = SolverStatus.error elif solvestat == 6: results.solver.status = SolverStatus.unknown results.solver.return_code = 0 # Not sure if this value is actually user time # "the elapsed time it took to execute a solve statement in total" results.solver.user_time = t1.out_db["ETSOLVE"].find_record().value results.solver.system_time = None results.solver.wallclock_time = None results.solver.termination_message = None soln = Solution() modelstat = t1.out_db["MODELSTAT"].find_record().value if modelstat == 1: results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 2: results.solver.termination_condition = TerminationCondition.locallyOptimal soln.status = SolutionStatus.locallyOptimal elif modelstat in [3, 18]: results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif modelstat in [4, 5, 6, 10, 19]: results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif modelstat == 7: results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif modelstat == 8: # 'Integer solution model found' results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 9: results.solver.termination_condition = TerminationCondition.intermediateNonInteger soln.status = SolutionStatus.other elif modelstat == 11: # Should be handled above, if modelstat and solvestat both # indicate a licensing problem if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.licensingProblems soln.status = SolutionStatus.error elif modelstat in [12, 13]: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif modelstat == 14: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.noSolution soln.status = SolutionStatus.unknown elif modelstat in [15, 16, 17]: # Having to do with CNS models, # not sure what to make of status descriptions results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.unsure else: # This is just a backup catch, all cases are handled above soln.status = SolutionStatus.error soln.gap = abs(results.problem.upper_bound \ - results.problem.lower_bound) for sym, ref in iteritems(symbolMap.bySymbol): obj = ref() if isinstance(model, IBlockStorage): # Kernel variables have no 'parent_component' if obj.ctype is Objective: soln.objective[sym] = {'Value': objctvval} if obj.ctype is not Var: continue else: if obj.parent_component().type() is Objective: soln.objective[sym] = {'Value': objctvval} if obj.parent_component().type() is not Var: continue rec = t1.out_db[sym].find_record() # obj.value = rec.level soln.variable[sym] = {"Value": rec.level} if extract_rc and not math.isnan(rec.marginal): # Do not set marginals to nan # model.rc[obj] = rec.marginal soln.variable[sym]['rc'] = rec.marginal if extract_dual: for c in model.component_data_objects(Constraint, active=True): if c.body.is_fixed(): continue sym = symbolMap.getSymbol(c) if c.equality: rec = t1.out_db[sym].find_record() if not math.isnan(rec.marginal): # model.dual[c] = rec.marginal soln.constraint[sym] = {'dual': rec.marginal} else: # Solver didn't provide marginals, # nothing else to do here break else: # Inequality, assume if 2-sided that only # one side's marginal is nonzero # Negate marginal for _lo equations marg = 0 if c.lower is not None: rec_lo = t1.out_db[sym + '_lo'].find_record() marg -= rec_lo.marginal if c.upper is not None: rec_hi = t1.out_db[sym + '_hi'].find_record() marg += rec_hi.marginal if not math.isnan(marg): # model.dual[c] = marg soln.constraint[sym] = {'dual': marg} else: # Solver didn't provide marginals, # nothing else to do here break results.solution.insert(soln) if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted t1 = rec = rec_lo = rec_hi = None file_removal_gams_direct(tmpdir, newdir) #################################################################### # Finish with results #################################################################### results._smap_id = smap_id results._smap = None if isinstance(model, IBlockStorage): if len(results.solution) == 1: results.solution(0).symbol_map = \ getattr(model, "._symbol_maps")[results._smap_id] results.solution(0).default_variable_value = \ self._default_variable_value if load_solutions: model.load_solution(results.solution(0)) results.solution.clear() else: assert len(results.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(model, "._symbol_maps")) == 1 delattr(model, "._symbol_maps") del results._smap_id else: if load_solutions: model.solutions.load_from(results) results._smap_id = None results.solution.clear() else: results._smap = model.solutions.symbol_map[smap_id] model.solutions.delete_symbol_map(smap_id) return results
def process_logfile(self): """ Process logfile """ results = SolverResults() # The logfile output for cbc when using nl files # provides no information worth parsing here if self._problem_format is ProblemFormat.nl: return results # # Initial values # soln = Solution() # # Process logfile # OUTPUT = open(self._log_file) output = "".join(OUTPUT.readlines()) OUTPUT.close() # # Parse logfile lines # results.problem.sense = ProblemSense.minimize results.problem.name = None optim_value = float('inf') lower_bound = None upper_bound = None gap = None nodes = None # See https://www.coin-or.org/Cbc/cbcuserguide.html#messages for line in output.split("\n"): tokens = tuple(re.split('[ \t]+', line.strip())) n_tokens = len(tokens) if n_tokens > 1: # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L3769 if n_tokens > 4 and tokens[:4] == ('Continuous', 'objective', 'value', 'is'): lower_bound = float(tokens[4]) # Search completed - best objective %g, took %d iterations and %d nodes elif n_tokens > 12 and tokens[1:3] == ('Search', 'completed') \ and tokens[4:6] == ('best', 'objective') and tokens[9] == 'iterations' \ and tokens[12] == 'nodes': optim_value = float(tokens[6][:-1]) results.solver.statistics.black_box.number_of_iterations = int( tokens[8]) nodes = int(tokens[11]) elif tokens[1] == 'Exiting' and n_tokens > 4: if tokens[2:4] == ('on', 'maximum'): results.solver.termination_condition = { 'nodes': TerminationCondition.maxEvaluations, 'time': TerminationCondition.maxTimeLimit, 'solutions': TerminationCondition.other, 'iterations': TerminationCondition.maxIterations }.get(tokens[4], TerminationCondition.other) # elif tokens[2:5] == ('as', 'integer', 'gap'): # # We might want to handle this case # Integer solution of %g found... elif n_tokens >= 4 and tokens[1:4] == ('Integer', 'solution', 'of'): optim_value = float(tokens[4]) try: results.solver.statistics.black_box.number_of_iterations = \ int(tokens[tokens.index('iterations') - 1]) nodes = int(tokens[tokens.index('nodes') - 1]) except ValueError: pass # Partial search - best objective %g (best possible %g), took %d iterations and %d nodes elif n_tokens > 15 and tokens[1:3] == ('Partial', 'search') \ and tokens[4:6] == ('best', 'objective') and tokens[7:9] == ('(best', 'possible') \ and tokens[12] == 'iterations' and tokens[15] == 'nodes': optim_value = float(tokens[6]) lower_bound = float(tokens[9][:-2]) results.solver.statistics.black_box.number_of_iterations = int( tokens[11]) nodes = int(tokens[14]) elif n_tokens > 12 and tokens[1] == 'After' and tokens[3] == 'nodes,' \ and tokens[8:10] == ('best', 'solution,') and tokens[10:12] == ('best', 'possible'): nodes = int(tokens[2]) optim_value = float(tokens[7]) lower_bound = float(tokens[12]) elif tokens[0] == "Current" and n_tokens == 10 and tokens[1] == "default" and tokens[2] == "(if" \ and results.problem.name is None: results.problem.name = tokens[-1] if '.' in results.problem.name: parts = results.problem.name.split('.') if len(parts) > 2: results.problem.name = '.'.join(parts[:-1]) else: results.problem.name = results.problem.name.split( '.')[0] if '/' in results.problem.name: results.problem.name = results.problem.name.split( '/')[-1] if '\\' in results.problem.name: results.problem.name = results.problem.name.split( '\\')[-1] # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L10840 elif tokens[0] == 'Presolve': if n_tokens > 9 and tokens[3] == 'rows,' and tokens[ 6] == 'columns': results.problem.number_of_variables = int( tokens[4]) - int(tokens[5][1:-1]) results.problem.number_of_constraints = int( tokens[1]) - int(tokens[2][1:-1]) results.problem.number_of_objectives = 1 elif n_tokens > 6 and tokens[6] == 'infeasible': soln.status = SolutionStatus.infeasible # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L11105 elif n_tokens > 11 and tokens[:2] == ('Problem', 'has') and tokens[3] == 'rows,' and \ tokens[5] == 'columns' and tokens[7:9] == ('with', 'objective)'): results.problem.number_of_variables = int(tokens[4]) results.problem.number_of_constraints = int(tokens[2]) results.problem.number_of_nonzeros = int(tokens[6][1:]) results.problem.number_of_objectives = 1 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L10814 elif n_tokens > 8 and tokens[:3] == ('Original', 'problem', 'has') and tokens[4] == 'integers' \ and tokens[6:9] == ('of', 'which', 'binary)'): results.problem.number_of_integer_variables = int( tokens[3]) results.problem.number_of_binary_variables = int( tokens[5][1:]) elif n_tokens == 5 and tokens[3] == "NAME": results.problem.name = tokens[4] elif 'CoinLpIO::readLp(): Maximization problem reformulated as minimization' in ' '.join( tokens): results.problem.sense = ProblemSense.maximize # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L3047 elif n_tokens > 3 and tokens[:2] == ('Result', '-'): if tokens[2:4] in [('Run', 'abandoned'), ('User', 'ctrl-c')]: results.solver.termination_condition = TerminationCondition.userInterrupt if n_tokens > 4: if tokens[2:5] == ('Optimal', 'solution', 'found'): # parser for log file generetated with discrete variable soln.status = SolutionStatus.optimal # if n_tokens > 7 and tokens[5:8] == ('(within', 'gap', 'tolerance)'): # # We might want to handle this case elif tokens[2:5] in [ ('Linear', 'relaxation', 'infeasible'), ('Problem', 'proven', 'infeasible') ]: soln.status = SolutionStatus.infeasible elif tokens[2:5] == ('Linear', 'relaxation', 'unbounded'): soln.status = SolutionStatus.unbounded elif n_tokens > 5 and tokens[2:4] == ( 'Stopped', 'on') and tokens[5] == 'limit': results.solver.termination_condition = { 'node': TerminationCondition.maxEvaluations, 'time': TerminationCondition.maxTimeLimit, 'solution': TerminationCondition.other, 'iterations': TerminationCondition.maxIterations }.get(tokens[4], TerminationCondition.other) # perhaps from https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L12318 elif n_tokens > 3 and tokens[2] == "Finished": soln.status = SolutionStatus.optimal optim_value = float(tokens[4]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7904 elif n_tokens >= 3 and tokens[:2] == ('Objective', 'value:'): # parser for log file generetated with discrete variable optim_value = float(tokens[2]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7904 elif n_tokens >= 4 and tokens[:4] == ('No', 'feasible', 'solution', 'found'): soln.status = SolutionStatus.infeasible elif n_tokens > 2 and tokens[:2] == ('Lower', 'bound:'): if lower_bound is None: # Only use if not already found since this is to less decimal places results.problem.lower_bound = float(tokens[2]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7918 elif tokens[0] == 'Gap:': # This is relative and only to 2 decimal places - could calculate explicitly using lower bound gap = float(tokens[1]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7923 elif n_tokens > 2 and tokens[:2] == ('Enumerated', 'nodes:'): nodes = int(tokens[2]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7926 elif n_tokens > 2 and tokens[:2] == ('Total', 'iterations:'): results.solver.statistics.black_box.number_of_iterations = int( tokens[2]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7930 elif n_tokens > 3 and tokens[:3] == ('Time', '(CPU', 'seconds):'): results.solver.system_time = float(tokens[3]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7933 elif n_tokens > 3 and tokens[:3] == ('Time', '(Wallclock', 'Seconds):'): results.solver.wallclock_time = float(tokens[3]) # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L10477 elif n_tokens > 4 and tokens[:4] == ('Total', 'time', '(CPU', 'seconds):'): results.solver.system_time = float(tokens[4]) if n_tokens > 7 and tokens[5:7] == ('(Wallclock', 'seconds):'): results.solver.wallclock_time = float(tokens[7]) elif tokens[0] == "Optimal": if n_tokens > 4 and tokens[ 2] == "objective" and tokens[4] != "and": # parser for log file generetated without discrete variable # see pull request #339: last check avoids lines like "Optimal - objective gap and # complementarity gap both smallish and small steps" soln.status = SolutionStatus.optimal optim_value = float(tokens[4]) elif n_tokens > 5 and tokens[1] == 'objective' and tokens[ 5] == 'iterations': soln.status = SolutionStatus.optimal optim_value = float(tokens[2]) results.solver.statistics.black_box.number_of_iterations = int( tokens[4]) elif tokens[0] == "sys" and n_tokens == 2: results.solver.system_time = float(tokens[1]) elif tokens[0] == "user" and n_tokens == 2: results.solver.user_time = float(tokens[1]) elif n_tokens == 10 and "Presolve" in tokens and \ "iterations" in tokens and tokens[0] == "Optimal" and "objective" == tokens[1]: soln.status = SolutionStatus.optimal optim_value = float(tokens[2]) results.solver.user_time = -1.0 # Why is this set to -1? if results.problem.name is None: results.problem.name = 'unknown' if soln.status is SolutionStatus.optimal: results.solver.termination_message = "Model was solved to optimality (subject to tolerances), and an " \ "optimal solution is available." results.solver.termination_condition = TerminationCondition.optimal results.solver.status = SolverStatus.ok if gap is None: lower_bound = optim_value gap = 0.0 elif soln.status == SolutionStatus.infeasible: results.solver.termination_message = "Model was proven to be infeasible." results.solver.termination_condition = TerminationCondition.infeasible results.solver.status = SolverStatus.warning elif soln.status == SolutionStatus.unbounded: results.solver.termination_message = "Model was proven to be unbounded." results.solver.termination_condition = TerminationCondition.unbounded results.solver.status = SolverStatus.warning elif results.solver.termination_condition in [ TerminationCondition.maxTimeLimit, TerminationCondition.maxEvaluations, TerminationCondition.other, TerminationCondition.maxIterations ]: results.solver.status = SolverStatus.aborted soln.status = SolutionStatus.stoppedByLimit if results.solver.termination_condition == TerminationCondition.maxTimeLimit: results.solver.termination_message = "Optimization terminated because the time expended " \ "exceeded the value specified in the seconds " \ "parameter." elif results.solver.termination_condition == TerminationCondition.maxEvaluations: results.solver.termination_message = \ "Optimization terminated because the total number of branch-and-cut nodes explored " \ "exceeded the value specified in the maxNodes parameter" elif results.solver.termination_condition == TerminationCondition.other: results.solver.termination_message = "Optimization terminated because the number of " \ "solutions found reached the value specified in the " \ "maxSolutions parameter." elif results.solver.termination_condition == TerminationCondition.maxIterations: results.solver.termination_message = "Optimization terminated because the total number of simplex " \ "iterations performed exceeded the value specified in the " \ "maxIterations parameter." soln.gap = gap if results.problem.sense == ProblemSense.minimize: upper_bound = optim_value elif results.problem.sense == ProblemSense.maximize: optim_value *= -1 upper_bound = None if lower_bound is None else -lower_bound lower_bound = optim_value soln.objective['__default_objective__'] = {'Value': optim_value} results.problem.lower_bound = lower_bound results.problem.upper_bound = upper_bound results.solver.statistics.branch_and_bound.number_of_bounded_subproblems = nodes results.solver.statistics.branch_and_bound.number_of_created_subproblems = nodes if soln.status in [ SolutionStatus.optimal, SolutionStatus.stoppedByLimit, SolutionStatus.unknown, SolutionStatus.other ]: results.solution.insert(soln) return results
def _postsolve(self): lp = self._glpk_instance num_variables = glp_get_num_cols(lp) bin_variables = glp_get_num_bin(lp) int_variables = glp_get_num_int(lp) # check suffixes for suffix in self._suffixes: if True: raise RuntimeError( "***The glpk_direct solver plugin cannot extract solution suffix=" + suffix) tpeak = glp_long() glp_mem_usage(None, None, None, tpeak) # black magic trickery, thanks to Python's lack of pointers and SWIG's # automatic API conversion peak_mem = tpeak.lo results = SolverResults() soln = Solution() prob = results.problem solv = results.solver solv.name = "GLPK " + glp_version() solv.status = self._glpk_get_solver_status() solv.return_code = self.solve_return_code solv.message = self._glpk_return_code_to_message() solv.algorithm = self.algo solv.memory_used = "%d bytes, (%d KiB)" % (peak_mem, peak_mem / 1024) # solv.user_time = None # solv.system_time = None solv.wallclock_time = self._glpk_solve_time # solv.termination_condition = None # solv.termination_message = None prob.name = glp_get_prob_name(lp) prob.number_of_constraints = glp_get_num_rows(lp) prob.number_of_nonzeros = glp_get_num_nz(lp) prob.number_of_variables = num_variables prob.number_of_binary_variables = bin_variables prob.number_of_integer_variables = int_variables prob.number_of_continuous_variables = num_variables - int_variables prob.number_of_objectives = 1 prob.sense = ProblemSense.minimize if GLP_MAX == glp_get_obj_dir(lp): prob.sense = ProblemSense.maximize soln.status = self._glpk_get_solution_status() if soln.status in (SolutionStatus.optimal, SolutionStatus.feasible): get_col_prim = glp_get_col_prim get_row_prim = glp_get_row_prim get_obj_val = glp_get_obj_val if self.is_integer: get_col_prim = glp_mip_col_val get_row_prim = glp_mip_row_val get_obj_val = glp_mip_obj_val obj_val = get_obj_val(lp) if prob.sense == ProblemSense.minimize: prob.lower_bound = obj_val else: prob.upper_bound = obj_val objective_name = lp.objective_name soln.objective[objective_name] = {'Value': obj_val} colvar_map = self._glpk_colvar_map rowvar_map = self._glpk_rowvar_map for var_label in colvar_map: col = colvar_map[var_label] soln.variable[var_label] = {"Value": get_col_prim(lp, col)} for row_label in rowvar_map: row = rowvar_map[row_label] soln.constraint[row_label] = {"Value": get_row_prim(lp, row)} results.solution.insert(soln) self.results = results # All done with the GLPK object, so free up some memory. glp_free(lp) del self._glpk_instance, lp # let the base class deal with returning results. return OptSolver._postsolve(self)
def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. This function performs all of the GDPopt solver setup and problem validation. It then calls upon helper functions to construct the initial master approximation and iteration loop. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) solve_data = GDPoptSolveData() solve_data.results = SolverResults() solve_data.timing = Container() old_logger_level = config.logger.getEffectiveLevel() with time_code(solve_data.timing, 'total'), \ restore_logger_level(config.logger), \ create_utility_block(model, 'GDPopt_utils'): if config.tee and old_logger_level > logging.INFO: # If the logger does not already include INFO, include it. config.logger.setLevel(logging.INFO) config.logger.info("---Starting GDPopt---") solve_data.original_model = model solve_data.working_model = clone_orig_model_with_lists(model) GDPopt = solve_data.working_model.GDPopt_utils record_original_model_statistics(solve_data, config) solve_data.current_strategy = config.strategy # Reformulate integer variables to binary reformulate_integer_variables(solve_data.working_model, config) process_objective(solve_data, config) # Save ordered lists of main modeling components, so that data can # be easily transferred between future model clones. build_ordered_component_lists(solve_data.working_model) record_working_model_statistics(solve_data, config) solve_data.results.solver.name = 'GDPopt ' + str(self.version()) # Save model initial values. These are used later to initialize NLP # subproblems. solve_data.initial_var_values = list( v.value for v in GDPopt.working_var_list) # Store the initial model state as the best solution found. If we # find no better solution, then we will restore from this copy. solve_data.best_solution_found = solve_data.initial_var_values # Validate the model to ensure that GDPopt is able to solve it. if not model_is_valid(solve_data, config): return # Maps in order to keep track of certain generated constraints GDPopt.oa_cut_map = ComponentMap() # Integer cuts exclude particular discrete decisions GDPopt.integer_cuts = ConstraintList(doc='integer cuts') # Feasible integer cuts exclude discrete realizations that have # been explored via an NLP subproblem. Depending on model # characteristics, the user may wish to revisit NLP subproblems # (with a different initialization, for example). Therefore, these # cuts are not enabled by default, unless the initial model has no # discrete decisions. # Note: these cuts will only exclude integer realizations that are # not already in the primary GDPopt_integer_cuts ConstraintList. GDPopt.no_backtracking = ConstraintList( doc='explored integer cuts') # Set up iteration counters solve_data.master_iteration = 0 solve_data.mip_iteration = 0 solve_data.nlp_iteration = 0 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.iteration_log = {} # Flag indicating whether the solution improved in the past # iteration or not solve_data.feasible_solution_improved = False # Initialize the master problem with time_code(solve_data.timing, 'initialization'): GDPopt_initialize_master(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): GDPopt_iteration_loop(solve_data, config) # Update values in working model copy_var_list_values(from_list=solve_data.best_solution_found, to_list=GDPopt.working_var_list, config=config) GDPopt.objective_value.set_value( value(solve_data.working_objective_expr, exception=False)) # Update values in original model copy_var_list_values( GDPopt.orig_var_list, solve_data.original_model.GDPopt_utils.orig_var_list, config) solve_data.results.problem.lower_bound = solve_data.LB solve_data.results.problem.upper_bound = solve_data.UB solve_data.results.solver.timing = solve_data.timing return solve_data.results
def _solve(self, problem_id): """Return None if solution is infeasible or Solution dict otherwise""" if SOLVER == "" or SOLVER is None: if pyo.SolverFactory('cplex').available(): slv = pyo.SolverFactory('cplex') elif pyo.SolverFactory('glpk').available(): slv = pyo.SolverFactory('glpk') else: raise Exception("Solver not available") else: if pyo.SolverFactory(SOLVER).available(): slv = pyo.SolverFactory(SOLVER) else: raise Exception("Solver not available") results = SolverResults() r = slv.solve(self._diet, tee=False) # r = slv.solve(self._diet, tee=True) results.load(r) if not (results.solver.status == pyo.SolverStatus.ok and results.solver.termination_condition == pyo.TerminationCondition.optimal): logging.info("Solution status: {}".format( results.solver.termination_condition)) self._infeasible_output(problem_id) return None sol_id = { "Problem_ID": problem_id, "Feeding Time": self.parameters.c_model_feeding_time, "Initial weight": self.parameters.p_sbw, "Final weight": self.parameters.c_model_final_weight } params = self._get_params(self.parameters.c_swg) sol = dict( zip([ f"x{i} - {self.data.d_name_ing_map[i]}" for i in self._diet.v_x ], [self._diet.v_x[i].value for i in self._diet.v_x])) sol["obj_func"] = self._diet.f_obj.expr() sol["obj_cost"] = -self._diet.f_obj.expr() + self.computed.cst_obj if self.parameters.p_obj == "MaxProfitSWG" or self.parameters.p_obj == "MinCostSWG": sol["obj_cost"] *= self.parameters.c_swg sol["obj_revenue"] = self.computed.revenue sol["MP diet"] = self._diet.c_mpm.body() x_sol = [self._diet.v_x[i].value for i in self._diet.v_x] sol["NPN"] = sum([ list(self.data.dc_npn.values())[i] * x_sol[i] * 0.01 for i in range(len(x_sol)) ]) sol["RDP"] = sum([ list(self.data.dc_rdp.values())[i] * x_sol[i] for i in range(len(x_sol)) ]) is_active_constraints = [] l_slack = {} u_slack = {} lower = {} upper = {} duals = {} for c in self._diet.component_objects(pyo.Constraint): is_active_constraints.append(c.active) if c.active: duals["{}_dual".format(c)] = self._diet.dual[c] l_slack["{}_lslack".format(c)] = c.lslack() u_slack["{}_uslack".format(c)] = c.uslack() if c.has_lb(): lower["{}_lower".format(c)] = c.lower() upper["{}_upper".format(c)] = "None" else: lower["{}_lower".format(c)] = "None" upper["{}_upper".format(c)] = c.upper() else: duals["{}_dual".format(c)] = "None" l_slack["{}_lslack".format(c)] = "None" u_slack["{}_uslack".format(c)] = "None" lower["{}_lower".format(c)] = "None" upper["{}_upper".format(c)] = "None" sol_red_cost = dict( zip(["x{}_red_cost".format(i) for i in self._diet.v_x], [self._diet.rc[self._diet.v_x[i]] for i in self._diet.v_x])) sol_fat_orient = {"fat orient": self.parameters.p_fat_orient} sol = { **sol_id, **params, **sol, **sol_red_cost, **duals, **sol_fat_orient, **l_slack, **u_slack, **lower, **upper } return sol
def solve(self, *args, **kwds): """ Uses command line to call GAMS. tee=False: Output GAMS log to stdout. load_solutions=True: Does not support load_solutions=False. keepfiles=False: Keep temporary files. tmpdir=None: Specify directory path for storing temporary files. A directory will be created if one of this name doesn't exist. io_options: Updated with additional keywords passed to solve() warmstart=False: Warmstart by initializing model's variables to their values. symbolic_solver_labels=False: Use full Pyomo component names rather than shortened symbols (slower, but useful for debugging). labeler=None: Custom labeler. Incompatible with symbolic_solver_labels. solver=None: If None, GAMS will use default solver for model type. mtype=None: Model type. If None, will chose from lp, nlp, mip, and minlp. add_options=None: List of additional lines to write directly into model file before the solve statement. For model attributes, <model name> is GAMS_MODEL. skip_trivial_constraints=False: Skip writing constraints whose body section is fixed file_determinism=1: How much effort do we want to put into ensuring the LP file is written deterministically for a Pyomo model: 0 : None 1 : sort keys of indexed components (default) 2 : sort keys AND sort names (over declaration order) put_results='results': Not available for modification on GAMSShell solver. """ # Make sure available() doesn't crash self.available() if len(args) != 1: raise ValueError('Exactly one model must be passed ' 'to solve method of GAMSSolver.') model = args[0] load_solutions = kwds.pop("load_solutions", True) tee = kwds.pop("tee", False) keepfiles = kwds.pop("keepfiles", False) tmpdir = kwds.pop("tmpdir", None) io_options = kwds.pop("io_options", {}) if len(kwds): # Pass remaining keywords to writer, which will handle # any unrecognized arguments io_options.update(kwds) #################################################################### # Presolve #################################################################### # IMPORTANT - only delete the whole tmpdir if the solver was the one # that made the directory. Otherwise, just delete the files the solver # made, if not keepfiles. That way the user can select a directory # they already have, like the current directory, without having to # worry about the rest of the contents of that directory being deleted. newdir = False if tmpdir is None: tmpdir = mkdtemp() newdir = True elif not os.path.exists(tmpdir): # makedirs creates all necessary intermediate directories in order # to create the path to tmpdir, if they don't already exist. # However, if keepfiles is False, we only delete the final folder, # leaving the rest of the intermediate ones. os.makedirs(tmpdir) newdir = True output_filename = os.path.join(tmpdir, 'model.gms') lst_filename = os.path.join(tmpdir, 'output.lst') statresults_filename = os.path.join(tmpdir, 'resultsstat.dat') io_options['put_results'] = os.path.join(tmpdir, 'results') results_filename = os.path.join(tmpdir, 'results.dat') if isinstance(model, IBlockStorage): # Kernel blocks have slightly different write method smap_id = model.write(filename=output_filename, format=ProblemFormat.gams, _called_by_solver=True, **io_options) symbolMap = getattr(model, "._symbol_maps")[smap_id] else: (_, smap_id) = model.write(filename=output_filename, format=ProblemFormat.gams, io_options=io_options) symbolMap = model.solutions.symbol_map[smap_id] #################################################################### # Apply solver #################################################################### exe = self.executable() command = [exe, output_filename, 'o=' + lst_filename] if not tee: command.append("lo=0") try: rc = subprocess.call(command) if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % tmpdir) if rc == 1 or rc == 127: raise RuntimeError("Command 'gams' was not recognized") elif rc != 0: if rc == 3: # Execution Error # Run check_expr_evaluation, which errors if necessary check_expr_evaluation(model, symbolMap, 'shell') # If nothing was raised, or for all other cases, raise this raise RuntimeError("GAMS encountered an error during solve. " "Check listing file for details.") with open(results_filename, 'r') as results_file: results_text = results_file.read() with open(statresults_filename, 'r') as statresults_file: statresults_text = statresults_file.read() finally: if not keepfiles: if newdir: shutil.rmtree(tmpdir) else: os.remove(output_filename) os.remove(lst_filename) os.remove(results_filename) os.remove(statresults_filename) #################################################################### # Postsolve #################################################################### # import suffixes must be on the top-level model if isinstance(model, IBlockStorage): model_suffixes = list(name for (name,comp) \ in pyomo.core.kernel.component_suffix.\ import_suffix_generator(model, active=True, descend_into=False, return_key=True)) else: model_suffixes = list(name for (name,comp) \ in pyomo.core.base.suffix.\ active_import_suffix_generator(model)) extract_dual = ('dual' in model_suffixes) extract_rc = ('rc' in model_suffixes) stat_vars = dict() # Skip first line of explanatory text for line in statresults_text.splitlines()[1:]: items = line.split() try: stat_vars[items[0]] = float(items[1]) except ValueError: # GAMS printed NA, just make it nan stat_vars[items[0]] = float('nan') results = SolverResults() results.problem.name = output_filename results.problem.lower_bound = stat_vars["OBJEST"] results.problem.upper_bound = stat_vars["OBJEST"] results.problem.number_of_variables = stat_vars["NUMVAR"] results.problem.number_of_constraints = stat_vars["NUMEQU"] results.problem.number_of_nonzeros = stat_vars["NUMNZ"] results.problem.number_of_binary_variables = None # Includes binary vars: results.problem.number_of_integer_variables = stat_vars["NUMDVAR"] results.problem.number_of_continuous_variables = stat_vars["NUMVAR"] \ - stat_vars["NUMDVAR"] results.problem.number_of_objectives = 1 # required by GAMS writer obj = list(model.component_data_objects(Objective, active=True)) assert len(obj) == 1, 'Only one objective is allowed.' obj = obj[0] objctvval = stat_vars["OBJVAL"] if obj.is_minimizing(): results.problem.sense = ProblemSense.minimize results.problem.upper_bound = objctvval else: results.problem.sense = ProblemSense.maximize results.problem.lower_bound = objctvval results.solver.name = "GAMS " + str(self.version()) # Init termination condition to None to give preference to this first # block of code, only set certain TC's below if it's still None results.solver.termination_condition = None results.solver.message = None solvestat = stat_vars["SOLVESTAT"] if solvestat == 1: results.solver.status = SolverStatus.ok elif solvestat == 2: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxIterations elif solvestat == 3: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit elif solvestat == 5: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations elif solvestat == 7: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.licensingProblems elif solvestat == 8: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.userInterrupt elif solvestat == 10: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.solverFailure elif solvestat == 11: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.internalSolverError elif solvestat == 4: results.solver.status = SolverStatus.warning results.solver.message = "Solver quit with a problem (see LST file)" elif solvestat in (9, 12, 13): results.solver.status = SolverStatus.error elif solvestat == 6: results.solver.status = SolverStatus.unknown results.solver.return_code = rc # 0 # Not sure if this value is actually user time # "the elapsed time it took to execute a solve statement in total" results.solver.user_time = stat_vars["ETSOLVE"] results.solver.system_time = None results.solver.wallclock_time = None results.solver.termination_message = None soln = Solution() modelstat = stat_vars["MODELSTAT"] if modelstat == 1: results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 2: results.solver.termination_condition = TerminationCondition.locallyOptimal soln.status = SolutionStatus.locallyOptimal elif modelstat in [3, 18]: results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif modelstat in [4, 5, 6, 10, 19]: results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif modelstat == 7: results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif modelstat == 8: # 'Integer solution model found' results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 9: results.solver.termination_condition = TerminationCondition.intermediateNonInteger soln.status = SolutionStatus.other elif modelstat == 11: # Should be handled above, if modelstat and solvestat both # indicate a licensing problem if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.licensingProblems soln.status = SolutionStatus.error elif modelstat in [12, 13]: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif modelstat == 14: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.noSolution soln.status = SolutionStatus.unknown elif modelstat in [15, 16, 17]: # Having to do with CNS models, # not sure what to make of status descriptions results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.unsure else: # This is just a backup catch, all cases are handled above soln.status = SolutionStatus.error soln.gap = abs(results.problem.upper_bound \ - results.problem.lower_bound) model_soln = dict() # Skip first line of explanatory text for line in results_text.splitlines()[1:]: items = line.split() model_soln[items[0]] = (items[1], items[2]) has_rc_info = True for sym, ref in iteritems(symbolMap.bySymbol): obj = ref() if isinstance(model, IBlockStorage): # Kernel variables have no 'parent_component' if obj.ctype is Objective: soln.objective[sym] = {'Value': objctvval} if obj.ctype is not Var: continue else: if obj.parent_component().type() is Objective: soln.objective[sym] = {'Value': objctvval} if obj.parent_component().type() is not Var: continue rec = model_soln[sym] # obj.value = float(rec[0]) soln.variable[sym] = {"Value": float(rec[0])} if extract_rc and has_rc_info: try: # model.rc[obj] = float(rec[1]) soln.variable[sym]['rc'] = float(rec[1]) except ValueError: # Solver didn't provide marginals has_rc_info = False if extract_dual: for c in model.component_data_objects(Constraint, active=True): if c.body.is_fixed(): continue sym = symbolMap.getSymbol(c) if c.equality: rec = model_soln[sym] try: # model.dual[c] = float(rec[1]) soln.constraint[sym] = {'dual': float(rec[1])} except ValueError: # Solver didn't provide marginals # nothing else to do here break else: # Inequality, assume if 2-sided that only # one side's marginal is nonzero # Negate marginal for _lo equations marg = 0 if c.lower is not None: rec_lo = model_soln[sym + '_lo'] try: marg -= float(rec_lo[1]) except ValueError: # Solver didn't provide marginals marg = float('nan') if c.upper is not None: rec_hi = model_soln[sym + '_hi'] try: marg += float(rec_hi[1]) except ValueError: # Solver didn't provide marginals marg = float('nan') if not math.isnan(marg): # model.dual[c] = marg soln.constraint[sym] = {'dual': marg} else: # Solver didn't provide marginals # nothing else to do here break results.solution.insert(soln) #################################################################### # Finish with results #################################################################### results._smap_id = smap_id results._smap = None if isinstance(model, IBlockStorage): if len(results.solution) == 1: results.solution(0).symbol_map = \ getattr(model, "._symbol_maps")[results._smap_id] results.solution(0).default_variable_value = \ self._default_variable_value if load_solutions: model.load_solution(results.solution(0)) results.solution.clear() else: assert len(results.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(model, "._symbol_maps")) == 1 delattr(model, "._symbol_maps") del results._smap_id else: if load_solutions: model.solutions.load_from(results) results._smap_id = None results.solution.clear() else: results._smap = model.solutions.symbol_map[smap_id] model.solutions.delete_symbol_map(smap_id) return results
def solve(self, *args, **kwds): """ Solve a model via the GAMS executable. Keyword Arguments ----------------- tee=False: bool Output GAMS log to stdout. logfile=None: str Filename to output GAMS log to a file. load_solutions=True: bool Load solution into model. If False, the results object will contain the solution data. keepfiles=False: bool Keep temporary files. tmpdir=None: str Specify directory path for storing temporary files. A directory will be created if one of this name doesn't exist. By default uses the system default temporary path. report_timing=False: bool Print timing reports for presolve, solver, postsolve, etc. io_options: dict Options that get passed to the writer. See writer in pyomo.repn.plugins.gams_writer for details. Updated with any other keywords passed to solve method. Note: put_results is not available for modification on GAMSShell solver. """ # Make sure available() doesn't crash self.available() if len(args) != 1: raise ValueError('Exactly one model must be passed ' 'to solve method of GAMSSolver.') model = args[0] # self.options are default for each run, overwritten by kwds options = dict() options.update(self.options) options.update(kwds) load_solutions = options.pop("load_solutions", True) tee = options.pop("tee", False) logfile = options.pop("logfile", None) keepfiles = options.pop("keepfiles", False) tmpdir = options.pop("tmpdir", None) report_timing = options.pop("report_timing", False) io_options = options.pop("io_options", {}) io_options.update(options) # Pass remaining keywords to writer, which will handle # any unrecognized arguments initial_time = time.time() #################################################################### # Presolve #################################################################### # IMPORTANT - only delete the whole tmpdir if the solver was the one # that made the directory. Otherwise, just delete the files the solver # made, if not keepfiles. That way the user can select a directory # they already have, like the current directory, without having to # worry about the rest of the contents of that directory being deleted. newdir = False if tmpdir is None: tmpdir = mkdtemp() newdir = True elif not os.path.exists(tmpdir): # makedirs creates all necessary intermediate directories in order # to create the path to tmpdir, if they don't already exist. # However, if keepfiles is False, we only delete the final folder, # leaving the rest of the intermediate ones. os.makedirs(tmpdir) newdir = True output = "model.gms" output_filename = os.path.join(tmpdir, output) lst = "output.lst" lst_filename = os.path.join(tmpdir, lst) put_results = "results" io_options["put_results"] = put_results results_filename = os.path.join(tmpdir, put_results + ".dat") statresults_filename = os.path.join(tmpdir, put_results + "stat.dat") if isinstance(model, IBlock): # Kernel blocks have slightly different write method smap_id = model.write(filename=output_filename, format=ProblemFormat.gams, _called_by_solver=True, **io_options) symbolMap = getattr(model, "._symbol_maps")[smap_id] else: (_, smap_id) = model.write(filename=output_filename, format=ProblemFormat.gams, io_options=io_options) symbolMap = model.solutions.symbol_map[smap_id] presolve_completion_time = time.time() if report_timing: print(" %6.2f seconds required for presolve" % (presolve_completion_time - initial_time)) #################################################################### # Apply solver #################################################################### exe = self.executable() command = [exe, output, "o=" + lst, "curdir=" + tmpdir] if tee and not logfile: # default behaviour of gams is to print to console, for # compatability with windows and *nix we want to explicitly log to # stdout (see https://www.gams.com/latest/docs/UG_GamsCall.html) command.append("lo=3") elif not tee and not logfile: command.append("lo=0") elif not tee and logfile: command.append("lo=2") elif tee and logfile: command.append("lo=4") if logfile: command.append("lf=" + str(logfile)) try: rc, _ = pyutilib.subprocess.run(command, tee=tee) if keepfiles: print("\nGAMS WORKING DIRECTORY: %s\n" % tmpdir) if rc == 1 or rc == 127: raise RuntimeError("Command 'gams' was not recognized") elif rc != 0: if rc == 3: # Execution Error # Run check_expr_evaluation, which errors if necessary check_expr_evaluation(model, symbolMap, 'shell') # If nothing was raised, or for all other cases, raise this raise RuntimeError("GAMS encountered an error during solve. " "Check listing file for details.") with open(results_filename, 'r') as results_file: results_text = results_file.read() with open(statresults_filename, 'r') as statresults_file: statresults_text = statresults_file.read() finally: if not keepfiles: if newdir: shutil.rmtree(tmpdir) else: os.remove(output_filename) os.remove(lst_filename) os.remove(results_filename) os.remove(statresults_filename) solve_completion_time = time.time() if report_timing: print(" %6.2f seconds required for solver" % (solve_completion_time - presolve_completion_time)) #################################################################### # Postsolve #################################################################### # import suffixes must be on the top-level model if isinstance(model, IBlock): model_suffixes = list(comp.storage_key for comp \ in pyomo.core.kernel.suffix.\ import_suffix_generator(model, active=True, descend_into=False)) else: model_suffixes = list(name for (name,comp) \ in pyomo.core.base.suffix.\ active_import_suffix_generator(model)) extract_dual = ('dual' in model_suffixes) extract_rc = ('rc' in model_suffixes) stat_vars = dict() # Skip first line of explanatory text for line in statresults_text.splitlines()[1:]: items = line.split() try: stat_vars[items[0]] = float(items[1]) except ValueError: # GAMS printed NA, just make it nan stat_vars[items[0]] = float('nan') results = SolverResults() results.problem.name = output_filename results.problem.lower_bound = stat_vars["OBJEST"] results.problem.upper_bound = stat_vars["OBJEST"] results.problem.number_of_variables = stat_vars["NUMVAR"] results.problem.number_of_constraints = stat_vars["NUMEQU"] results.problem.number_of_nonzeros = stat_vars["NUMNZ"] results.problem.number_of_binary_variables = None # Includes binary vars: results.problem.number_of_integer_variables = stat_vars["NUMDVAR"] results.problem.number_of_continuous_variables = stat_vars["NUMVAR"] \ - stat_vars["NUMDVAR"] results.problem.number_of_objectives = 1 # required by GAMS writer obj = list(model.component_data_objects(Objective, active=True)) assert len(obj) == 1, 'Only one objective is allowed.' obj = obj[0] objctvval = stat_vars["OBJVAL"] if obj.is_minimizing(): results.problem.sense = ProblemSense.minimize results.problem.upper_bound = objctvval else: results.problem.sense = ProblemSense.maximize results.problem.lower_bound = objctvval results.solver.name = "GAMS " + str(self.version()) # Init termination condition to None to give preference to this first # block of code, only set certain TC's below if it's still None results.solver.termination_condition = None results.solver.message = None solvestat = stat_vars["SOLVESTAT"] if solvestat == 1: results.solver.status = SolverStatus.ok elif solvestat == 2: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxIterations elif solvestat == 3: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit elif solvestat == 5: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations elif solvestat == 7: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.licensingProblems elif solvestat == 8: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.userInterrupt elif solvestat == 10: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.solverFailure elif solvestat == 11: results.solver.status = SolverStatus.error results.solver.termination_condition = TerminationCondition.internalSolverError elif solvestat == 4: results.solver.status = SolverStatus.warning results.solver.message = "Solver quit with a problem (see LST file)" elif solvestat in (9, 12, 13): results.solver.status = SolverStatus.error elif solvestat == 6: results.solver.status = SolverStatus.unknown results.solver.return_code = rc # 0 # Not sure if this value is actually user time # "the elapsed time it took to execute a solve statement in total" results.solver.user_time = stat_vars["ETSOLVE"] results.solver.system_time = None results.solver.wallclock_time = None results.solver.termination_message = None soln = Solution() modelstat = stat_vars["MODELSTAT"] if modelstat == 1: results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 2: results.solver.termination_condition = TerminationCondition.locallyOptimal soln.status = SolutionStatus.locallyOptimal elif modelstat in [3, 18]: results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif modelstat in [4, 5, 6, 10, 19]: results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif modelstat == 7: results.solver.termination_condition = TerminationCondition.feasible soln.status = SolutionStatus.feasible elif modelstat == 8: # 'Integer solution model found' results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif modelstat == 9: results.solver.termination_condition = TerminationCondition.intermediateNonInteger soln.status = SolutionStatus.other elif modelstat == 11: # Should be handled above, if modelstat and solvestat both # indicate a licensing problem if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.licensingProblems soln.status = SolutionStatus.error elif modelstat in [12, 13]: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif modelstat == 14: if results.solver.termination_condition is None: results.solver.termination_condition = TerminationCondition.noSolution soln.status = SolutionStatus.unknown elif modelstat in [15, 16, 17]: # Having to do with CNS models, # not sure what to make of status descriptions results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.unsure else: # This is just a backup catch, all cases are handled above soln.status = SolutionStatus.error soln.gap = abs(results.problem.upper_bound \ - results.problem.lower_bound) model_soln = dict() # Skip first line of explanatory text for line in results_text.splitlines()[1:]: items = line.split() model_soln[items[0]] = (items[1], items[2]) has_rc_info = True for sym, ref in iteritems(symbolMap.bySymbol): obj = ref() if isinstance(model, IBlock): # Kernel variables have no 'parent_component' if obj.ctype is IObjective: soln.objective[sym] = {'Value': objctvval} if obj.ctype is not IVariable: continue else: if obj.parent_component().type() is Objective: soln.objective[sym] = {'Value': objctvval} if obj.parent_component().type() is not Var: continue rec = model_soln[sym] # obj.value = float(rec[0]) soln.variable[sym] = {"Value": float(rec[0])} if extract_rc and has_rc_info: try: # model.rc[obj] = float(rec[1]) soln.variable[sym]['rc'] = float(rec[1]) except ValueError: # Solver didn't provide marginals has_rc_info = False if extract_dual: for c in model.component_data_objects(Constraint, active=True): if (c.body.is_fixed()) or \ (not (c.has_lb() or c.has_ub())): # the constraint was not sent to GAMS continue sym = symbolMap.getSymbol(c) if c.equality: rec = model_soln[sym] try: # model.dual[c] = float(rec[1]) soln.constraint[sym] = {'dual': float(rec[1])} except ValueError: # Solver didn't provide marginals # nothing else to do here break else: # Inequality, assume if 2-sided that only # one side's marginal is nonzero # Negate marginal for _lo equations marg = 0 if c.lower is not None: rec_lo = model_soln[sym + '_lo'] try: marg -= float(rec_lo[1]) except ValueError: # Solver didn't provide marginals marg = float('nan') if c.upper is not None: rec_hi = model_soln[sym + '_hi'] try: marg += float(rec_hi[1]) except ValueError: # Solver didn't provide marginals marg = float('nan') if not math.isnan(marg): # model.dual[c] = marg soln.constraint[sym] = {'dual': marg} else: # Solver didn't provide marginals # nothing else to do here break results.solution.insert(soln) #################################################################### # Finish with results #################################################################### results._smap_id = smap_id results._smap = None if isinstance(model, IBlock): if len(results.solution) == 1: results.solution(0).symbol_map = \ getattr(model, "._symbol_maps")[results._smap_id] results.solution(0).default_variable_value = \ self._default_variable_value if load_solutions: model.load_solution(results.solution(0)) else: assert len(results.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(model, "._symbol_maps")) == 1 delattr(model, "._symbol_maps") del results._smap_id if load_solutions and \ (len(results.solution) == 0): logger.error("No solution is available") else: if load_solutions: model.solutions.load_from(results) results._smap_id = None results.solution.clear() else: results._smap = model.solutions.symbol_map[smap_id] model.solutions.delete_symbol_map(smap_id) postsolve_completion_time = time.time() if report_timing: print(" %6.2f seconds required for postsolve" % (postsolve_completion_time - solve_completion_time)) print(" %6.2f seconds required total" % (postsolve_completion_time - initial_time)) return results
def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. This function performs all of the GDPopt solver setup and problem validation. It then calls upon helper functions to construct the initial master approximation and iteration loop. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) solve_data = GDPoptSolveData() solve_data.results = SolverResults() solve_data.timing = Container() old_logger_level = config.logger.getEffectiveLevel() with time_code(solve_data.timing, 'total'), \ restore_logger_level(config.logger), \ create_utility_block(model, 'GDPopt_utils', solve_data): if config.tee and old_logger_level > logging.INFO: # If the logger does not already include INFO, include it. config.logger.setLevel(logging.INFO) config.logger.info( "Starting GDPopt version %s using %s algorithm" % (".".join(map(str, self.version())), config.strategy) ) config.logger.info( """ If you use this software, you may cite the following: - Implementation: Chen, Q; Johnson, ES; Siirola, JD; Grossmann, IE. Pyomo.GDP: Disjunctive Models in Python. Proc. of the 13th Intl. Symposium on Process Systems Eng. San Diego, 2018. - LOA algorithm: Türkay, M; Grossmann, IE. Logic-based MINLP algorithms for the optimal synthesis of process networks. Comp. and Chem. Eng. 1996, 20(8), 959–978. DOI: 10.1016/0098-1354(95)00219-7. - GLOA algorithm: Lee, S; Grossmann, IE. A Global Optimization Algorithm for Nonconvex Generalized Disjunctive Programming and Applications to Process Systems Comp. and Chem. Eng. 2001, 25, 1675-1697. DOI: 10.1016/S0098-1354(01)00732-3 """.strip() ) solve_data.results.solver.name = 'GDPopt %s - %s' % ( str(self.version()), config.strategy) solve_data.original_model = model solve_data.working_model = model.clone() GDPopt = solve_data.working_model.GDPopt_utils setup_results_object(solve_data, config) solve_data.current_strategy = config.strategy # Verify that objective has correct form process_objective(solve_data, config) # Save model initial values. These are used later to initialize NLP # subproblems. solve_data.initial_var_values = list( v.value for v in GDPopt.variable_list) solve_data.best_solution_found = None # Validate the model to ensure that GDPopt is able to solve it. if not model_is_valid(solve_data, config): return # Integer cuts exclude particular discrete decisions GDPopt.integer_cuts = ConstraintList(doc='integer cuts') # Feasible integer cuts exclude discrete realizations that have # been explored via an NLP subproblem. Depending on model # characteristics, the user may wish to revisit NLP subproblems # (with a different initialization, for example). Therefore, these # cuts are not enabled by default, unless the initial model has no # discrete decisions. # Note: these cuts will only exclude integer realizations that are # not already in the primary GDPopt_integer_cuts ConstraintList. GDPopt.no_backtracking = ConstraintList( doc='explored integer cuts') # Set up iteration counters solve_data.master_iteration = 0 solve_data.mip_iteration = 0 solve_data.nlp_iteration = 0 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.iteration_log = {} # Flag indicating whether the solution improved in the past # iteration or not solve_data.feasible_solution_improved = False # Initialize the master problem with time_code(solve_data.timing, 'initialization'): GDPopt_initialize_master(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): GDPopt_iteration_loop(solve_data, config) if solve_data.best_solution_found is not None: # Update values in working model copy_var_list_values( from_list=solve_data.best_solution_found.GDPopt_utils.variable_list, to_list=GDPopt.variable_list, config=config) # Update values in original model copy_var_list_values( GDPopt.variable_list, solve_data.original_model.GDPopt_utils.variable_list, config) solve_data.results.problem.lower_bound = solve_data.LB solve_data.results.problem.upper_bound = solve_data.UB solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.user_time = solve_data.timing.total solve_data.results.solver.wallclock_time = solve_data.timing.total solve_data.results.solver.iterations = solve_data.master_iteration return solve_data.results
def process_logfile(self): """ Process logfile """ results = SolverResults() results.problem.number_of_variables = None results.problem.number_of_nonzeros = None # # Process logfile # OUTPUT = open(self._log_file) output = "".join(OUTPUT.readlines()) OUTPUT.close() # # It is generally useful to know the CPLEX version number for logfile parsing. # cplex_version = None # # Parse logfile lines # # caching for subsequent use - we need to known the problem sense before using this information. # adding to plugin to cache across invocation of process_logfile and process_soln_file. self._best_bound = None self._gap = None for line in output.split("\n"): tokens = re.split('[ \t]+', line.strip()) if len(tokens ) > 3 and tokens[0] == "CPLEX" and tokens[1] == "Error": # IMPT: See below - cplex can generate an error line and then terminate fine, e.g., in CPLEX 12.1. # To handle these cases, we should be specifying some kind of termination criterion always # in the course of parsing a log file (we aren't doing so currently - just in some conditions). results.solver.status = SolverStatus.error results.solver.error = " ".join(tokens) elif len(tokens ) >= 3 and tokens[0] == "ILOG" and tokens[1] == "CPLEX": cplex_version = tokens[2].rstrip(',') elif len(tokens) >= 3 and tokens[0] == "Variables": if results.problem.number_of_variables is None: # CPLEX 11.2 and subsequent versions have two Variables sections in the log file output. results.problem.number_of_variables = int(tokens[2]) # In CPLEX 11 (and presumably before), there was only a single line output to # indicate the constriant count, e.g., "Linear constraints : 16 [Less: 7, Greater: 6, Equal: 3]". # In CPLEX 11.2 (or somewhere in between 11 and 11.2 - I haven't bothered to track it down # in that detail), there is another instance of this line prefix in the min/max problem statistics # block - which we don't care about. In this case, the line looks like: "Linear constraints :" and # that's all. elif len(tokens) >= 4 and tokens[0] == "Linear" and tokens[ 1] == "constraints": results.problem.number_of_constraints = int(tokens[3]) elif len(tokens) >= 3 and tokens[0] == "Nonzeros": if results.problem.number_of_nonzeros is None: # CPLEX 11.2 and subsequent has two Nonzeros sections. results.problem.number_of_nonzeros = int(tokens[2]) elif len(tokens) >= 5 and tokens[4] == "MINIMIZE": results.problem.sense = ProblemSense.minimize elif len(tokens) >= 5 and tokens[4] == "MAXIMIZE": results.problem.sense = ProblemSense.maximize elif len(tokens) >= 4 and tokens[0] == "Solution" and tokens[ 1] == "time" and tokens[2] == "=": # technically, I'm not sure if this is CPLEX user time or user+system - CPLEX doesn't appear # to differentiate, and I'm not sure we can always provide a break-down. results.solver.user_time = float(tokens[3]) elif len(tokens) >= 4 and tokens[0] == "Primal" and tokens[ 1] == "simplex" and tokens[3] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[ 1] == "simplex" and tokens[3] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Barrier" and tokens[ 2] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[ 3] == "Infeasible:": results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "MIP" and tokens[ 2] == "Integer" and tokens[3] == "infeasible.": # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 10 and tokens[0] == "MIP" and tokens[ 2] == "Time" and tokens[3] == "limit" and tokens[ 6] == "feasible:": # handle processing when the time limit has been exceeded, and we have a feasible solution. results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 10 and tokens[0] == "Current" and tokens[ 1] == "MIP" and tokens[2] == "best" and tokens[ 3] == "bound": self._best_bound = float(tokens[5]) self._gap = float(tokens[8].rstrip(',')) # for the case below, CPLEX sometimes reports "true" optimal (the first case) # and other times within-tolerance optimal (the second case). elif (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal") or \ (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal,"): # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 3 and tokens[0] == "Presolve" and tokens[ 2] == "Infeasible.": # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif ((len(tokens) == 6) and \ (tokens[2] == "Integer") and \ (tokens[3] == "infeasible") and \ (tokens[5] == "unbounded.")) or \ ((len(tokens) >= 4) and \ (tokens[0] == "MIP") and \ (tokens[1] == "-") and \ (tokens[2] == "Integer") and \ (tokens[3] == "unbounded:")) or \ ((len(tokens) >= 5) and \ (tokens[0] == "Presolve") and \ (tokens[2] == "Unbounded") and \ (tokens[4] == "infeasible.")): # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok # It isn't clear whether we can determine if the problem is unbounded from # CPLEX's output. results.solver.termination_condition = TerminationCondition.unbounded results.solver.termination_message = ' '.join(tokens) try: results.solver.termination_message = yaml_fix( results.solver.termination_message) except: pass return results
def process_logfile(self): results = SolverResults() results.problem.number_of_variables = None results.problem.number_of_nonzeros = None log_file = open(self._log_file) log_file_contents = "".join(log_file.readlines()) log_file.close() for line in log_file_contents.split("\n"): tokens = re.split('[ \t]+', line.strip()) if len(tokens ) > 3 and tokens[0] == "XPRESS" and tokens[1] == "Error": # IMPT: See below - cplex can generate an error line and then terminate fine, e.g., in XPRESS 12.1. # To handle these cases, we should be specifying some kind of termination criterion always # in the course of parsing a log file (we aren't doing so currently - just in some conditions). results.solver.status = SolverStatus.error results.solver.error = " ".join(tokens) elif len(tokens ) >= 3 and tokens[0] == "ILOG" and tokens[1] == "XPRESS": cplex_version = tokens[2].rstrip(',') elif len(tokens) >= 3 and tokens[0] == "Variables": if results.problem.number_of_variables is None: # XPRESS 11.2 and subsequent versions have two Variables sections in the log file output. results.problem.number_of_variables = int(tokens[2]) # In XPRESS 11 (and presumably before), there was only a single line output to # indicate the constriant count, e.g., "Linear constraints : 16 [Less: 7, Greater: 6, Equal: 3]". # In XPRESS 11.2 (or somewhere in between 11 and 11.2 - I haven't bothered to track it down # in that detail), there is another instance of this line prefix in the min/max problem statistics # block - which we don't care about. In this case, the line looks like: "Linear constraints :" and # that's all. elif len(tokens) >= 4 and tokens[0] == "Linear" and tokens[ 1] == "constraints": results.problem.number_of_constraints = int(tokens[3]) elif len(tokens) >= 3 and tokens[0] == "Nonzeros": if results.problem.number_of_nonzeros is None: # XPRESS 11.2 and subsequent has two Nonzeros sections. results.problem.number_of_nonzeros = int(tokens[2]) elif len(tokens) >= 5 and tokens[4] == "MINIMIZE": results.problem.sense = ProblemSense.minimize elif len(tokens) >= 5 and tokens[4] == "MAXIMIZE": results.problem.sense = ProblemSense.maximize elif len(tokens) >= 4 and tokens[0] == "Solution" and tokens[ 1] == "time" and tokens[2] == "=": # technically, I'm not sure if this is XPRESS user time or user+system - XPRESS doesn't appear # to differentiate, and I'm not sure we can always provide a break-down. results.solver.user_time = float(tokens[3]) elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[ 1] == "simplex" and tokens[3] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Barrier" and tokens[ 2] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[ 3] == "Infeasible:": results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "MIP" and tokens[ 2] == "Integer" and tokens[3] == "infeasible.": # if XPRESS has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) # for the case below, XPRESS sometimes reports "true" optimal (the first case) # and other times within-tolerance optimal (the second case). elif (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal") or \ (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal,"): # if XPRESS has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 3 and tokens[0] == "Presolve" and tokens[ 2] == "Infeasible.": # if XPRESS has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif (len(tokens) == 6 and tokens[2] == "Integer" and tokens[3] == "infeasible" and tokens[5] == "unbounded.") or (len(tokens) >= 5 and tokens[0] == "Presolve" and tokens[2] == "Unbounded" and tokens[4] == "infeasible."): # if XPRESS has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok # It isn't clear whether we can determine if the problem is unbounded from # XPRESS's output. results.solver.termination_condition = TerminationCondition.unbounded results.solver.termination_message = ' '.join(tokens) try: results.solver.termination_message = yaml_fix( results.solver.termination_message) except: pass return results
def process_logfile(self): """ Process the logfile for information about the optimization process. """ return SolverResults()
def process_logfile(self): """ Process logfile """ results = SolverResults() results.problem.number_of_variables = None results.problem.number_of_nonzeros = None # # Process logfile # OUTPUT = open(self._log_file) output = "".join(OUTPUT.readlines()) OUTPUT.close() # # It is generally useful to know the CPLEX version number for logfile parsing. # cplex_version = None # # Parse logfile lines # # caching for subsequent use - we need to known the problem sense before using this information. # adding to plugin to cache across invocation of process_logfile and process_soln_file. self._best_bound = None self._gap = None # use regular expressions to use multi-line match patterns: results.solver.root_node_processing_time = get_root_node_processing_time( log_output=output) results.solver.tree_processing_time = get_tree_processing_time( log_output=output) # Check if a mip start was attempted but failed mip_start_warning = re.search( r'Warning:\s+No solution found from \d+ MIP starts', output) results.solver.mip_start_failed = bool(mip_start_warning) for line in output.split("\n"): tokens = re.split('[ \t]+', line.strip()) if len(tokens) > 3 and ("CPLEX", "Error") in { tuple(tokens[0:2]), tuple(tokens[1:3]) }: # IMPT: See below - cplex can generate an error line and then terminate fine, e.g., in CPLEX 12.1. # To handle these cases, we should be specifying some kind of termination criterion always # in the course of parsing a log file (we aren't doing so currently - just in some conditions). if (results.solver.status == SolverStatus.ok and results.solver.termination_condition in { TerminationCondition.optimal, TerminationCondition.infeasible, TerminationCondition.maxTimeLimit, TerminationCondition.noSolution, TerminationCondition.unbounded, }): # If we have already determined the termination condition, reduce it to a warning. # This is to be consistent with the code in the rest of this method that downgrades an error to a # warning upon determining these termination conditions. results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.error results.solver.error = " ".join(tokens) # Find the first token that starts with an integer, and strip non-integer characters for the return code error_code_token = next( (token for token in tokens if re.match(r'\d', token)), None) if error_code_token: results.solver.return_code = int( re.sub(r'[^\d]', '', error_code_token)) else: results.solver.return_code = None elif len(tokens ) >= 3 and tokens[0] == "ILOG" and tokens[1] == "CPLEX": cplex_version = tokens[2].rstrip(',') elif len(tokens) >= 3 and tokens[1] == "Version": cplex_version = tokens[3] elif len(tokens) >= 3 and tokens[0] == "Variables": if results.problem.number_of_variables is None: # CPLEX 11.2 and subsequent versions have two Variables sections in the log file output. results.problem.number_of_variables = int(tokens[2]) if len(tokens) >= 5 and "Nneg" in tokens[3]: results.problem.number_of_continuous_variables = int( tokens[4].rstrip(',')) if len(tokens) >= 7 and "Binary" in tokens[5]: results.problem.number_of_binary_variables = int( tokens[6].rstrip('],')) # In CPLEX 11 (and presumably before), there was only a single line output to # indicate the constriant count, e.g., "Linear constraints : 16 [Less: 7, Greater: 6, Equal: 3]". # In CPLEX 11.2 (or somewhere in between 11 and 11.2 - I haven't bothered to track it down # in that detail), there is another instance of this line prefix in the min/max problem statistics # block - which we don't care about. In this case, the line looks like: "Linear constraints :" and # that's all. elif len(tokens) >= 4 and tokens[0] == "Linear" and tokens[ 1] == "constraints": results.problem.number_of_constraints = int(tokens[3]) elif len(tokens) >= 3 and tokens[0] == "Nonzeros": if results.problem.number_of_nonzeros is None: # CPLEX 11.2 and subsequent has two Nonzeros sections. results.problem.number_of_nonzeros = int(tokens[2]) elif (len(tokens) >= 5 and tokens[4] == "MINIMIZE") or ( len(tokens) >= 4 and tokens[3] == 'Minimize'): results.problem.sense = ProblemSense.minimize elif (len(tokens) >= 5 and tokens[4] == "MAXIMIZE") or ( len(tokens) >= 4 and tokens[3] == 'Maximize'): results.problem.sense = ProblemSense.maximize elif len(tokens) >= 4 and tokens[0] == "Solution" and tokens[ 1] == "time" and tokens[2] == "=": # technically, I'm not sure if this is CPLEX user time or user+system - CPLEX doesn't appear # to differentiate, and I'm not sure we can always provide a break-down. results.solver.user_time = float(tokens[3]) elif len(tokens) >= 4 and tokens[0] == "Deterministic" and tokens[ 1] == "time" and tokens[2] == "=": results.solver.deterministic_time = float(tokens[3]) elif len(tokens) >= 4 and tokens[0] == "Primal" and tokens[ 1] == "simplex" and tokens[3] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[ 1] == "simplex" and tokens[3] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Barrier" and tokens[ 2] == "Optimal:": results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "Dual" and tokens[ 3] == "Infeasible:": results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 4 and tokens[0] == "MIP" and tokens[ 2] == "Integer" and tokens[3] == "infeasible.": # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 10 and tokens[0] == "MIP" and tokens[ 3] == "limit" and tokens[6] == "feasible:": if tokens[2] == "Time": # handle processing when the time limit has been exceeded, and we have a feasible solution. results.solver.termination_condition = TerminationCondition.maxTimeLimit elif tokens[2] == "Solution": results.solver.termination_condition = TerminationCondition.maxEvaluations results.solver.status = SolverStatus.ok results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 10 and tokens[0] == "MIP" and tokens[ 2] == "Deterministic" and tokens[3] == "time" and tokens[ 4] == "limit" and tokens[7] == "feasible:": # handle processing when the deterministic time limit has been exceeded, and we have a feasible solution. results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 6 and tokens[0] == "MIP" and tuple( tokens[5:]) == ('no', 'integer', 'solution.'): results.solver.termination_condition = TerminationCondition.noSolution results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 9 and tokens[0] == "MIP" and tokens[ 1] == "start" and tokens[7] == "objective": results.solver.warm_start_objective_value = float( tokens[8].rstrip('.')) elif len(tokens) >= 5 and tokens[0:2] == [ "Solution", "pool:" ] and tokens[3] in ["solution", "solutions" ] and tokens[4] == "saved.": results.solver.n_solutions_found = int(tokens[2]) elif len(tokens) >= 10 and tokens[0] == "Current" and tokens[ 1] == "MIP" and tokens[2] == "best" and tokens[ 3] == "bound": self._best_bound = float(tokens[5]) self._gap = float(tokens[8].rstrip(',')) # for the case below, CPLEX sometimes reports "true" optimal (the first case) # and other times within-tolerance optimal (the second case). elif (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal") or \ (len(tokens) >= 4 and tokens[0] == "MIP" and tokens[2] == "Integer" and tokens[3] == "optimal,"): # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.optimal results.solver.termination_message = ' '.join(tokens) elif len(tokens) >= 3 and tokens[0] == "Presolve" and tokens[ 2] == "Infeasible.": # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.infeasible results.solver.termination_message = ' '.join(tokens) elif ((len(tokens) == 6) and \ (tokens[2] == "Integer") and \ (tokens[3] == "infeasible") and \ (tokens[5] == "unbounded.")) or \ ((len(tokens) >= 4) and \ (tokens[0] == "MIP") and \ (tokens[1] == "-") and \ (tokens[2] == "Integer") and \ (tokens[3] == "unbounded:")) or \ ((len(tokens) >= 5) and \ (tokens[0] == "Presolve") and \ (tokens[2] == "Unbounded") and \ (tokens[4] == "infeasible.")): # if CPLEX has previously printed an error message, reduce it to a warning - # there is a strong indication it recovered, but we can't be sure. if results.solver.status == SolverStatus.error: results.solver.status = SolverStatus.warning else: results.solver.status = SolverStatus.ok # It isn't clear whether we can determine if the problem is unbounded from # CPLEX's output. results.solver.termination_condition = TerminationCondition.unbounded results.solver.termination_message = ' '.join(tokens) try: if isinstance(results.solver.termination_message, str): results.solver.termination_message = results.solver.termination_message.replace( ':', '\\x3a') except: pass return results