Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
 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
Ejemplo n.º 4
0
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))
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
    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
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
    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
Ejemplo n.º 17
0
    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
Ejemplo n.º 18
0
    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
Ejemplo n.º 19
0
 def process_logfile(self):
     """
     Process the logfile for information about the optimization process.
     """
     return SolverResults()
Ejemplo n.º 20
0
    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