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

        gprob = self._solver_model
        grb = self._gurobipy.GRB
        status = gprob.Status

        if gprob.getAttr(self._gurobipy.GRB.Attr.IsMIP):
            if extract_reduced_costs:
                logger.warning("Cannot get reduced costs for MIP.")
            if extract_duals:
                logger.warning("Cannot get duals for MIP.")
            extract_reduced_costs = False
            extract_duals = False

        self.results = SolverResults()
        soln = Solution()

        self.results.solver.name = self._name
        self.results.solver.wallclock_time = gprob.Runtime

        if status == grb.LOADED:  # problem is loaded, but no solution
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Model is loaded, but no solution information is available."
            self.results.solver.termination_condition = TerminationCondition.error
            soln.status = SolutionStatus.unknown
        elif status == grb.OPTIMAL:  # optimal
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \
                                                      "and an optimal solution is available."
            self.results.solver.termination_condition = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal
        elif status == grb.INFEASIBLE:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message = "Model was proven to be infeasible"
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible
        elif status == grb.INF_OR_UNBD:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message = "Problem proven to be infeasible or unbounded."
            self.results.solver.termination_condition = TerminationCondition.infeasibleOrUnbounded
            soln.status = SolutionStatus.unsure
        elif status == grb.UNBOUNDED:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message = "Model was proven to be unbounded."
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.unbounded
        elif status == grb.CUTOFF:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimal objective for model was proven to be worse than the " \
                                                      "value specified in the Cutoff parameter. No solution " \
                                                      "information is available."
            self.results.solver.termination_condition = TerminationCondition.minFunctionValue
            soln.status = SolutionStatus.unknown
        elif status == grb.ITERATION_LIMIT:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimization terminated because the total number of simplex " \
                                                      "iterations performed exceeded the value specified in the " \
                                                      "IterationLimit parameter."
            self.results.solver.termination_condition = TerminationCondition.maxIterations
            soln.status = SolutionStatus.stoppedByLimit
        elif status == grb.NODE_LIMIT:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimization terminated because the total number of " \
                                                      "branch-and-cut nodes explored exceeded the value specified " \
                                                      "in the NodeLimit parameter"
            self.results.solver.termination_condition = TerminationCondition.maxEvaluations
            soln.status = SolutionStatus.stoppedByLimit
        elif status == grb.TIME_LIMIT:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimization terminated because the time expended exceeded " \
                                                      "the value specified in the TimeLimit parameter."
            self.results.solver.termination_condition = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit
        elif status == grb.SOLUTION_LIMIT:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimization terminated because the number of solutions found " \
                                                      "reached the value specified in the SolutionLimit parameter."
            self.results.solver.termination_condition = TerminationCondition.unknown
            soln.status = SolutionStatus.stoppedByLimit
        elif status == grb.INTERRUPTED:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimization was terminated by the user."
            self.results.solver.termination_condition = TerminationCondition.error
            soln.status = SolutionStatus.error
        elif status == grb.NUMERIC:
            self.results.solver.status = SolverStatus.error
            self.results.solver.termination_message = "Optimization was terminated due to unrecoverable numerical " \
                                                      "difficulties."
            self.results.solver.termination_condition = TerminationCondition.error
            soln.status = SolutionStatus.error
        elif status == grb.SUBOPTIMAL:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message = "Unable to satisfy optimality tolerances; a sub-optimal " \
                                                      "solution is available."
            self.results.solver.termination_condition = TerminationCondition.other
            soln.status = SolutionStatus.feasible
        else:
            self.results.solver.status = SolverStatus.error
            self.results.solver.termination_message = "Unknown return code from GUROBI."
            self.results.solver.termination_condition = TerminationCondition.error
            soln.status = SolutionStatus.error

        self.results.problem.name = gprob.ModelName

        if gprob.ModelSense == 1:
            self.results.problem.sense = pyomo.core.kernel.minimize
        elif gprob.ModelSense == -1:
            self.results.problem.sense = pyomo.core.kernel.maximize
        else:
            raise RuntimeError(
                'Unrecognized gurobi objective sense: {0}'.format(
                    gprob.ModelSense))

        self.results.problem.upper_bound = None
        self.results.problem.lower_bound = None
        if (gprob.NumBinVars + gprob.NumIntVars) == 0:
            try:
                self.results.problem.upper_bound = gprob.ObjVal
                self.results.problem.lower_bound = gprob.ObjVal
            except (self._gurobipy.GurobiError, AttributeError):
                pass
        elif gprob.ModelSense == 1:  # minimizing
            try:
                self.results.problem.upper_bound = gprob.ObjVal
            except (self._gurobipy.GurobiError, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = gprob.ObjBound
            except (self._gurobipy.GurobiError, AttributeError):
                pass
        elif gprob.ModelSense == -1:  # maximizing
            try:
                self.results.problem.upper_bound = gprob.ObjBound
            except (self._gurobipy.GurobiError, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = gprob.ObjVal
            except (self._gurobipy.GurobiError, AttributeError):
                pass
        else:
            raise RuntimeError(
                'Unrecognized gurobi objective sense: {0}'.format(
                    gprob.ModelSense))

        try:
            soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound
        except TypeError:
            soln.gap = None

        self.results.problem.number_of_constraints = gprob.NumConstrs + gprob.NumQConstrs + gprob.NumSOS
        self.results.problem.number_of_nonzeros = gprob.NumNZs
        self.results.problem.number_of_variables = gprob.NumVars
        self.results.problem.number_of_binary_variables = gprob.NumBinVars
        self.results.problem.number_of_integer_variables = gprob.NumIntVars
        self.results.problem.number_of_continuous_variables = gprob.NumVars - gprob.NumIntVars - gprob.NumBinVars
        self.results.problem.number_of_objectives = 1
        self.results.problem.number_of_solutions = gprob.SolCount

        # if a solve was stopped by a limit, we still need to check to
        # see if there is a solution available - this may not always
        # be the case, both in LP and MIP contexts.
        if self._save_results:
            """
            This code in this if statement is only needed for backwards compatability. It is more efficient to set
            _save_results to False and use load_vars, load_duals, etc.
            """
            if gprob.SolCount > 0:
                soln_variables = soln.variable
                soln_constraints = soln.constraint

                gurobi_vars = self._solver_model.getVars()
                gurobi_vars = list(
                    set(gurobi_vars).intersection(
                        set(self._pyomo_var_to_solver_var_map.values())))
                var_vals = self._solver_model.getAttr("X", gurobi_vars)
                names = self._solver_model.getAttr("VarName", gurobi_vars)
                for gurobi_var, val, name in zip(gurobi_vars, var_vals, names):
                    pyomo_var = self._solver_var_to_pyomo_var_map[gurobi_var]
                    if self._referenced_variables[pyomo_var] > 0:
                        pyomo_var.stale = False
                        soln_variables[name] = {"Value": val}

                if extract_reduced_costs:
                    vals = self._solver_model.getAttr("Rc", gurobi_vars)
                    for gurobi_var, val, name in zip(gurobi_vars, vals, names):
                        pyomo_var = self._solver_var_to_pyomo_var_map[
                            gurobi_var]
                        if self._referenced_variables[pyomo_var] > 0:
                            soln_variables[name]["Rc"] = val

                if extract_duals or extract_slacks:
                    gurobi_cons = self._solver_model.getConstrs()
                    con_names = self._solver_model.getAttr(
                        "ConstrName", gurobi_cons)
                    for name in con_names:
                        soln_constraints[name] = {}
                    if self._version_major >= 5:
                        gurobi_q_cons = self._solver_model.getQConstrs()
                        q_con_names = self._solver_model.getAttr(
                            "QCName", gurobi_q_cons)
                        for name in q_con_names:
                            soln_constraints[name] = {}

                if extract_duals:
                    vals = self._solver_model.getAttr("Pi", gurobi_cons)
                    for val, name in zip(vals, con_names):
                        soln_constraints[name]["Dual"] = val
                    if self._version_major >= 5:
                        q_vals = self._solver_model.getAttr(
                            "QCPi", gurobi_q_cons)
                        for val, name in zip(q_vals, q_con_names):
                            soln_constraints[name]["Dual"] = val

                if extract_slacks:
                    gurobi_range_con_vars = set(
                        self._solver_model.getVars()) - set(
                            self._pyomo_var_to_solver_var_map.values())
                    vals = self._solver_model.getAttr("Slack", gurobi_cons)
                    for gurobi_con, val, name in zip(gurobi_cons, vals,
                                                     con_names):
                        pyomo_con = self._solver_con_to_pyomo_con_map[
                            gurobi_con]
                        if pyomo_con in self._range_constraints:
                            lin_expr = self._solver_model.getRow(gurobi_con)
                            for i in reversed(range(lin_expr.size())):
                                v = lin_expr.getVar(i)
                                if v in gurobi_range_con_vars:
                                    Us_ = v.X
                                    Ls_ = v.UB - v.X
                                    if Us_ > Ls_:
                                        soln_constraints[name]["Slack"] = Us_
                                    else:
                                        soln_constraints[name]["Slack"] = -Ls_
                                    break
                        else:
                            soln_constraints[name]["Slack"] = val
                    if self._version_major >= 5:
                        q_vals = self._solver_model.getAttr(
                            "QCSlack", gurobi_q_cons)
                        for val, name in zip(q_vals, q_con_names):
                            soln_constraints[name]["Slack"] = val
        elif self._load_solutions:
            if gprob.SolCount > 0:

                self._load_vars()

                if extract_reduced_costs:
                    self._load_rc()

                if extract_duals:
                    self._load_duals()

                if extract_slacks:
                    self._load_slacks()

        self.results.solution.insert(soln)

        # finally, clean any temporary files registered with the temp file
        # manager, created populated *directly* by this plugin.
        pyutilib.services.TempfileManager.pop(remove=not self._keepfiles)

        return DirectOrPersistentSolver._postsolve(self)
Beispiel #2
0
    def _postsolve(self):

        extract_duals = False
        extract_slacks = False
        extract_reduced_costs = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "dual"):
                extract_duals = True
                flag = True
            if re.match(suffix, "slack"):
                extract_slacks = True
                flag = True
            if re.match(suffix, "rc"):
                extract_reduced_costs = True
                flag = True
            if not flag:
                raise RuntimeError(
                    "***MOSEK solver plugin cannot extract solution suffix = "
                    + suffix)

        msk_task = self._solver_model
        msk = self._mosek

        itr_soltypes = [
            msk.problemtype.qo, msk.problemtype.qcqo, msk.problemtype.conic
        ]

        if (msk_task.getnumintvar() >= 1):
            self._whichsol = msk.soltype.itg
            if extract_reduced_costs:
                logger.warning("Cannot get reduced costs for MIP.")
            if extract_duals:
                logger.warning("Cannot get duals for MIP.")
            extract_reduced_costs = False
            extract_duals = False
        elif (msk_task.getprobtype() in itr_soltypes):
            self._whichsol = msk.soltype.itr

        whichsol = self._whichsol
        sol_status = msk_task.getsolsta(whichsol)
        pro_status = msk_task.getprosta(whichsol)

        self.results = SolverResults()
        soln = Solution()

        self.results.solver.name = self._name
        self.results.solver.wallclock_time = msk_task.getdouinf(
            msk.dinfitem.optimizer_time)

        SOLSTA_MAP = {
            msk.solsta.unknown: 'unknown',
            msk.solsta.optimal: 'optimal',
            msk.solsta.prim_and_dual_feas: 'pd_feas',
            msk.solsta.prim_feas: 'p_feas',
            msk.solsta.dual_feas: 'd_feas',
            msk.solsta.prim_infeas_cer: 'p_infeas',
            msk.solsta.dual_infeas_cer: 'd_infeas',
            msk.solsta.prim_illposed_cer: 'p_illposed',
            msk.solsta.dual_illposed_cer: 'd_illposed',
            msk.solsta.integer_optimal: 'optimal'
        }
        PROSTA_MAP = {
            msk.prosta.unknown: 'unknown',
            msk.prosta.prim_and_dual_feas: 'pd_feas',
            msk.prosta.prim_feas: 'p_feas',
            msk.prosta.dual_feas: 'd_feas',
            msk.prosta.prim_infeas: 'p_infeas',
            msk.prosta.dual_infeas: 'd_infeas',
            msk.prosta.prim_and_dual_infeas: 'pd_infeas',
            msk.prosta.ill_posed: 'illposed',
            msk.prosta.prim_infeas_or_unbounded: 'p_inf_unb'
        }

        if self._version[0] < 9:
            SOLSTA_OLD = {
                msk.solsta.near_optimal: 'optimal',
                msk.solsta.near_integer_optimal: 'optimal',
                msk.solsta.near_prim_feas: 'p_feas',
                msk.solsta.near_dual_feas: 'd_feas',
                msk.solsta.near_prim_and_dual_feas: 'pd_feas',
                msk.solsta.near_prim_infeas_cer: 'p_infeas',
                msk.solsta.near_dual_infeas_cer: 'd_infeas'
            }
            PROSTA_OLD = {
                msk.prosta.near_prim_and_dual_feas: 'pd_feas',
                msk.prosta.near_prim_feas: 'p_feas',
                msk.prosta.near_dual_feas: 'd_feas'
            }
            SOLSTA_MAP.update(SOLSTA_OLD)
            PROSTA_MAP.update(PROSTA_OLD)

        if self._termcode == msk.rescode.ok:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = ""

        elif self._termcode == msk.rescode.trm_max_iterations:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "Optimizer terminated at the maximum number of iterations."
            self.results.solver.termination_condition = TerminationCondition.maxIterations
            soln.status = SolutionStatus.stoppedByLimit

        elif self._termcode == msk.rescode.trm_max_time:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "Optimizer terminated at the maximum amount of time."
            self.results.solver.termination_condition = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit

        elif self._termcode == msk.rescode.trm_user_callback:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimizer terminated due to the return of the "\
                "user-defined callback function."
            self.results.solver.termination_condition = TerminationCondition.userInterrupt
            soln.status = SolutionStatus.unknown

        elif self._termcode in [
                msk.rescode.trm_mio_num_relaxs,
                msk.rescode.trm_mio_num_branches,
                msk.rescode.trm_num_max_num_int_solutions
        ]:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "The mixed-integer optimizer terminated as the maximum number "\
                "of relaxations/branches/feasible solutions was reached."
            self.results.solver.termination_condition = TerminationCondition.maxEvaluations
            soln.status = SolutionStatus.stoppedByLimit
        else:
            self.results.solver.termination_message = " Optimization terminated with {} response code." \
                "Check MOSEK response code documentation for more information.".format(
                    self._termcode)
            self.results.solver.termination_condition = TerminationCondition.unknown

        if SOLSTA_MAP[sol_status] == 'unknown':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " The solution status is unknown."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.unknown
            soln.status = SolutionStatus.unknown

        if PROSTA_MAP[pro_status] == 'd_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is dual infeasible"
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.unbounded

        elif PROSTA_MAP[pro_status] == 'p_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is primal infeasible."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible

        elif PROSTA_MAP[pro_status] == 'pd_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is primal and dual infeasible."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible

        elif PROSTA_MAP[pro_status] == 'p_inf_unb':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is either primal infeasible or unbounded."\
                " This may happen for MIPs."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasibleOrUnbounded
            soln.status = SolutionStatus.unsure

        if SOLSTA_MAP[sol_status] == 'optimal':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " Model was solved to optimality and an optimal solution is available."
            self.results.solver.termination_condition = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal

        elif SOLSTA_MAP[sol_status] == 'pd_feas':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " The solution is both primal and dual feasible."
            self.results.solver.termination_condition = TerminationCondition.feasible
            soln.status = SolutionStatus.feasible

        elif SOLSTA_MAP[sol_status] == 'p_feas':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " The solution is primal feasible."
            self.results.solver.termination_condition = TerminationCondition.feasible
            soln.status = SolutionStatus.feasible

        elif SOLSTA_MAP[sol_status] == 'd_feas':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " The solution is dual feasible."
            self.results.solver.termination_condition = TerminationCondition.feasible
            soln.status = SolutionStatus.feasible

        elif SOLSTA_MAP[sol_status] == 'd_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " The solution is a certificate of dual infeasibility."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.infeasible

        elif SOLSTA_MAP[sol_status] == 'p_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " The solution is a certificate of primal infeasibility."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible

        self.results.problem.name = msk_task.gettaskname()

        if msk_task.getobjsense() == msk.objsense.minimize:
            self.results.problem.sense = minimize
        elif msk_task.getobjsense() == msk.objsense.maximize:
            self.results.problem.sense = maximize
        else:
            raise RuntimeError(
                'Unrecognized Mosek objective sense: {0}'.format(
                    msk_task.getobjname()))

        self.results.problem.upper_bound = None
        self.results.problem.lower_bound = None

        if msk_task.getnumintvar() == 0:
            try:
                if msk_task.getobjsense() == msk.objsense.minimize:
                    self.results.problem.upper_bound = msk_task.getprimalobj(
                        whichsol)
                    self.results.problem.lower_bound = msk_task.getdualobj(
                        whichsol)
                elif msk_task.getobjsense() == msk.objsense.maximize:
                    self.results.problem.upper_bound = msk_task.getprimalobj(
                        whichsol)
                    self.results.problem.lower_bound = msk_task.getdualobj(
                        whichsol)

            except (msk.MosekException, AttributeError):
                pass
        elif msk_task.getobjsense() == msk.objsense.minimize:  # minimizing
            try:
                self.results.problem.upper_bound = msk_task.getprimalobj(
                    whichsol)
            except (msk.MosekException, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = msk_task.getdouinf(
                    msk.dinfitem.mio_obj_bound)
            except (msk.MosekException, AttributeError):
                pass
        elif msk_task.getobjsense() == msk.objsense.maximize:  # maximizing
            try:
                self.results.problem.upper_bound = msk_task.getdouinf(
                    msk.dinfitem.mio_obj_bound)
            except (msk.MosekException, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = msk_task.getprimalobj(
                    whichsol)
            except (msk.MosekException, AttributeError):
                pass
        else:
            raise RuntimeError(
                'Unrecognized Mosek objective sense: {0}'.format(
                    msk_task.getobjsense()))

        try:
            soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound
        except TypeError:
            soln.gap = None

        self.results.problem.number_of_constraints = msk_task.getnumcon()
        self.results.problem.number_of_nonzeros = msk_task.getnumanz()
        self.results.problem.number_of_variables = msk_task.getnumvar()
        self.results.problem.number_of_integer_variables = msk_task.getnumintvar(
        )
        self.results.problem.number_of_continuous_variables = msk_task.getnumvar() - \
            msk_task.getnumintvar()
        self.results.problem.number_of_objectives = 1
        self.results.problem.number_of_solutions = 1

        if self._save_results:
            """
            This code in this if statement is only needed for backwards compatability. It is more efficient to set
            _save_results to False and use load_vars, load_duals, etc.
            """
            if self.results.problem.number_of_solutions > 0:
                soln_variables = soln.variable
                soln_constraints = soln.constraint

                mosek_vars = list(range(msk_task.getnumvar()))
                mosek_vars = list(
                    set(mosek_vars).intersection(
                        set(self._pyomo_var_to_solver_var_map.values())))
                var_vals = [0.0] * len(mosek_vars)
                self._solver_model.getxx(whichsol, var_vals)
                names = list(map(msk_task.getvarname, mosek_vars))

                for mosek_var, val, name in zip(mosek_vars, var_vals, names):
                    pyomo_var = self._solver_var_to_pyomo_var_map[mosek_var]
                    if self._referenced_variables[pyomo_var] > 0:
                        soln_variables[name] = {"Value": val}

                if extract_reduced_costs:
                    vals = [0.0] * len(mosek_vars)
                    msk_task.getreducedcosts(whichsol, 0, len(mosek_vars),
                                             vals)
                    for mosek_var, val, name in zip(mosek_vars, vals, names):
                        pyomo_var = self._solver_var_to_pyomo_var_map[
                            mosek_var]
                        if self._referenced_variables[pyomo_var] > 0:
                            soln_variables[name]["Rc"] = val

                if extract_duals or extract_slacks:
                    mosek_cons = list(range(msk_task.getnumcon()))
                    con_names = list(map(msk_task.getconname, mosek_cons))
                    for name in con_names:
                        soln_constraints[name] = {}
                    """TODO wrong length, needs to be getnumvars()
                    mosek_cones = list(range(msk_task.getnumcone()))
                    cone_names = []
                    for cone in mosek_cones:
                        cone_names.append(msk_task.getconename(cone))
                    for name in cone_names:
                        soln_constraints[name] = {}
                    """

                if extract_duals:
                    ncon = msk_task.getnumcon()
                    if ncon > 0:
                        vals = [0.0] * ncon
                        msk_task.gety(whichsol, vals)
                        for val, name in zip(vals, con_names):
                            soln_constraints[name]["Dual"] = val
                    """TODO: wrong length, needs to be getnumvars()
                    ncone = msk_task.getnumcone()
                    if ncone > 0:
                        vals = [0.0]*ncone
                        msk_task.getsnx(whichsol, vals)
                        for val, name in zip(vals, cone_names):
                            soln_constraints[name]["Dual"] = val
                    """

                if extract_slacks:
                    Ax = [0] * len(mosek_cons)
                    msk_task.getxc(self._whichsol, Ax)
                    for con, name in zip(mosek_cons, con_names):
                        Us = Ls = 0

                        bk, lb, ub = msk_task.getconbound(con)

                        if bk in [
                                msk.boundkey.fx, msk.boundkey.ra,
                                msk.boundkey.up
                        ]:
                            Us = ub - Ax[con]
                        if bk in [
                                msk.boundkey.fx, msk.boundkey.ra,
                                msk.boundkey.lo
                        ]:
                            Ls = Ax[con] - lb

                        if Us > Ls:
                            soln_constraints[name]["Slack"] = Us
                        else:
                            soln_constraints[name]["Slack"] = -Ls

        elif self._load_solutions:
            if self.results.problem.number_of_solutions > 0:

                self.load_vars()

                if extract_reduced_costs:
                    self._load_rc()

                if extract_duals:
                    self._load_duals()

                if extract_slacks:
                    self._load_slacks()

        self.results.solution.insert(soln)

        # finally, clean any temporary files registered with the temp file
        # manager, created populated *directly* by this plugin.
        TempfileManager.pop(remove=not self._keepfiles)

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

        cpxprob = self._solver_model
        status = cpxprob.solution.get_status()

        if cpxprob.get_problem_type() in [
                cpxprob.problem_type.MILP, cpxprob.problem_type.MIQP,
                cpxprob.problem_type.MIQCP
        ]:
            if extract_reduced_costs:
                logger.warning("Cannot get reduced costs for MIP.")
            if extract_duals:
                logger.warning("Cannot get duals for MIP.")
            extract_reduced_costs = False
            extract_duals = False

        self.results = SolverResults()
        soln = Solution()

        self.results.solver.name = ("CPLEX {0}".format(cpxprob.get_version()))
        self.results.solver.wallclock_time = self._wallclock_time

        if status in [1, 101, 102]:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_condition = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal
        elif status in [2, 40, 118, 133, 134]:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.unbounded
        elif status in [4, 119, 134]:
            # Note: status of 4 means infeasible or unbounded
            #       and 119 means MIP infeasible or unbounded
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = \
                TerminationCondition.infeasibleOrUnbounded
            soln.status = SolutionStatus.unsure
        elif status in [3, 103]:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible
        elif status in [10]:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_condition = TerminationCondition.maxIterations
            soln.status = SolutionStatus.stoppedByLimit
        elif status in [11, 25, 107, 131]:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_condition = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit
        else:
            self.results.solver.status = SolverStatus.error
            self.results.solver.termination_condition = TerminationCondition.error
            soln.status = SolutionStatus.error

        if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize:
            self.results.problem.sense = pyomo.core.kernel.minimize
        elif cpxprob.objective.get_sense() == cpxprob.objective.sense.maximize:
            self.results.problem.sense = pyomo.core.kernel.maximize
        else:
            raise RuntimeError('Unrecognized cplex objective sense: {0}'.\
                               format(cpxprob.objective.get_sense()))

        self.results.problem.upper_bound = None
        self.results.problem.lower_bound = None
        if (cpxprob.variables.get_num_binary() +
                cpxprob.variables.get_num_integer()) == 0:
            try:
                self.results.problem.upper_bound = cpxprob.solution.get_objective_value(
                )
                self.results.problem.lower_bound = cpxprob.solution.get_objective_value(
                )
            except self._cplex.exceptions.CplexError:
                pass
        elif cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize:
            try:
                self.results.problem.upper_bound = cpxprob.solution.get_objective_value(
                )
            except self._cplex.exceptions.CplexError:
                pass
            try:
                self.results.problem.lower_bound = cpxprob.solution.MIP.get_best_objective(
                )
            except self._cplex.exceptions.CplexError:
                pass
        elif cpxprob.objective.get_sense() == cpxprob.objective.sense.maximize:
            try:
                self.results.problem.upper_bound = cpxprob.solution.MIP.get_best_objective(
                )
            except self._cplex.exceptions.CplexError:
                pass
            try:
                self.results.problem.lower_bound = cpxprob.solution.get_objective_value(
                )
            except self._cplex.exceptions.CplexError:
                pass
        else:
            raise RuntimeError('Unrecognized cplex objective sense: {0}'.\
                               format(cpxprob.objective.get_sense()))

        try:
            soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound
        except TypeError:
            soln.gap = None

        self.results.problem.name = cpxprob.get_problem_name()
        assert cpxprob.indicator_constraints.get_num() == 0
        self.results.problem.number_of_constraints = \
            (cpxprob.linear_constraints.get_num() +
             cpxprob.quadratic_constraints.get_num() +
             cpxprob.SOS.get_num())
        self.results.problem.number_of_nonzeros = None
        self.results.problem.number_of_variables = cpxprob.variables.get_num()
        self.results.problem.number_of_binary_variables = cpxprob.variables.get_num_binary(
        )
        self.results.problem.number_of_integer_variables = cpxprob.variables.get_num_integer(
        )
        assert cpxprob.variables.get_num_semiinteger() == 0
        assert cpxprob.variables.get_num_semicontinuous() == 0
        self.results.problem.number_of_continuous_variables = \
            (cpxprob.variables.get_num() -
             cpxprob.variables.get_num_binary() -
             cpxprob.variables.get_num_integer())
        self.results.problem.number_of_objectives = 1

        # only try to get objective and variable values if a solution exists
        if self._save_results:
            """
            This code in this if statement is only needed for backwards compatability. It is more efficient to set
            _save_results to False and use load_vars, load_duals, etc.
            """
            if cpxprob.solution.get_solution_type() > 0:
                soln_variables = soln.variable
                soln_constraints = soln.constraint

                var_names = self._solver_model.variables.get_names()
                var_names = list(
                    set(var_names).intersection(
                        set(self._pyomo_var_to_solver_var_map.values())))
                var_vals = self._solver_model.solution.get_values(var_names)
                for i, name in enumerate(var_names):
                    pyomo_var = self._solver_var_to_pyomo_var_map[name]
                    if self._referenced_variables[pyomo_var] > 0:
                        pyomo_var.stale = False
                        soln_variables[name] = {"Value": var_vals[i]}

                if extract_reduced_costs:
                    reduced_costs = self._solver_model.solution.get_reduced_costs(
                        var_names)
                    for i, name in enumerate(var_names):
                        pyomo_var = self._solver_var_to_pyomo_var_map[name]
                        if self._referenced_variables[pyomo_var] > 0:
                            soln_variables[name]["Rc"] = reduced_costs[i]

                if extract_slacks:
                    for con_name in self._solver_model.linear_constraints.get_names(
                    ):
                        soln_constraints[con_name] = {}
                    for con_name in self._solver_model.quadratic_constraints.get_names(
                    ):
                        soln_constraints[con_name] = {}
                elif extract_duals:
                    # CPLEX PYTHON API DOES NOT SUPPORT QUADRATIC DUAL COLLECTION
                    for con_name in self._solver_model.linear_constraints.get_names(
                    ):
                        soln_constraints[con_name] = {}

                if extract_duals:
                    dual_values = self._solver_model.solution.get_dual_values()
                    for i, con_name in enumerate(
                            self._solver_model.linear_constraints.get_names()):
                        soln_constraints[con_name]["Dual"] = dual_values[i]

                if extract_slacks:
                    linear_slacks = self._solver_model.solution.get_linear_slacks(
                    )
                    qudratic_slacks = self._solver_model.solution.get_quadratic_slacks(
                    )
                    for i, con_name in enumerate(
                            self._solver_model.linear_constraints.get_names()):
                        pyomo_con = self._solver_con_to_pyomo_con_map[con_name]
                        if pyomo_con in self._range_constraints:
                            R_ = self._solver_model.linear_constraints.get_range_values(
                                con_name)
                            if R_ == 0:
                                soln_constraints[con_name][
                                    "Slack"] = linear_slacks[i]
                            else:
                                Ls_ = linear_slacks[i]
                                Us_ = R_ - Ls_
                                if abs(Us_) > abs(Ls_):
                                    soln_constraints[con_name]["Slack"] = Us_
                                else:
                                    soln_constraints[con_name]["Slack"] = -Ls_
                        else:
                            soln_constraints[con_name][
                                "Slack"] = linear_slacks[i]
                    for i, con_name in enumerate(
                            self._solver_model.quadratic_constraints.get_names(
                            )):
                        soln_constraints[con_name]["Slack"] = qudratic_slacks[
                            i]
        elif self._load_solutions:
            if cpxprob.solution.get_solution_type() > 0:
                self._load_vars()

                if extract_reduced_costs:
                    self._load_rc()

                if extract_duals:
                    self._load_duals()

                if extract_slacks:
                    self._load_slacks()

        self.results.solution.insert(soln)

        # finally, clean any temporary files registered with the temp file
        # manager, created populated *directly* by this plugin.
        pyutilib.services.TempfileManager.pop(remove=not self._keepfiles)

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

        cpxprob = self._solver_model
        status = cpxprob.solution.get_status()
        rtn_codes = cpxprob.solution.status

        if cpxprob.get_problem_type() in [
                cpxprob.problem_type.MILP, cpxprob.problem_type.MIQP,
                cpxprob.problem_type.MIQCP
        ]:
            if extract_reduced_costs:
                logger.warning("Cannot get reduced costs for MIP.")
            if extract_duals:
                logger.warning("Cannot get duals for MIP.")
            extract_reduced_costs = False
            extract_duals = False

        self.results = SolverResults()
        soln = Solution()

        self.results.solver.name = ("CPLEX {0}".format(cpxprob.get_version()))
        self.results.solver.wallclock_time = self._wallclock_time
        self.results.solver.deterministic_time = self._deterministic_time

        if status in {
                rtn_codes.optimal,
                rtn_codes.MIP_optimal,
                rtn_codes.optimal_tolerance,
                rtn_codes.multiobj_optimal,
        }:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_condition = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal
        elif status in {
                rtn_codes.unbounded,
                40,
                rtn_codes.MIP_unbounded,
                rtn_codes.relaxation_unbounded,
                134,
                rtn_codes.multiobj_unbounded,
        }:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.unbounded
        elif status in {
                rtn_codes.infeasible_or_unbounded,
                rtn_codes.MIP_infeasible_or_unbounded,
                134,
                rtn_codes.multiobj_inforunbd,
        }:
            # Note: status of 4 means infeasible or unbounded
            #       and 119 means MIP infeasible or unbounded
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = \
                TerminationCondition.infeasibleOrUnbounded
            soln.status = SolutionStatus.unsure
        elif status in {
                rtn_codes.infeasible, rtn_codes.MIP_infeasible,
                rtn_codes.multiobj_infeasible
        }:
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible
        elif status in {rtn_codes.abort_iteration_limit}:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_condition = TerminationCondition.maxIterations
            soln.status = SolutionStatus.stoppedByLimit
        elif status in {rtn_codes.MIP_abort_feasible}:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_condition = TerminationCondition.userInterrupt
            soln.status = SolutionStatus.feasible
        elif status in {
                rtn_codes.solution_limit, rtn_codes.node_limit_feasible,
                rtn_codes.mem_limit_feasible
        }:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_condition = TerminationCondition.maxEvaluations
            soln.status = SolutionStatus.stoppedByLimit
        elif status in {
                rtn_codes.abort_time_limit,
                rtn_codes.abort_dettime_limit,
                rtn_codes.MIP_time_limit_feasible,
                rtn_codes.MIP_dettime_limit_feasible,
                rtn_codes.multiobj_stopped,
                rtn_codes.multiobj_non_optimal,
        } and cpxprob.solution.get_solution_type(
        ) != cpxprob.solution.type.none:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_condition = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit
        elif status in {
                rtn_codes.MIP_time_limit_infeasible,
                rtn_codes.MIP_dettime_limit_infeasible,
                rtn_codes.node_limit_infeasible,
                rtn_codes.mem_limit_infeasible,
                rtn_codes.MIP_abort_infeasible,
                rtn_codes.multiobj_stopped,
        } or self._error_code == self._cplex.exceptions.error_codes.CPXERR_NO_SOLN:
            # CPLEX doesn't have a solution status for `noSolution` so we assume this from the combination of
            # maxTimeLimit + infeasible (instead of a generic `TerminationCondition.error`).
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_condition = TerminationCondition.noSolution
            soln.status = SolutionStatus.unknown
        else:
            self.results.solver.status = SolverStatus.error
            self.results.solver.termination_condition = TerminationCondition.error
            soln.status = SolutionStatus.error

        self.results.solver.return_code = self._error_code
        self.results.solver.termination_message = cpxprob.solution.get_status_string(
            status)

        # Get additional solver output from log file
        if self.version() >= (12, 5, 1) and isinstance(self._log_file, str):
            _log_file = open(self._log_file, 'r')
            _close_log_file = True
            log_output: str = "".join(_log_file.readlines())
        else:
            _log_file = self._log_file
            _close_log_file = False
            log_output: str = ""

        # use regular expressions to use multi-line match patterns:
        self.results.solver.root_node_processing_time = get_root_node_processing_time(
            log_output=log_output)
        self.results.solver.tree_processing_time = get_tree_processing_time(
            log_output=log_output)

        mip_start_warning = False
        self.results.solver.n_solutions_found = 0
        for line in log_output.split("\n"):
            if (line.startswith('Warning') and re.search(
                    r'No solution found from \d+ MIP starts', line)):
                mip_start_warning = True

            tokens = re.split('[ \t]+', line.strip())
            if len(tokens) >= 9 and tokens[0] == "MIP" and tokens[
                    1] == "start" and tokens[7] == "objective":
                self.results.solver.warm_start_objective_value = float(
                    tokens[8].rstrip('.'))
            elif line.startswith("Found incumbent of value"):
                self.results.solver.n_solutions_found += 1

        if _close_log_file:
            _log_file.close()

        self.results.solver.mip_start_failed = mip_start_warning

        if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize:
            self.results.problem.sense = minimize
        elif cpxprob.objective.get_sense() == cpxprob.objective.sense.maximize:
            self.results.problem.sense = maximize
        else:
            raise RuntimeError('Unrecognized cplex objective sense: {0}'.\
                               format(cpxprob.objective.get_sense()))

        self.results.problem.upper_bound = None
        self.results.problem.lower_bound = None
        if cpxprob.solution.get_solution_type() != cpxprob.solution.type.none:
            if (cpxprob.variables.get_num_binary() +
                    cpxprob.variables.get_num_integer()) == 0:
                self.results.problem.upper_bound = cpxprob.solution.get_objective_value(
                )
                self.results.problem.lower_bound = cpxprob.solution.get_objective_value(
                )
            elif cpxprob.objective.get_sense(
            ) == cpxprob.objective.sense.minimize:
                self.results.problem.upper_bound = cpxprob.solution.get_objective_value(
                )
                self.results.problem.lower_bound = cpxprob.solution.MIP.get_best_objective(
                )
            else:
                assert cpxprob.objective.get_sense(
                ) == cpxprob.objective.sense.maximize
                self.results.problem.upper_bound = cpxprob.solution.MIP.get_best_objective(
                )
                self.results.problem.lower_bound = cpxprob.solution.get_objective_value(
                )

        try:
            soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound
        except TypeError:
            soln.gap = None

        self.results.problem.name = cpxprob.get_problem_name()
        assert cpxprob.indicator_constraints.get_num() == 0
        self.results.problem.number_of_constraints = \
            (cpxprob.linear_constraints.get_num() +
             cpxprob.quadratic_constraints.get_num() +
             cpxprob.SOS.get_num())
        self.results.problem.number_of_nonzeros = None
        self.results.problem.number_of_variables = cpxprob.variables.get_num()
        self.results.problem.number_of_binary_variables = cpxprob.variables.get_num_binary(
        )
        self.results.problem.number_of_integer_variables = cpxprob.variables.get_num_integer(
        )
        assert cpxprob.variables.get_num_semiinteger() == 0
        assert cpxprob.variables.get_num_semicontinuous() == 0
        self.results.problem.number_of_continuous_variables = \
            (cpxprob.variables.get_num() -
             cpxprob.variables.get_num_binary() -
             cpxprob.variables.get_num_integer())
        self.results.problem.number_of_objectives = 1

        # only try to get objective and variable values if a solution exists
        if self._save_results:
            """
            This code in this if statement is only needed for backwards compatability. It is more efficient to set
            _save_results to False and use load_vars, load_duals, etc.
            """
            if cpxprob.solution.get_solution_type() > 0:
                soln_variables = soln.variable
                soln_constraints = soln.constraint

                var_names = self._solver_model.variables.get_names()
                assert set(var_names) == set(
                    self._pyomo_var_to_solver_var_map.values())
                var_vals = self._solver_model.solution.get_values()
                for name, val in zip(var_names, var_vals):
                    pyomo_var = self._solver_var_to_pyomo_var_map[name]
                    if self._referenced_variables[pyomo_var] > 0:
                        pyomo_var.stale = False
                        soln_variables[name] = {"Value": val}

                if extract_reduced_costs:
                    reduced_costs = self._solver_model.solution.get_reduced_costs(
                        var_names)
                    for i, name in enumerate(var_names):
                        pyomo_var = self._solver_var_to_pyomo_var_map[name]
                        if self._referenced_variables[pyomo_var] > 0:
                            soln_variables[name]["Rc"] = reduced_costs[i]

                if extract_slacks:
                    for con_name in self._solver_model.linear_constraints.get_names(
                    ):
                        soln_constraints[con_name] = {}
                    for con_name in self._solver_model.quadratic_constraints.get_names(
                    ):
                        soln_constraints[con_name] = {}
                elif extract_duals:
                    # CPLEX PYTHON API DOES NOT SUPPORT QUADRATIC DUAL COLLECTION
                    for con_name in self._solver_model.linear_constraints.get_names(
                    ):
                        soln_constraints[con_name] = {}

                if extract_duals:
                    dual_values = self._solver_model.solution.get_dual_values()
                    for i, con_name in enumerate(
                            self._solver_model.linear_constraints.get_names()):
                        soln_constraints[con_name]["Dual"] = dual_values[i]

                if extract_slacks:
                    linear_slacks = self._solver_model.solution.get_linear_slacks(
                    )
                    qudratic_slacks = self._solver_model.solution.get_quadratic_slacks(
                    )
                    for i, con_name in enumerate(
                            self._solver_model.linear_constraints.get_names()):
                        pyomo_con = self._solver_con_to_pyomo_con_map[con_name]
                        if pyomo_con in self._range_constraints:
                            R_ = self._solver_model.linear_constraints.get_range_values(
                                con_name)
                            if R_ == 0:
                                soln_constraints[con_name][
                                    "Slack"] = linear_slacks[i]
                            else:
                                Ls_ = linear_slacks[i]
                                Us_ = R_ - Ls_
                                if abs(Us_) > abs(Ls_):
                                    soln_constraints[con_name]["Slack"] = Us_
                                else:
                                    soln_constraints[con_name]["Slack"] = -Ls_
                        else:
                            soln_constraints[con_name][
                                "Slack"] = linear_slacks[i]
                    for i, con_name in enumerate(
                            self._solver_model.quadratic_constraints.get_names(
                            )):
                        soln_constraints[con_name]["Slack"] = qudratic_slacks[
                            i]
        elif self._load_solutions:
            if cpxprob.solution.get_solution_type() > 0:
                self._load_vars()

                if extract_reduced_costs:
                    self._load_rc()

                if extract_duals:
                    self._load_duals()

                if extract_slacks:
                    self._load_slacks()

        self.results.solution.insert(soln)

        # finally, clean any temporary files registered with the temp file
        # manager, created populated *directly* by this plugin.
        TempfileManager.pop(remove=not self._keepfiles)

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

        xprob = self._solver_model
        xp = xpress
        xprob_attrs = xprob.attributes

        ## XPRESS's status codes depend on this
        ## (number of integer vars > 0) or (number of special order sets > 0)
        is_mip = (xprob_attrs.mipents > 0) or (xprob_attrs.sets > 0)

        if is_mip:
            if extract_reduced_costs:
                logger.warning("Cannot get reduced costs for MIP.")
            if extract_duals:
                logger.warning("Cannot get duals for MIP.")
            extract_reduced_costs = False
            extract_duals = False

        self.results = SolverResults()
        soln = Solution()

        self.results.solver.name = XpressDirect._name
        self.results.solver.wallclock_time = self._opt_time

        if is_mip:
            status = xprob_attrs.mipstatus
            mip_sols = xprob_attrs.mipsols
            if status == xp.mip_not_loaded:
                self.results.solver.status = SolverStatus.aborted
                self.results.solver.termination_message = "Model is not loaded; no solution information is available."
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.unknown
            #no MIP solution, first LP did not solve, second LP did, third search started but incomplete
            elif status == xp.mip_lp_not_optimal \
                    or status == xp.mip_lp_optimal \
                    or status == xp.mip_no_sol_found:
                self.results.solver.status = SolverStatus.aborted
                self.results.solver.termination_message = "Model is loaded, but no solution information is available."
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.unknown
            elif status == xp.mip_solution:  # some solution available
                self.results.solver.status = SolverStatus.warning
                self.results.solver.termination_message = "Unable to satisfy optimality tolerances; a sub-optimal " \
                                                          "solution is available."
                self.results.solver.termination_condition = TerminationCondition.other
                soln.status = SolutionStatus.feasible
            elif status == xp.mip_infeas:  # MIP proven infeasible
                self.results.solver.status = SolverStatus.warning
                self.results.solver.termination_message = "Model was proven to be infeasible"
                self.results.solver.termination_condition = TerminationCondition.infeasible
                soln.status = SolutionStatus.infeasible
            elif status == xp.mip_optimal:  # optimal
                self.results.solver.status = SolverStatus.ok
                self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \
                                                          "and an optimal solution is available."
                self.results.solver.termination_condition = TerminationCondition.optimal
                soln.status = SolutionStatus.optimal
            elif status == xp.mip_unbounded and mip_sols > 0:
                self.results.solver.status = SolverStatus.warning
                self.results.solver.termination_message = "LP relaxation was proven to be unbounded, " \
                                                          "but a solution is available."
                self.results.solver.termination_condition = TerminationCondition.unbounded
                soln.status = SolutionStatus.unbounded
            elif status == xp.mip_unbounded and mip_sols <= 0:
                self.results.solver.status = SolverStatus.warning
                self.results.solver.termination_message = "LP relaxation was proven to be unbounded."
                self.results.solver.termination_condition = TerminationCondition.unbounded
                soln.status = SolutionStatus.unbounded
            else:
                self.results.solver.status = SolverStatus.error
                self.results.solver.termination_message = \
                    ("Unhandled Xpress solve status "
                     "("+str(status)+")")
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.error
        else:  ## an LP, we'll check the lpstatus
            status = xprob_attrs.lpstatus
            if status == xp.lp_unstarted:
                self.results.solver.status = SolverStatus.aborted
                self.results.solver.termination_message = "Model is not loaded; no solution information is available."
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.unknown
            elif status == xp.lp_optimal:
                self.results.solver.status = SolverStatus.ok
                self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \
                                                          "and an optimal solution is available."
                self.results.solver.termination_condition = TerminationCondition.optimal
                soln.status = SolutionStatus.optimal
            elif status == xp.lp_infeas:
                self.results.solver.status = SolverStatus.warning
                self.results.solver.termination_message = "Model was proven to be infeasible"
                self.results.solver.termination_condition = TerminationCondition.infeasible
                soln.status = SolutionStatus.infeasible
            elif status == xp.lp_cutoff:
                self.results.solver.status = SolverStatus.ok
                self.results.solver.termination_message = "Optimal objective for model was proven to be worse than the " \
                                                          "cutoff value specified; a solution is available."
                self.results.solver.termination_condition = TerminationCondition.minFunctionValue
                soln.status = SolutionStatus.optimal
            elif status == xp.lp_unfinished:
                self.results.solver.status = SolverStatus.aborted
                self.results.solver.termination_message = "Optimization was terminated by the user."
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.error
            elif status == xp.lp_unbounded:
                self.results.solver.status = SolverStatus.warning
                self.results.solver.termination_message = "Model was proven to be unbounded."
                self.results.solver.termination_condition = TerminationCondition.unbounded
                soln.status = SolutionStatus.unbounded
            elif status == xp.lp_cutoff_in_dual:
                self.results.solver.status = SolverStatus.ok
                self.results.solver.termination_message = "Xpress reported the LP was cutoff in the dual."
                self.results.solver.termination_condition = TerminationCondition.minFunctionValue
                soln.status = SolutionStatus.optimal
            elif status == xp.lp_unsolved:
                self.results.solver.status = SolverStatus.error
                self.results.solver.termination_message = "Optimization was terminated due to unrecoverable numerical " \
                                                          "difficulties."
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.error
            elif status == xp.lp_nonconvex:
                self.results.solver.status = SolverStatus.error
                self.results.solver.termination_message = "Optimization was terminated because nonconvex quadratic data " \
                                                          "were found."
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.error
            else:
                self.results.solver.status = SolverStatus.error
                self.results.solver.termination_message = \
                    ("Unhandled Xpress solve status "
                     "("+str(status)+")")
                self.results.solver.termination_condition = TerminationCondition.error
                soln.status = SolutionStatus.error

        self.results.problem.name = xprob_attrs.matrixname

        if xprob_attrs.objsense == 1.0:
            self.results.problem.sense = minimize
        elif xprob_attrs.objsense == -1.0:
            self.results.problem.sense = maximize
        else:
            raise RuntimeError(
                'Unrecognized Xpress objective sense: {0}'.format(
                    xprob_attrs.objsense))

        self.results.problem.upper_bound = None
        self.results.problem.lower_bound = None
        if not is_mip:  #LP or continuous problem
            try:
                self.results.problem.upper_bound = xprob_attrs.lpobjval
                self.results.problem.lower_bound = xprob_attrs.lpobjval
            except (XpressDirect.XpressException, AttributeError):
                pass
        elif xprob_attrs.objsense == 1.0:  # minimizing MIP
            try:
                self.results.problem.upper_bound = xprob_attrs.mipbestobjval
            except (XpressDirect.XpressException, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = xprob_attrs.bestbound
            except (XpressDirect.XpressException, AttributeError):
                pass
        elif xprob_attrs.objsense == -1.0:  # maximizing MIP
            try:
                self.results.problem.upper_bound = xprob_attrs.bestbound
            except (XpressDirect.XpressException, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = xprob_attrs.mipbestobjval
            except (XpressDirect.XpressException, AttributeError):
                pass
        else:
            raise RuntimeError(
                'Unrecognized xpress objective sense: {0}'.format(
                    xprob_attrs.objsense))

        try:
            soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound
        except TypeError:
            soln.gap = None

        self.results.problem.number_of_constraints = xprob_attrs.rows + xprob_attrs.sets + xprob_attrs.qconstraints
        self.results.problem.number_of_nonzeros = xprob_attrs.elems
        self.results.problem.number_of_variables = xprob_attrs.cols
        self.results.problem.number_of_integer_variables = xprob_attrs.mipents
        self.results.problem.number_of_continuous_variables = xprob_attrs.cols - xprob_attrs.mipents
        self.results.problem.number_of_objectives = 1
        self.results.problem.number_of_solutions = xprob_attrs.mipsols if is_mip else 1

        # if a solve was stopped by a limit, we still need to check to
        # see if there is a solution available - this may not always
        # be the case, both in LP and MIP contexts.
        if self._save_results:
            """
            This code in this if statement is only needed for backwards compatability. It is more efficient to set
            _save_results to False and use load_vars, load_duals, etc.
            """
            if xprob_attrs.lpstatus in \
                    [xp.lp_optimal, xp.lp_cutoff, xp.lp_cutoff_in_dual] or \
                    xprob_attrs.mipsols > 0:
                soln_variables = soln.variable
                soln_constraints = soln.constraint

                xpress_vars = list(self._solver_var_to_pyomo_var_map.keys())
                var_vals = xprob.getSolution(xpress_vars)
                for xpress_var, val in zip(xpress_vars, var_vals):
                    pyomo_var = self._solver_var_to_pyomo_var_map[xpress_var]
                    if self._referenced_variables[pyomo_var] > 0:
                        pyomo_var.stale = False
                        soln_variables[xpress_var.name] = {"Value": val}

                if extract_reduced_costs:
                    vals = xprob.getRCost(xpress_vars)
                    for xpress_var, val in zip(xpress_vars, vals):
                        pyomo_var = self._solver_var_to_pyomo_var_map[
                            xpress_var]
                        if self._referenced_variables[pyomo_var] > 0:
                            soln_variables[xpress_var.name]["Rc"] = val

                if extract_duals or extract_slacks:
                    xpress_cons = list(
                        self._solver_con_to_pyomo_con_map.keys())
                    for con in xpress_cons:
                        soln_constraints[con.name] = {}

                if extract_duals:
                    vals = xprob.getDual(xpress_cons)
                    for val, con in zip(vals, xpress_cons):
                        soln_constraints[con.name]["Dual"] = val

                if extract_slacks:
                    vals = xprob.getSlack(xpress_cons)
                    for con, val in zip(xpress_cons, vals):
                        if con in self._range_constraints:
                            ## for xpress, the slack on a range constraint
                            ## is based on the upper bound
                            lb = con.lb
                            ub = con.ub
                            ub_s = val
                            expr_val = ub - ub_s
                            lb_s = lb - expr_val
                            if abs(ub_s) > abs(lb_s):
                                soln_constraints[con.name]["Slack"] = ub_s
                            else:
                                soln_constraints[con.name]["Slack"] = lb_s
                        else:
                            soln_constraints[con.name]["Slack"] = val

        elif self._load_solutions:
            if xprob_attrs.lpstatus == xp.lp_optimal and \
                    ((not is_mip) or (xprob_attrs.mipsols > 0)):

                self._load_vars()

                if extract_reduced_costs:
                    self._load_rc()

                if extract_duals:
                    self._load_duals()

                if extract_slacks:
                    self._load_slacks()

        self.results.solution.insert(soln)

        # finally, clean any temporary files registered with the temp file
        # manager, created populated *directly* by this plugin.
        TempfileManager.pop(remove=not self._keepfiles)
        return DirectOrPersistentSolver._postsolve(self)
Beispiel #6
0
    def solve(self, *args, **kwargs):
        t0 = time.time()
        logger.info('{0:<10}{1:<12}{2:<12}{3:<12}{4:<12}'.format(
            'Iter', 'objective', 'max_viol', 'time', '# cuts'))

        if not self._using_persistent_solver:
            self._pyomo_model = args[0]

        options = self.options(kwargs.pop('options', dict()))
        subproblem_solver_options = self.subproblem_solver_options(
            kwargs.pop('subproblem_solver_options', dict()))

        obj = None
        for _obj in self._pyomo_model.component_data_objects(pe.Objective,
                                                             descend_into=True,
                                                             active=True,
                                                             sort=True):
            if obj is not None:
                raise ValueError('Found multiple active objectives')
            obj = _obj
        if obj is None:
            raise ValueError('Could not find any active objectives')

        final_res = SolverResults()

        self._relaxations = ComponentSet()
        self._relaxations_not_tracking_solver = ComponentSet()
        self._relaxations_with_added_cuts = ComponentSet()
        for b in self._pyomo_model.component_data_objects(pe.Block,
                                                          descend_into=True,
                                                          active=True,
                                                          sort=True):
            if isinstance(b, (BaseRelaxationData, BaseRelaxation)):
                self._relaxations.add(b)
                if self._using_persistent_solver:
                    if self not in b._persistent_solvers:
                        b.add_persistent_solver(self)
                        self._relaxations_not_tracking_solver.add(b)

        for _iter in range(options.max_iter):
            if time.time() - t0 > options.time_limit:
                final_res.solver.termination_condition = pe.TerminationCondition.maxTimeLimit
                final_res.solver.status = pe.SolverStatus.aborted
                logger.warning('ECPBounder: time limit reached.')
                break
            if self._using_persistent_solver:
                res = self._subproblem_solver.solve(
                    save_results=False, options=subproblem_solver_options)
            else:
                res = self._subproblem_solver.solve(
                    self._pyomo_model, options=subproblem_solver_options)
            if res.solver.termination_condition != pe.TerminationCondition.optimal:
                final_res.solver.termination_condition = pe.TerminationCondition.other
                final_res.solver.status = pe.SolverStatus.aborted
                logger.warning(
                    'ECPBounder: subproblem did not terminate optimally')
                break

            num_cuts_added = 0
            max_viol = 0
            for b in self._relaxations:
                viol = None
                if b.is_rhs_convex():
                    viol = pe.value(b.get_rhs_expr()) - b.get_aux_var().value
                elif b.is_rhs_concave():
                    viol = b.get_aux_var().value - pe.value(b.get_rhs_expr())
                if viol is not None:
                    if viol > max_viol:
                        max_viol = viol
                    if viol > options.feasibility_tol:
                        b.add_cut(keep_cut=options.keep_cuts)
                        self._relaxations_with_added_cuts.add(b)
                        num_cuts_added += 1

            if obj.sense == pe.minimize:
                obj_val = res.problem.lower_bound
                final_res.problem.sense = pe.minimize
                final_res.problem.upper_bound = None
                final_res.problem.lower_bound = obj_val
            else:
                obj_val = res.problem.upper_bound
                final_res.problem.sense = pe.maximize
                final_res.problem.lower_bound = None
                final_res.problem.upper_bound = obj_val
            elapsed_time = time.time() - t0
            logger.info(
                '{0:<10d}{1:<12.3e}{2:<12.3e}{3:<12.3e}{4:<12d}'.format(
                    _iter, obj_val, max_viol, elapsed_time, num_cuts_added))

            if num_cuts_added == 0:
                final_res.solver.termination_condition = pe.TerminationCondition.optimal
                final_res.solver.status = pe.SolverStatus.ok
                logger.info('ECPBounder: converged!')
                break

            if _iter == options.max_iter - 1:
                final_res.solver.termination_condition = pe.TerminationCondition.maxIterations
                final_res.solver.status = pe.SolverStatus.aborted
                logger.warning(
                    'ECPBounder: reached maximum number of iterations')

        if not options.keep_cuts:
            for b in self._relaxations_with_added_cuts:
                b.rebuild()

        if self._using_persistent_solver:
            for b in self._relaxations_not_tracking_solver:
                b.remove_persistent_solver(self)

        final_res.solver.wallclock_time = time.time() - t0
        return final_res
Beispiel #7
0
    def solve(self,
              model: _BlockData,
              tee: bool = False,
              load_solutions: bool = True,
              logfile: Optional[str] = None,
              solnfile: Optional[str] = None,
              timelimit: Optional[float] = None,
              report_timing: bool = False,
              solver_io: Optional[str] = None,
              suffixes: Optional[Sequence] = None,
              options: Optional[Dict] = None,
              keepfiles: bool = False,
              symbolic_solver_labels: bool = False):
        original_config = self.config
        self.config = self.config()
        self.config.stream_solver = tee
        self.config.load_solution = load_solutions
        self.config.symbolic_solver_labels = symbolic_solver_labels
        self.config.time_limit = timelimit
        self.config.report_timing = report_timing
        if solver_io is not None:
            raise NotImplementedError('Still working on this')
        if suffixes is not None:
            raise NotImplementedError('Still working on this')
        if logfile is not None:
            raise NotImplementedError('Still working on this')
        if 'keepfiles' in self.config:
            self.config.keepfiles = keepfiles
        if solnfile is not None:
            if 'filename' in self.config:
                filename = os.path.splitext(solnfile)[0]
                self.config.filename = filename
        original_options = self.options
        if options is not None:
            self.options = options

        results: Results = super(LegacySolverInterface, self).solve(model)

        legacy_results = LegacySolverResults()
        legacy_soln = LegacySolution()
        legacy_results.solver.status = legacy_solver_status_map[results.termination_condition]
        legacy_results.solver.termination_condition = legacy_termination_condition_map[results.termination_condition]
        legacy_soln.status = legacy_solution_status_map[results.termination_condition]
        legacy_results.solver.termination_message = str(results.termination_condition)

        obj = get_objective(model)
        legacy_results.problem.sense = obj.sense

        if obj.sense == minimize:
            legacy_results.problem.lower_bound = results.best_objective_bound
            legacy_results.problem.upper_bound = results.best_feasible_objective
        else:
            legacy_results.problem.upper_bound = results.best_objective_bound
            legacy_results.problem.lower_bound = results.best_feasible_objective
        if results.best_feasible_objective is not None and results.best_objective_bound is not None:
            legacy_soln.gap = abs(results.best_feasible_objective - results.best_objective_bound)
        else:
            legacy_soln.gap = None

        symbol_map = SymbolMap()
        symbol_map.byObject = dict(self.symbol_map.byObject)
        symbol_map.bySymbol = {symb: weakref.ref(obj()) for symb, obj in self.symbol_map.bySymbol.items()}
        symbol_map.aliases = {symb: weakref.ref(obj()) for symb, obj in self.symbol_map.aliases.items()}
        symbol_map.default_labeler = self.symbol_map.default_labeler
        model.solutions.add_symbol_map(symbol_map)
        legacy_results._smap_id = id(symbol_map)

        delete_legacy_soln = True
        if load_solutions:
            if hasattr(model, 'dual') and model.dual.import_enabled():
                for c, val in results.solution_loader.get_duals().items():
                    model.dual[c] = val
            if hasattr(model, 'slack') and model.slack.import_enabled():
                for c, val in results.solution_loader.get_slacks().items():
                    model.slack[c] = val
            if hasattr(model, 'rc') and model.rc.import_enabled():
                for v, val in results.solution_loader.get_reduced_costs().items():
                    model.rc[v] = val
        elif results.best_feasible_objective is not None:
            delete_legacy_soln = False
            for v, val in results.solution_loader.get_primals().items():
                legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val}
            if hasattr(model, 'dual') and model.dual.import_enabled():
                for c, val in results.solution_loader.get_duals().items():
                    legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val}
            if hasattr(model, 'slack') and model.slack.import_enabled():
                for c, val in results.solution_loader.get_slacks().items():
                    symbol = symbol_map.getSymbol(c)
                    if symbol in legacy_soln.constraint:
                        legacy_soln.constraint[symbol]['Slack'] = val
            if hasattr(model, 'rc') and model.rc.import_enabled():
                for v, val in results.solution_loader.get_reduced_costs().items():
                    legacy_soln.variable['Rc'] = val

        legacy_results.solution.insert(legacy_soln)
        if delete_legacy_soln:
            legacy_results.solution.delete(0)

        self.config = original_config
        self.options = original_options

        return legacy_results
Beispiel #8
0
    def _postprocess(self, result):
        """Create a SolverResult object, save it as _result and write
        solution data onto the user given Pyomo object (_py_model).
        :param result: The BnBResult indicating the result of the
        branch and bound process.
        """
        assert self._stage == SolvingStage.DONE
        assert type(result) is BnBResult

        # Copy solution to model.
        optinst = self._bnb_tree.best_instance()
        if optinst is not None:
            optinst.write_solution(self._py_model)

        # Create result object.
        self._result = SolverResults()
        soln = Solution()

        # Set some result meta data.
        self._result.solver.name = ('PyMINLP')
        self._result.solver.wallclock_time = Stats.get_solver_time()

        # Set solver status.
        if result == BnBResult.INFEASIBLE:
            self._result.solver.status = SolverStatus.ok
            self._result.solver.termination_condition \
                = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible
        elif result == BnBResult.OPTIMAL:
            self._result.solver.status = SolverStatus.ok
            self._result.solver.termination_condition \
                = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal
        elif result == BnBResult.TIMEOUT:
            self._result.solver.status = SolverStatus.ok
            self._result.solver.termination_condition \
                = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit

        if result == BnBResult.OPTIMAL or result == BnBResult.TIMEOUT:
            # Set solution data.
            self._result.problem.sense = pyomo.core.kernel.minimize
            self._result.problem.lower_bound = self._bnb_tree.lower_bound()
            self._result.problem.upper_bound = self._bnb_tree.upper_bound()
            soln.gap = self._bnb_tree.upper_bound() \
                       - self._bnb_tree.lower_bound()

            if optinst is not None:
                # Set objective data.
                obj = self._py_model.component_objects(Objective)
                for o in obj:
                    obj_name = o.name
                    obj_val = value(o)
                    break
                soln.objective[obj_name] = {'Value': obj_val}

                # Set variable data.
                #for vartype in self._py_model.component_objects(Var):
                #    for v in vartype:
                #        name = vartype[v].name
                #        val = value(vartype[v])
                #        soln.variable[name] = {'Value': val}

        # Set problem instance data.
        self._result.problem.name = self._py_model.name
        self._result.problem.number_of_constraints = \
            self._py_model.nconstraints()
        # self._result.problem.number_of_nonzeros = None
        self._result.problem.number_of_variables = self._py_model.nvariables()
        # self._result.problem.number_of_binary_variables = None
        # self._result.problem.number_of_integer_variables = None
        # self._result.problem.number_of_continuous_variables = None
        self._result.problem.number_of_objectives = \
            self._py_model.nobjectives()

        # Set branch and bound data.
        nsubprob = 'Number of created subproblems'
        self._result.solver.statistics.branch_and_bound[nsubprob] = \
            self._bnb_tree.nopennodes() + self._bnb_tree.nconsiderednodes()
        nsubprob = 'Number of considered subproblems'
        self._result.solver.statistics.branch_and_bound[nsubprob] = \
            self._bnb_tree.nconsiderednodes()

        self._result.solution.insert(soln)