Пример #1
0
    def store_to(self, results, cuid=False, skip_stale_vars=False):
        """
        Return a Solution() object that is populated with the values in the model.
        """
        instance = self._instance()
        results.solution.clear()
        results._smap_id = None

        for soln_ in self.solutions:
            soln = Solution()
            soln._cuid = cuid
            for key, val in soln_._metadata.items():
                setattr(soln, key, val)

            if cuid:
                labeler = CuidLabeler()
            else:
                labeler = CNameLabeler()
            sm = SymbolMap()

            entry = soln_._entry['objective']
            for obj in instance.component_data_objects(Objective, active=True):
                vals = entry.get(id(obj), None)
                if vals is None:
                    vals = {}
                else:
                    vals = vals[1]
                vals['Value'] = value(obj)
                soln.objective[sm.getSymbol(obj, labeler)] = vals
            entry = soln_._entry['variable']
            for obj in instance.component_data_objects(Var, active=True):
                if obj.stale and skip_stale_vars:
                    continue
                vals = entry.get(id(obj), None)
                if vals is None:
                    vals = {}
                else:
                    vals = vals[1]
                vals['Value'] = value(obj)
                soln.variable[sm.getSymbol(obj, labeler)] = vals
            entry = soln_._entry['constraint']
            for obj in instance.component_data_objects(Constraint,
                                                       active=True):
                vals = entry.get(id(obj), None)
                if vals is None:
                    continue
                else:
                    vals = vals[1]
                soln.constraint[sm.getSymbol(obj, labeler)] = vals
            results.solution.insert(soln)
Пример #2
0
    def store_to(self, results, cuid=False):
        """
        Return a Solution() object that is populated with the values in the model.
        """
        instance = self._instance()
        results.solution.clear()
        results._smap_id = None

        for soln_ in self.solutions:
            soln = Solution()
            soln._cuid = cuid
            for key, val in iteritems(soln_._metadata):
                setattr(soln, key, val)

            if cuid:
                labeler = CuidLabeler()
            else:
                labeler = CNameLabeler()
            sm = SymbolMap()

            entry = soln_._entry['objective']
            for obj in instance.component_data_objects(Objective, active=True):
                vals = entry.get(id(obj), None)
                if vals is None:
                    vals = {}
                else:
                    vals = vals[1]
                vals['Value'] = value(obj)
                soln.objective[ sm.getSymbol(obj, labeler) ] = vals
            entry = soln_._entry['variable']
            for obj in instance.component_data_objects(Var, active=True):
                if obj.stale:
                    continue
                vals = entry.get(id(obj), None)
                if vals is None:
                    vals = {}
                else:
                    vals = vals[1]
                vals['Value'] = value(obj)
                soln.variable[ sm.getSymbol(obj, labeler) ] = vals
            entry = soln_._entry['constraint']
            for obj in instance.component_data_objects(Constraint, active=True):
                vals = entry.get(id(obj), None)
                if vals is None:
                    continue
                else:
                    vals = vals[1]
                soln.constraint[ sm.getSymbol(obj, labeler) ] = vals
            results.solution.insert( soln )
Пример #3
0
    def process_soln_file(self, results):
        # the only suffixes that we extract from CBC are
        # constraint duals and variable reduced-costs. scan
        # through the solver suffix list and throw an
        # exception if the user has specified any others.
        extract_duals = False
        extract_reduced_costs = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "dual"):
                extract_duals = True
                flag = True
            if re.match(suffix, "rc"):
                extract_reduced_costs = True
                flag = True
            if not flag:
                raise RuntimeError(
                    "***CBC solver plugin cannot extract solution suffix=" +
                    suffix)

        # if dealing with SOL format files, we've already read
        # this via the base class reader functionality.
        if self._results_format is ResultsFormat.sol:
            return

        # otherwise, go with the native CBC solution format.
        if len(results.solution) > 0:
            solution = results.solution(0)
        else:
            solution = Solution()

        results.problem.number_of_objectives = 1

        processing_constraints = None  # None means header, True means constraints, False means variables.
        header_processed = False
        optim_value = None

        try:
            INPUT = open(self._soln_file, "r")
        except IOError:
            INPUT = []

        for line in INPUT:
            tokens = tuple(re.split('[ \t]+', line.strip()))
            n_tokens = len(tokens)
            #
            # These are the only header entries CBC will generate (identified via browsing CbcSolver.cpp)
            # See https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp
            # Search for (no integer solution - continuous used)      Currently line 9912 as of rev2497
            # Note that since this possibly also covers old CBC versions, we shall not be removing any functionality,
            # even if it is not seen in the current revision
            #
            if not header_processed:
                if tokens[0] == 'Optimal':
                    results.solver.termination_condition = TerminationCondition.optimal
                    results.solver.status = SolverStatus.ok
                    results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \
                                                         "and an optimal solution is available."
                    solution.status = SolutionStatus.optimal
                    optim_value = float(tokens[-1])
                elif tokens[0] in ('Infeasible', 'PrimalInfeasible') or (
                        n_tokens > 1
                        and tokens[0:2] == ('Integer', 'infeasible')):
                    results.solver.termination_message = "Model was proven to be infeasible."
                    results.solver.termination_condition = TerminationCondition.infeasible
                    results.solver.status = SolverStatus.warning
                    solution.status = SolutionStatus.infeasible
                    INPUT.close()
                    return
                elif tokens[0] == 'Unbounded' or (
                        n_tokens > 2 and tokens[0] == 'Problem' and tokens[2]
                        == 'unbounded') or (n_tokens > 1 and tokens[0:2]
                                            == ('Dual', 'infeasible')):
                    results.solver.termination_message = "Model was proven to be unbounded."
                    results.solver.termination_condition = TerminationCondition.unbounded
                    results.solver.status = SolverStatus.warning
                    solution.status = SolutionStatus.unbounded
                    INPUT.close()
                    return
                elif n_tokens > 2 and tokens[0:2] == ('Stopped', 'on'):
                    optim_value = float(tokens[-1])
                    solution.gap = None
                    results.solver.status = SolverStatus.aborted
                    solution.status = SolutionStatus.stoppedByLimit
                    if tokens[2] == 'time':
                        results.solver.termination_message = "Optimization terminated because the time expended " \
                                                             "exceeded the value specified in the seconds " \
                                                             "parameter."
                        results.solver.termination_condition = TerminationCondition.maxTimeLimit
                    elif tokens[2] == 'iterations':
                        # Only add extra info if not already obtained from logs (which give a better description)
                        if results.solver.termination_condition not in [
                                TerminationCondition.maxEvaluations,
                                TerminationCondition.other,
                                TerminationCondition.maxIterations
                        ]:
                            results.solver.termination_message = "Optimization terminated because a limit was hit"
                            results.solver.termination_condition = TerminationCondition.maxIterations
                    elif tokens[2] == 'difficulties':
                        results.solver.termination_condition = TerminationCondition.solverFailure
                        results.solver.status = SolverStatus.error
                        solution.status = SolutionStatus.error
                    elif tokens[2] == 'ctrl-c':
                        results.solver.termination_message = "Optimization was terminated by the user."
                        results.solver.termination_condition = TerminationCondition.userInterrupt
                        solution.status = SolutionStatus.unknown
                    else:
                        results.solver.termination_condition = TerminationCondition.unknown
                        results.solver.status = SolverStatus.unknown
                        solution.status = SolutionStatus.unknown
                        results.solver.termination_message = ' '.join(tokens)
                        print(
                            '***WARNING: CBC plugin currently not processing solution status=Stopped correctly. Full '
                            'status line is: {}'.format(line.strip()))
                    if n_tokens > 8 and tokens[3:9] == ('(no', 'integer',
                                                        'solution', '-',
                                                        'continuous', 'used)'):
                        results.solver.termination_message = "Optimization terminated because a limit was hit, " \
                                                             "however it had not found an integer solution yet."
                        results.solver.termination_condition = TerminationCondition.intermediateNonInteger
                        solution.status = SolutionStatus.other
                else:
                    results.solver.termination_condition = TerminationCondition.unknown
                    results.solver.status = SolverStatus.unknown
                    solution.status = SolutionStatus.unknown
                    results.solver.termination_message = ' '.join(tokens)
                    print(
                        '***WARNING: CBC plugin currently not processing solution status={} correctly. Full status '
                        'line is: {}'.format(tokens[0], line.strip()))

            # most of the first tokens should be integers
            # if it's not an integer, only then check the list of results
            try:
                row_number = int(tokens[0])
                if row_number == 0:  # indicates section start.
                    if processing_constraints is None:
                        processing_constraints = True
                    elif processing_constraints is True:
                        processing_constraints = False
                    else:
                        raise RuntimeError(
                            "CBC plugin encountered unexpected line=(" +
                            line.strip() + ") in solution file=" +
                            self._soln_file +
                            "; constraint and variable sections already processed!"
                        )
            except ValueError:
                if tokens[0] in ("Optimal", "Infeasible", "Unbounded",
                                 "Stopped", "Integer", "Status"):
                    if optim_value is not None:
                        if results.problem.sense == ProblemSense.maximize:
                            optim_value *= -1
                        solution.objective['__default_objective__'] = {
                            'Value': optim_value
                        }
                    header_processed = True

            if (processing_constraints is True) and (extract_duals is True):
                if n_tokens == 4:
                    pass
                elif (n_tokens == 5) and tokens[0] == "**":
                    tokens = tokens[1:]
                else:
                    raise RuntimeError(
                        "Unexpected line format encountered in CBC solution file - line="
                        + line)

                constraint = tokens[1]
                constraint_ax = float(
                    tokens[2]
                )  # CBC reports the constraint row times the solution vector - not the slack.
                constraint_dual = float(tokens[3])
                if constraint[:2] == 'c_':
                    solution.constraint[constraint] = {"Dual": constraint_dual}
                elif constraint[:2] == 'r_':
                    # For the range constraints, supply only the dual with the largest
                    # magnitude (at least one should always be numerically zero)
                    existing_constraint_dual_dict = solution.constraint.get(
                        'r_l_' + constraint[4:], None)
                    if existing_constraint_dual_dict:
                        # if a constraint dual is already saved, then update it if its
                        # magnitude is larger than existing; this avoids checking vs
                        # zero (avoiding problems with solver tolerances)
                        existing_constraint_dual = existing_constraint_dual_dict[
                            "Dual"]
                        if abs(constraint_dual) > abs(
                                existing_constraint_dual):
                            solution.constraint['r_l_' + constraint[4:]] = {
                                "Dual": constraint_dual
                            }
                    else:
                        # if no constraint with that name yet, just save it in the solution constraint dictionary
                        solution.constraint['r_l_' + constraint[4:]] = {
                            "Dual": constraint_dual
                        }

            elif processing_constraints is False:
                if n_tokens == 4:
                    pass
                elif (n_tokens == 5) and tokens[0] == "**":
                    tokens = tokens[1:]
                else:
                    raise RuntimeError("Unexpected line format encountered "
                                       "in CBC solution file - line=" + line)

                variable_name = tokens[1]
                variable_value = float(tokens[2])
                variable = solution.variable[variable_name] = {
                    "Value": variable_value
                }
                if extract_reduced_costs is True:
                    variable_reduced_cost = float(
                        tokens[3])  # currently ignored.
                    variable["Rc"] = variable_reduced_cost

            elif header_processed is True:
                pass

            else:
                raise RuntimeError("CBC plugin encountered unexpected "
                                   "line=(" + line.strip() +
                                   ") in solution file=" + self._soln_file +
                                   "; expecting header, but "
                                   "found data!")

        if not type(INPUT) is list:
            INPUT.close()

        if len(results.solution) == 0 and solution.status in [
                SolutionStatus.optimal, SolutionStatus.stoppedByLimit,
                SolutionStatus.unknown, SolutionStatus.other
        ]:
            results.solution.insert(solution)
Пример #4
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
Пример #5
0
    def process_soln_file(self, results):

        # the only suffixes that we extract from CPLEX are
        # constraint duals, constraint slacks, and variable
        # reduced-costs. scan through the solver suffix list
        # and throw an exception if the user has specified
        # any others.
        extract_duals = False
        extract_slacks = False
        extract_reduced_costs = False
        extract_rc = False
        extract_lrc = False
        extract_urc = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "dual"):
                extract_duals = True
                flag = True
            if re.match(suffix, "slack"):
                extract_slacks = True
                flag = True
            if re.match(suffix, "rc"):
                extract_reduced_costs = True
                extract_rc = True
                flag = True
            if re.match(suffix, "lrc"):
                extract_reduced_costs = True
                extract_lrc = True
                flag = True
            if re.match(suffix, "urc"):
                extract_reduced_costs = True
                extract_urc = True
                flag = True
            if not flag:
                raise RuntimeError(
                    "***The CPLEX solver plugin cannot extract solution suffix="
                    + suffix)

        # check for existence of the solution file
        # not sure why we just return - would think that we
        # would want to indicate some sort of error
        if not os.path.exists(self._soln_file):
            return

        range_duals = {}
        range_slacks = {}
        soln = Solution()
        soln.objective['__default_objective__'] = {'Value': None}

        # caching for efficiency
        soln_variables = soln.variable
        soln_constraints = soln.constraint

        INPUT = open(self._soln_file, "r")
        results.problem.number_of_objectives = 1
        time_limit_exceeded = False
        mip_problem = False
        for line in INPUT:
            line = line.strip()
            line = line.lstrip('<?/')
            line = line.rstrip('/>?')
            tokens = line.split(' ')

            if tokens[0] == "variable":
                variable_name = None
                variable_value = None
                variable_reduced_cost = None
                variable_status = None
                for i in xrange(1, len(tokens)):
                    field_name = tokens[i].split('=')[0]
                    field_value = tokens[i].split('=')[1].lstrip("\"").rstrip(
                        "\"")
                    if field_name == "name":
                        variable_name = field_value
                    elif field_name == "value":
                        variable_value = field_value
                    elif (extract_reduced_costs is
                          True) and (field_name == "reducedCost"):
                        variable_reduced_cost = field_value
                    elif (extract_reduced_costs is True) and (field_name
                                                              == "status"):
                        variable_status = field_value

                # skip the "constant-one" variable, used to capture/retain objective offsets in the CPLEX LP format.
                if variable_name != "ONE_VAR_CONSTANT":
                    variable = soln_variables[variable_name] = {
                        "Value": float(variable_value)
                    }
                    if (variable_reduced_cost
                            is not None) and (extract_reduced_costs is True):
                        try:
                            if extract_rc is True:
                                variable["Rc"] = float(variable_reduced_cost)
                            if variable_status is not None:
                                if extract_lrc is True:
                                    if variable_status == "LL":
                                        variable["Lrc"] = float(
                                            variable_reduced_cost)
                                    else:
                                        variable["Lrc"] = 0.0
                                if extract_urc is True:
                                    if variable_status == "UL":
                                        variable["Urc"] = float(
                                            variable_reduced_cost)
                                    else:
                                        variable["Urc"] = 0.0
                        except:
                            raise ValueError("Unexpected reduced-cost value=" +
                                             str(variable_reduced_cost) +
                                             " encountered for variable=" +
                                             variable_name)
            elif (tokens[0] == "constraint") and ((extract_duals is True) or
                                                  (extract_slacks is True)):
                is_range = False
                rlabel = None
                rkey = None
                for i in xrange(1, len(tokens)):
                    field_name = tokens[i].split('=')[0]
                    field_value = tokens[i].split('=')[1].lstrip("\"").rstrip(
                        "\"")
                    if field_name == "name":
                        if field_value.startswith('c_'):
                            constraint = soln_constraints[field_value] = {}
                        elif field_value.startswith('r_l_'):
                            is_range = True
                            rlabel = field_value[4:]
                            rkey = 0
                        elif field_value.startswith('r_u_'):
                            is_range = True
                            rlabel = field_value[4:]
                            rkey = 1
                    elif (extract_duals is True) and (field_name
                                                      == "dual"):  # for LPs
                        if is_range is False:
                            constraint["Dual"] = float(field_value)
                        else:
                            range_duals.setdefault(
                                rlabel, [0, 0])[rkey] = float(field_value)
                    elif (extract_slacks is True) and (field_name
                                                       == "slack"):  # for MIPs
                        if is_range is False:
                            constraint["Slack"] = float(field_value)
                        else:
                            range_slacks.setdefault(
                                rlabel, [0, 0])[rkey] = float(field_value)
            elif tokens[0].startswith("problemName"):
                filename = (
                    tokens[0].split('=')[1].strip()).lstrip("\"").rstrip("\"")
                results.problem.name = os.path.basename(filename)
                if '.' in results.problem.name:
                    results.problem.name = results.problem.name.split('.')[0]
                tINPUT = open(filename, "r")
                for tline in tINPUT:
                    tline = tline.strip()
                    if tline == "":
                        continue
                    tokens = re.split('[\t ]+', tline)
                    if tokens[0][0] in ['\\', '*']:
                        continue
                    elif tokens[0] == "NAME":
                        results.problem.name = tokens[1]
                    else:
                        sense = tokens[0].lower()
                        if sense in ['max', 'maximize']:
                            results.problem.sense = ProblemSense.maximize
                        if sense in ['min', 'minimize']:
                            results.problem.sense = ProblemSense.minimize
                    break
                tINPUT.close()

            elif tokens[0].startswith("objectiveValue"):
                objective_value = (
                    tokens[0].split('=')[1].strip()).lstrip("\"").rstrip("\"")
                soln.objective['__default_objective__']['Value'] = float(
                    objective_value)
            elif tokens[0].startswith("solutionStatusValue"):
                pieces = tokens[0].split("=")
                solution_status = eval(pieces[1])
                # solution status = 1 => optimal
                # solution status = 3 => infeasible
                if soln.status == SolutionStatus.unknown:
                    if solution_status == 1:
                        soln.status = SolutionStatus.optimal
                    elif solution_status == 3:
                        soln.status = SolutionStatus.infeasible
                        soln.gap = None
                    else:
                        # we are flagging anything with a solution status >= 4 as an error, to possibly
                        # be over-ridden as we learn more about the status (e.g., due to time limit exceeded).
                        soln.status = SolutionStatus.error
                        soln.gap = None
            elif tokens[0].startswith("solutionStatusString"):
                solution_status = ((" ".join(tokens).split('=')[1]).strip()
                                   ).lstrip("\"").rstrip("\"")
                if solution_status in [
                        "optimal", "integer optimal solution",
                        "integer optimal, tolerance"
                ]:
                    soln.status = 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 "integer" in solution_status:
                        mip_problem = True
                elif solution_status in ["infeasible"]:
                    soln.status = SolutionStatus.infeasible
                    soln.gap = None
                elif solution_status in ["time limit exceeded"]:
                    # we need to know if the solution is primal feasible, and if it is, set the solution status accordingly.
                    # for now, just set the flag so we can trigger the logic when we see the primalFeasible keyword.
                    time_limit_exceeded = True
            elif tokens[0].startswith("MIPNodes"):
                if mip_problem:
                    n = eval(
                        eval((" ".join(tokens).split('=')[1]
                              ).strip()).lstrip("\"").rstrip("\""))
                    results.solver.statistics.branch_and_bound.number_of_created_subproblems = n
                    results.solver.statistics.branch_and_bound.number_of_bounded_subproblems = n
            elif tokens[0].startswith("primalFeasible") and (
                    time_limit_exceeded is True):
                primal_feasible = int(((" ".join(tokens).split('=')[1]
                                        ).strip()).lstrip("\"").rstrip("\""))
                if primal_feasible == 1:
                    soln.status = SolutionStatus.feasible
                    if (results.problem.sense == ProblemSense.minimize):
                        results.problem.upper_bound = soln.objective[
                            '__default_objective__']['Value']
                    else:
                        results.problem.lower_bound = soln.objective[
                            '__default_objective__']['Value']
                else:
                    soln.status = SolutionStatus.infeasible

        if self._best_bound is not None:
            if results.problem.sense == ProblemSense.minimize:
                results.problem.lower_bound = self._best_bound
            else:
                results.problem.upper_bound = self._best_bound
        if self._gap is not None:
            soln.gap = self._gap

        # For the range constraints, supply only the dual with the largest
        # magnitude (at least one should always be numerically zero)
        for key, (ld, ud) in iteritems(range_duals):
            if abs(ld) > abs(ud):
                soln_constraints['r_l_' + key] = {"Dual": ld}
            else:
                soln_constraints['r_l_' + key] = {
                    "Dual": ud
                }  # Use the same key
        # slacks
        for key, (ls, us) in iteritems(range_slacks):
            if abs(ls) > abs(us):
                soln_constraints.setdefault('r_l_' + key, {})["Slack"] = ls
            else:
                soln_constraints.setdefault(
                    'r_l_' + key, {})["Slack"] = us  # Use the same key

        if not results.solver.status is SolverStatus.error:
            if 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)
            elif (results.solver.termination_condition is \
                  TerminationCondition.maxTimeLimit) and \
                  (soln.status is not SolutionStatus.infeasible):
                results.solution.insert(soln)

        INPUT.close()
Пример #6
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
Пример #7
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
Пример #8
0
    def _process_soln_file(self, results, TimFile, INPUT):
        #
        # **NOTE: This solution parser assumes the baron input file
        #         was generated by the Pyomo baron_writer plugin, and
        #         that a dummy constraint named c_e_FIX_ONE_VAR_CONST__
        #         was added as the initial constraint in order to
        #         support trivial constraint equations arrising from
        #         fixing pyomo variables. Thus, the dual price solution
        #         information for the first constraint in the solution
        #         file will be excluded from the results object.
        #

        # TODO: Is there a way to handle non-zero return values from baron?
        #       Example: the "NonLinearity Error if POW expression"
        #       (caused by  x ^ y) when both x and y are variables
        #       causes an ugly python error and the solver log has a single
        #       line to display the error, hard to pick out of the list

        # Check for suffixes to send back to pyomo
        extract_marginals = False
        extract_price = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "rc"): #baron_marginal
                extract_marginals = True
                flag = True
            if re.match(suffix, "dual"): #baron_price
                extract_price = True
                flag = True
            if not flag:
                raise RuntimeError("***The BARON solver plugin cannot"
                                   "extract solution suffix="+suffix)

        soln = Solution()

        #
        # Process model and solver status from the Baron tim file
        #
        line = TimFile.readline().split()
        results.problem.name = line[0]
        results.problem.number_of_constraints = int(line[1])
        results.problem.number_of_variables = int(line[2])
        results.problem.lower_bound = float(line[5])
        results.problem.upper_bound = float(line[6])
        soln.gap = results.problem.upper_bound - results.problem.lower_bound
        solver_status = line[7]
        model_status = line[8]

        objective = None
        ##try:
        ##    objective = symbol_map.getObject("__default_objective__")
        ##    objective_label = symbol_map_byObjects[id(objective)]
        ##except:
        ##    objective_label = "__default_objective__"
        # [JDS 17/Feb/15] I am not sure why this is needed, but all
        # other solvers (in particular the ASL solver and CPLEX) always
        # return the objective value in the __default_objective__ label,
        # and not by the Pyomo object name.  For consistency, we will
        # do the same here.
        objective_label = "__default_objective__"

        soln.objective[objective_label] = {'Value': None}
        results.problem.number_of_objectives = 1
        if objective is not None:
            results.problem.sense = \
                'minimizing' if objective.is_minimizing() else 'maximizing'

        if solver_status == '1':
            results.solver.status = SolverStatus.ok
        elif solver_status == '2':
            results.solver.status = SolverStatus.error
            results.solver.termination_condition = TerminationCondition.error
            #CLH: I wasn't sure if this was double reporting errors. I
            #     just filled in one termination_message for now
            results.solver.termination_message = \
                ("Insufficient memory to store the number of nodes required "
                 "for this seach tree. Increase physical memory or change "
                 "algorithmic options")
        elif solver_status == '3':
            results.solver.status = SolverStatus.ok
            results.solver.termination_condition = \
                TerminationCondition.maxIterations
        elif solver_status == '4':
            results.solver.status = SolverStatus.ok
            results.solver.termination_condition = \
                TerminationCondition.maxTimeLimit
        elif solver_status == '5':
            results.solver.status = SolverStatus.warning
            results.solver.termination_condition = \
                TerminationCondition.other
        elif solver_status == '6':
            results.solver.status = SolverStatus.aborted
            results.solver.termination_condition = \
                TerminationCondition.userInterrupt
        elif solver_status == '7':
            results.solver.status = SolverStatus.error
            results.solver.termination_condition = \
                TerminationCondition.error
        elif solver_status == '8':
            results.solver.status = SolverStatus.unknown
            results.solver.termination_condition = \
                TerminationCondition.unknown
        elif solver_status == '9':
            results.solver.status = SolverStatus.error
            results.solver.termination_condition = \
                TerminationCondition.solverFailure
        elif solver_status == '10':
            results.solver.status = SolverStatus.error
            results.solver.termination_condition = \
                TerminationCondition.error
        elif solver_status == '11':
            results.solver.status = SolverStatus.aborted
            results.solver.termination_condition = \
                TerminationCondition.licensingProblems
            results.solver.termination_message = \
                'Run terminated because of a licensing error.'

        if model_status == '1':
            soln.status = SolutionStatus.optimal
            results.solver.termination_condition = \
                TerminationCondition.optimal
        elif model_status == '2':
            soln.status = SolutionStatus.infeasible
            results.solver.termination_condition = \
                TerminationCondition.infeasible
        elif model_status == '3':
            soln.status = SolutionStatus.unbounded
            results.solver.termination_condition = \
                TerminationCondition.unbounded
        elif model_status == '4':
            soln.status = SolutionStatus.feasible
        elif model_status == '5':
            soln.status = SolutionStatus.unknown

        #
        # Process BARON results file
        #

        # Solutions that were preprocessed infeasible, were aborted,
        # or gave error will not have filled in res.lst files
        if results.solver.status not in [SolverStatus.error,
                                         SolverStatus.aborted]:
            #
            # Extract the solution vector and objective value from BARON
            #
            var_value = []
            var_name = []
            var_marginal = []
            con_price = []
            SolvedDuringPreprocessing = False

            #############
            #
            # Scan through the first part of the solution file, until the
            # termination message '*** Normal completion ***'
            line = ''
            while '***' not in line:
                line = INPUT.readline()
                if 'Problem solved during preprocessing' in line:
                    SolvedDuringPreprocessing = True

            INPUT.readline()
            INPUT.readline()
            try:
                objective_value = float(INPUT.readline().split()[4])
            except IndexError:
                # No objective value, so no solution to return
                if solver_status == '1' and model_status in ('1','4'):
                    logger.error(
"""Failed to process BARON solution file: could not extract the final
objective value, but BARON completed normally.  This is indicative of a
bug in Pyomo's BARON solution parser.  Please report this (along with
the Pyomo model and BARON version) to the Pyomo Developers.""")
                return
            INPUT.readline()
            INPUT.readline()

            # Scan through the solution variable values
            line = INPUT.readline()
            while line.strip() != '':
                var_value.append(float(line.split()[2]))
                line = INPUT.readline()

            # Only scan through the marginal and price values if baron
            # found that information.
            has_dual_info = False
            if 'Corresponding dual solution vector is' in INPUT.readline():
                has_dual_info = True
                INPUT.readline()
                line = INPUT.readline()
                while 'Price' not in line and line.strip() != '':
                    var_marginal.append(float(line.split()[2]))
                    line = INPUT.readline()

                if 'Price' in line:
                    line = INPUT.readline()
                    #
                    # Assume the baron_writer added the dummy
                    # c_e_FIX_ONE_VAR_CONST__ constraint as the first
                    #
                    line = INPUT.readline()
                    while line.strip() != '':
                        con_price.append(float(line.split()[2]))
                        line = INPUT.readline()

            # Skip either a few blank lines or an empty block of useless
            # marginal and price values (if 'No dual information is available')
            while 'The best solution found is' not in INPUT.readline():
                pass

            # Collect the variable names, which are given in the same
            # order as the lists for values already read
            INPUT.readline()
            INPUT.readline()
            line = INPUT.readline()
            while line.strip() != '':
                var_name.append(line.split()[0])
                line = INPUT.readline()

            assert len(var_name) == len(var_value)
            #
            #
            ################

            #
            # Plug gathered information into pyomo soln
            #

            soln_variable = soln.variable
            # After collecting solution information, the soln is
            # filled with variable name, number, and value. Also,
            # optionally fill the baron_marginal suffix
            for i, (label, val) in enumerate(zip(var_name, var_value)):

                soln_variable[label] = {"Value": val}

                # Only adds the baron_marginal key it is requested and exists
                if extract_marginals and has_dual_info:
                    soln_variable[label]["rc"] = var_marginal[i]

            # Fill in the constraint 'price' information
            if extract_price and has_dual_info:
                soln_constraint = soln.constraint
                #
                # Assume the baron_writer added the dummy
                # c_e_FIX_ONE_VAR_CONST__ constraint as the first,
                # so constraint aliases start at 1
                #
                for i, price_val in enumerate(con_price, 1):
                    # use the alias made by the Baron writer
                    con_label = ".c"+str(i)
                    soln_constraint[con_label] = {"dual": price_val}

            # This check is necessary because solutions that are
            # preprocessed infeasible have ok solver status, but no
            # objective value located in the res.lst file
            if not (SolvedDuringPreprocessing and \
                    soln.status == SolutionStatus.infeasible):
                soln.objective[objective_label] = {'Value': objective_value}

            # Fill the solution for most cases, except errors
            results.solution.insert(soln)
Пример #9
0
    def process_soln_file(self, results):
        # the only suffixes that we extract from CPLEX are
        # constraint duals, constraint slacks, and variable
        # reduced-costs. scan through the solver suffix list
        # and throw an exception if the user has specified
        # any others.
        extract_duals = False
        extract_slacks = False
        extract_rc = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "dual"):
                extract_duals = True
                flag = True
            if re.match(suffix, "slack"):
                extract_slacks = True
                flag = True
            if re.match(suffix, "rc"):
                extract_rc = True
                flag = True
            if not flag:
                raise RuntimeError(
                    "***The GUROBI solver plugin cannot extract solution suffix="
                    + suffix)

        # check for existence of the solution file
        # not sure why we just return - would think that we
        # would want to indicate some sort of error
        if not os.path.exists(self._soln_file):
            return

        soln = Solution()

        # caching for efficiency
        soln_variables = soln.variable
        soln_constraints = soln.constraint

        num_variables_read = 0

        # string compares are too expensive, so simply introduce some
        # section IDs.
        # 0 - unknown
        # 1 - problem
        # 2 - solution
        # 3 - solver

        section = 0  # unknown

        solution_seen = False

        range_duals = {}
        range_slacks = {}

        INPUT = open(self._soln_file, "r")
        for line in INPUT:
            line = line.strip()
            tokens = [token.strip() for token in line.split(":")]
            if (tokens[0] == 'section'):
                if (tokens[1] == 'problem'):
                    section = 1
                elif (tokens[1] == 'solution'):
                    section = 2
                    solution_seen = True
                elif (tokens[1] == 'solver'):
                    section = 3
            else:
                if (section == 2):
                    if (tokens[0] == 'var'):
                        if tokens[1] != "ONE_VAR_CONSTANT":
                            soln_variables[tokens[1]] = {
                                "Value": float(tokens[2])
                            }
                            num_variables_read += 1
                    elif (tokens[0] == 'status'):
                        soln.status = getattr(SolutionStatus, tokens[1])
                    elif (tokens[0] == 'gap'):
                        soln.gap = float(tokens[1])
                    elif (tokens[0] == 'objective'):
                        if tokens[1].strip() != 'None':
                            soln.objective['__default_objective__'] = \
                                {'Value': float(tokens[1])}
                            if results.problem.sense == ProblemSense.minimize:
                                results.problem.upper_bound = float(tokens[1])
                            else:
                                results.problem.lower_bound = float(tokens[1])
                    elif (tokens[0] == 'constraintdual'):
                        name = tokens[1]
                        if name != "c_e_ONE_VAR_CONSTANT":
                            if name.startswith('c_'):
                                soln_constraints.setdefault(
                                    tokens[1], {})["Dual"] = float(tokens[2])
                            elif name.startswith('r_l_'):
                                range_duals.setdefault(
                                    name[4:], [0, 0])[0] = float(tokens[2])
                            elif name.startswith('r_u_'):
                                range_duals.setdefault(
                                    name[4:], [0, 0])[1] = float(tokens[2])
                    elif (tokens[0] == 'constraintslack'):
                        name = tokens[1]
                        if name != "c_e_ONE_VAR_CONSTANT":
                            if name.startswith('c_'):
                                soln_constraints.setdefault(
                                    tokens[1], {})["Slack"] = float(tokens[2])
                            elif name.startswith('r_l_'):
                                range_slacks.setdefault(
                                    name[4:], [0, 0])[0] = float(tokens[2])
                            elif name.startswith('r_u_'):
                                range_slacks.setdefault(
                                    name[4:], [0, 0])[1] = float(tokens[2])
                    elif (tokens[0] == 'varrc'):
                        if tokens[1] != "ONE_VAR_CONSTANT":
                            soln_variables[tokens[1]]["Rc"] = float(tokens[2])
                    else:
                        setattr(soln, tokens[0], tokens[1])
                elif (section == 1):
                    if tokens[0] == 'sense':
                        if tokens[1] == 'minimize':
                            results.problem.sense = ProblemSense.minimize
                        elif tokens[1] == 'maximize':
                            results.problem.sense = ProblemSense.maximize
                    else:
                        try:
                            val = eval(tokens[1])
                        except:
                            val = tokens[1]
                        setattr(results.problem, tokens[0], val)
                elif (section == 3):
                    if (tokens[0] == 'status'):
                        results.solver.status = getattr(
                            SolverStatus, tokens[1])
                    elif (tokens[0] == 'termination_condition'):
                        try:
                            results.solver.termination_condition = getattr(
                                TerminationCondition, tokens[1])
                        except AttributeError:
                            results.solver.termination_condition = TerminationCondition.unknown
                    else:
                        setattr(results.solver, tokens[0], tokens[1])

        INPUT.close()

        # For the range constraints, supply only the dual with the largest
        # magnitude (at least one should always be numerically zero)
        for key, (ld, ud) in range_duals.items():
            if abs(ld) > abs(ud):
                soln_constraints['r_l_' + key] = {"Dual": ld}
            else:
                # Use the same key
                soln_constraints['r_l_' + key] = {"Dual": ud}
        # slacks
        for key, (ls, us) in range_slacks.items():
            if abs(ls) > abs(us):
                soln_constraints.setdefault('r_l_' + key, {})["Slack"] = ls
            else:
                # Use the same key
                soln_constraints.setdefault('r_l_' + key, {})["Slack"] = us

        if solution_seen:
            results.solution.insert(soln)
Пример #10
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
Пример #11
0
    def process_soln_file(self, results):

        # the only suffixes that we extract from Xpress are
        # constraint duals, constraint slacks, and variable
        # reduced-costs. scan through the solver suffix list
        # and throw an exception if the user has specified
        # any others.
        extract_duals = False
        extract_slacks = False
        extract_reduced_costs = False
        extract_rc = False
        extract_lrc = False
        extract_urc = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "dual"):
                extract_duals = True
                flag = True
            if re.match(suffix, "slack"):
                extract_slacks = True
                flag = True
            if re.match(suffix, "rc"):
                extract_reduced_costs = True
                extract_rc = True
                flag = True
            if re.match(suffix, "lrc"):
                extract_reduced_costs = True
                extract_lrc = True
                flag = True
            if re.match(suffix, "urc"):
                extract_reduced_costs = True
                extract_urc = True
                flag = True
            if not flag:
                raise RuntimeError(
                    "***The xpress solver plugin cannot extract solution suffix="
                    + suffix)

        if not os.path.exists(self._soln_file):
            return

        soln = Solution()
        soln.objective['__default_objective__'] = {
            'Value': None
        }  # TBD: NOT SURE HOW TO EXTRACT THE OBJECTIVE VALUE YET!
        soln_variable = soln.variable  # caching for efficiency
        solution_file = open(self._soln_file, "r")
        results.problem.number_of_objectives = 1

        for line in solution_file:

            line = line.strip()
            tokens = line.split(',')

            name = tokens[0].strip("\" ")
            type = tokens[1].strip("\" ")

            primary_value = float(tokens[2].strip("\" "))
            secondary_value = float(tokens[3].strip("\" "))
            tertiary_value = float(tokens[4].strip("\" "))

            if type == "C":  # a 'C' type in Xpress is a variable (i.e., column) - everything else is a constraint.

                variable_name = name
                variable_value = primary_value
                variable_reduced_cost = None
                ### TODO: What is going on here with field_name, and shortly thereafter, with variable_status and whatnot? They've never been defined.
                ### It seems like this is trying to mimic the CPLEX solver but has some issues
                if (extract_reduced_costs is True) and (field_name
                                                        == "reducedCost"):
                    variable_reduced_cost = tertiary_value

                if variable_name != "ONE_VAR_CONSTANT":
                    variable = soln_variable[variable_name] = {
                        "Value": float(variable_value)
                    }
                    if (variable_reduced_cost
                            is not None) and (extract_reduced_costs is True):
                        try:
                            if extract_rc is True:
                                variable["Rc"] = float(variable_reduced_cost)
                            if variable_status is not None:
                                if extract_lrc is True:
                                    if variable_status == "LL":
                                        variable["Lrc"] = float(
                                            variable_reduced_cost)
                                    else:
                                        variable["Lrc"] = 0.0
                                if extract_urc is True:
                                    if variable_status == "UL":
                                        variable["Urc"] = float(
                                            variable_reduced_cost)
                                    else:
                                        variable["Urc"] = 0.0
                        except:
                            raise ValueError("Unexpected reduced-cost value=" +
                                             str(variable_reduced_cost) +
                                             " encountered for variable=" +
                                             variable_name)

            else:

                constraint = soln.constraint[name] = {}

                if (extract_duals is True) and (tertiary_value != 0.0):
                    constraint["Dual"] = tertiary_value
                if (extract_slacks is True) and (secondary_value != 0.0):
                    constraint["Slack"] = secondary_value

        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)
        solution_file.close()
Пример #12
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
Пример #13
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