Source code for picos.solvers.solver_gurobi

# coding: utf-8

# ------------------------------------------------------------------------------
# Copyright (C) 2018-2019 Maximilian Stahlberg
#
# This file is part of PICOS.
#
# PICOS is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PICOS is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------

"""Implementation of :class:`GurobiSolver`."""

from collections import namedtuple

import cvxopt

from ..apidoc import api_end, api_start
from ..compat import assert_import_exists
from ..constraints import (AffineConstraint, ConvexQuadraticConstraint,
                           RSOCConstraint, SOCConstraint)
from ..expressions import (CONTINUOUS_VARTYPES, AffineExpression,
                           BinaryVariable, IntegerVariable,
                           QuadraticExpression)
from ..modeling.footprint import Specification
from ..modeling.solution import (PS_FEASIBLE, PS_INFEASIBLE, PS_UNBOUNDED,
                                 PS_UNKNOWN, PS_UNSTABLE, SS_EMPTY,
                                 SS_FEASIBLE, SS_INFEASIBLE, SS_OPTIMAL,
                                 SS_PREMATURE, SS_UNKNOWN)
from .solver import Solver

_API_START = api_start(globals())
# -------------------------------


[docs]class GurobiSolver(Solver): """Interface to the Gurobi solver via its official Python interface.""" # TODO: Allow nonconvex quadratic constraints in the integer case? # TODO: Don't support (conic) quadratic constraints when duals are # requested because their precision is bad and can't be controlled. SUPPORTED = Specification.compile( directions=Specification.ALL, objectives=[ AffineExpression, QuadraticExpression], variables=Specification.ALL, constraints=[ AffineConstraint, SOCConstraint, RSOCConstraint, ConvexQuadraticConstraint])
[docs] @classmethod def supports(cls, footprint): """Implement :meth:`~.solver.Solver.supports`.""" if not Solver.supports(footprint): return False if footprint.nonconvex_quadratic_objective and footprint.continuous: return False return footprint << cls.SUPPORTED
[docs] @classmethod def default_penalty(cls): """Implement :meth:`~.solver.Solver.default_penalty`.""" return 0.0 # Commercial solver.
[docs] @classmethod def test_availability(cls): """Implement :meth:`~.solver.Solver.test_availability`.""" assert_import_exists("gurobipy")
[docs] @classmethod def names(cls): """Implement :meth:`~.solver.Solver.names`.""" return "gurobi", "Gurobi", "Gurobi Optimizer"
GurobiSOCC = namedtuple("GurobiSOCC", ("LHSVars", "RHSVar", "LHSCons", "RHSCon", "quadCon")) GurobiRSOCC = namedtuple("GurobiRSOCC", ("LHSVars", "RHSVars", "LHSCons", "RHSCons", "quadCon"))
[docs] def __init__(self, problem): """Initialize a Gurobi solver interface. :param ~picos.Problem problem: The problem to be solved. """ super(GurobiSolver, self).__init__(problem) self._gurobiVar = dict() """Maps PICOS variable indices to Gurobi variables.""" self._gurobiLinearConstraints = dict() """Maps a PICOS (multidimensional) linear constraint to a collection of Gurobi (scalar) linear constraints.""" self._gurobiQuadConstraint = dict() """Maps a PICOS quadratic constraint to a Gurobi quadr. constraint.""" self._gurobiSOCC = dict() """Maps a PICOS second order cone constraint to its Gurobi representation involving auxiliary variables and constraints.""" self._gurobiRSOCC = dict() """Maps a PICOS rotated second order cone constraint to its Gurobi representation involving auxiliary variables and constraints."""
[docs] def reset_problem(self): """Implement :meth:`~.solver.Solver.reset_problem`.""" self.int = None self._gurobiVar.clear() self._gurobiLinearConstraints.clear() self._gurobiQuadConstraint.clear() self._gurobiSOCC.clear() self._gurobiRSOCC.clear()
def _import_variable(self, picosVar): import gurobipy as gurobi dim = picosVar.dim # Retrieve types. if isinstance(picosVar, CONTINUOUS_VARTYPES): gurobiVarType = gurobi.GRB.CONTINUOUS elif isinstance(picosVar, IntegerVariable): gurobiVarType = gurobi.GRB.INTEGER elif isinstance(picosVar, BinaryVariable): gurobiVarType = gurobi.GRB.BINARY else: assert False, "Unexpected variable type." # Retrieve bounds. lowerBounds = [-gurobi.GRB.INFINITY]*dim upperBounds = [gurobi.GRB.INFINITY]*dim lower, upper = picosVar.bound_dicts for i, b in lower.items(): lowerBounds[i] = b for i, b in upper.items(): upperBounds[i] = b # Import variable. # Note that Gurobi allows importing the objective function coefficients # for the new variables here, but that is done later to streamline # updates to the objective. gurobiVars = self.int.addVars(dim, lb=lowerBounds, ub=upperBounds, vtype=gurobiVarType, name=picosVar.name) # Map PICOS variable indices to Gurobi variables. for localIndex in range(dim): self._gurobiVar[picosVar.id_at(localIndex)] = gurobiVars[localIndex] def _remove_variable(self, picosVar): gurobiVars = [self._gurobiVar.pop(picosVar.id_at(localIndex)) for localIndex in range(picosVar.dim)] self.int.remove(gurobiVars) def _import_variable_values(self): for picosVar in self.ext.variables.values(): if picosVar.valued: value = picosVar.internal_value for localIndex in range(picosVar.dim): gurobiVar = self._gurobiVar[picosVar.id_at(localIndex)] gurobiVar.Start = value[localIndex] def _reset_variable_values(self): import gurobipy as gurobi for gurobiVar in self._gurobiVar.values(): gurobiVar.Start = gurobi.GRB.UNDEFINED def _affinexp_pic2grb(self, picosExpression): import gurobipy as gurobi for gurobiVars, coefficients, constant in picosExpression.sparse_rows( None, indexFunction=lambda picosVar, localIndex: self._gurobiVar[picosVar.id_at(localIndex)]): gurobiExpression = gurobi.LinExpr(coefficients, gurobiVars) gurobiExpression.addConstant(constant) yield gurobiExpression def _scalar_affinexp_pic2grb(self, picosExpression): """Transform a scalar affine expression from PICOS to Gurobi. :returns: A :class:`LinExpr <gurobipy.LinExpr>`. """ assert len(picosExpression) == 1 return next(self._affinexp_pic2grb(picosExpression)) def _quadexp_pic2grb(self, picosExpression): import gurobipy as gurobi assert isinstance(picosExpression, QuadraticExpression) # Import affine part of expression. gurobiExpression = gurobi.QuadExpr( self._scalar_affinexp_pic2grb(picosExpression.aff)) # Import quadratic form. gurobiI, gurobiJ, gurobiV = [], [], [] for (picosVar1, picosVar2), picosCoefficients \ in picosExpression.quadratic_forms.items(): for sparseIndex in range(len(picosCoefficients)): localVar1Index = picosCoefficients.I[sparseIndex] localVar2Index = picosCoefficients.J[sparseIndex] localCoefficient = picosCoefficients.V[sparseIndex] gurobiI.append(self._gurobiVar[picosVar1.id_at(localVar1Index)]) gurobiJ.append(self._gurobiVar[picosVar2.id_at(localVar2Index)]) gurobiV.append(localCoefficient) gurobiExpression.addTerms(gurobiV, gurobiI, gurobiJ) return gurobiExpression def _import_linear_constraint(self, picosConstraint): import gurobipy as gurobi assert isinstance(picosConstraint, AffineConstraint) # Retrieve sense. if picosConstraint.is_increasing(): gurobiSense = gurobi.GRB.LESS_EQUAL elif picosConstraint.is_decreasing(): gurobiSense = gurobi.GRB.GREATER_EQUAL elif picosConstraint.is_equality(): gurobiSense = gurobi.GRB.EQUAL else: assert False, "Unexpected constraint relation." # Append scalar constraints. gurobiCons = [] for localConIndex, (gurobiLHS, gurobiRHS) in enumerate(zip( self._affinexp_pic2grb(picosConstraint.lhs), self._affinexp_pic2grb(picosConstraint.rhs))): if picosConstraint.name: gurobiName = "{}:{}".format(picosConstraint.name, localConIndex) else: gurobiName = "" gurobiCons.append(self.int.addConstr( gurobiLHS, gurobiSense, gurobiRHS, gurobiName)) return gurobiCons def _import_quad_constraint(self, picosConstraint): import gurobipy as gurobi assert isinstance(picosConstraint, ConvexQuadraticConstraint) gurobiLHS = self._quadexp_pic2grb(picosConstraint.le0) gurobiRHS = -gurobiLHS.getLinExpr().getConstant() if gurobiRHS: gurobiLHS.getLinExpr().addConstant(gurobiRHS) return self.int.addQConstr( gurobiLHS, gurobi.GRB.LESS_EQUAL, gurobiRHS) # TODO: Handle SOC → Quadratic via a reformulation. def _import_socone_constraint(self, picosConstraint): import gurobipy as gurobi assert isinstance(picosConstraint, SOCConstraint) picosLHS = picosConstraint.ne picosRHS = picosConstraint.ub picosLHSLen = len(picosLHS) # Add auxiliary variables: One for every dimension of the left hand side # of the PICOS constraint and one for its right hand side. gurobiLHSVarsIndexed = self.int.addVars( picosLHSLen, lb=-gurobi.GRB.INFINITY, ub=gurobi.GRB.INFINITY) gurobiLHSVars = gurobiLHSVarsIndexed.values() gurobiRHSVar = self.int.addVar(lb=0.0, ub=gurobi.GRB.INFINITY) # Add constraints that identify the left hand side Gurobi auxiliary # variables with their slice of the PICOS left hand side expression. gurobiLHSSlices = dict() for dimension, slice in enumerate(self._affinexp_pic2grb(picosLHS)): gurobiLHSSlices[dimension] = slice gurobiLHSCons = self.int.addConstrs( (gurobiLHSSlices[dimension] - gurobiLHSVarsIndexed[dimension] == 0 for dimension in range(picosLHSLen))).values() # Add a constraint that identifies the right hand side Gurobi auxiliary # variable with the PICOS right hand side scalar expression. gurobiRHSExp = self._scalar_affinexp_pic2grb(picosRHS) gurobiRHSCon = self.int.addConstr( gurobiRHSVar - gurobiRHSExp, gurobi.GRB.EQUAL, 0) # Add a quadratic constraint over the auxiliary variables that # represents the PICOS second order cone constraint itself. quadExpr = gurobi.QuadExpr() quadExpr.addTerms([1.0] * picosLHSLen, gurobiLHSVars, gurobiLHSVars) gurobiName = picosConstraint.name if picosConstraint.name else "" gurobiQuadCon = self.int.addQConstr(quadExpr, gurobi.GRB.LESS_EQUAL, gurobiRHSVar * gurobiRHSVar, gurobiName) gurobiMetaCon = self.GurobiSOCC( LHSVars=gurobiLHSVars, RHSVar=gurobiRHSVar, LHSCons=gurobiLHSCons, RHSCon=gurobiRHSCon, quadCon=gurobiQuadCon) return gurobiMetaCon # TODO: Handle RSOC → Quadratic via a reformulation. def _import_rscone_constraint(self, picosConstraint): import gurobipy as gurobi assert isinstance(picosConstraint, RSOCConstraint) picosLHS = picosConstraint.ne picosRHS1 = picosConstraint.ub1 picosRHS2 = picosConstraint.ub2 picosLHSLen = len(picosLHS) # Add auxiliary variables: One for every dimension of the left hand side # of the PICOS constraint and one for its right hand side. gurobiLHSVarsIndexed = self.int.addVars( picosLHSLen, lb=-gurobi.GRB.INFINITY, ub=gurobi.GRB.INFINITY) gurobiLHSVars = gurobiLHSVarsIndexed.values() gurobiRHSVars = self.int.addVars( 2, lb=0.0, ub=gurobi.GRB.INFINITY).values() # Add constraints that identify the left hand side Gurobi auxiliary # variables with their slice of the PICOS left hand side expression. gurobiLHSSlices = dict() for dimension, slice in enumerate(self._affinexp_pic2grb(picosLHS)): gurobiLHSSlices[dimension] = slice gurobiLHSCons = self.int.addConstrs( (gurobiLHSSlices[dimension] - gurobiLHSVarsIndexed[dimension] == 0 for dimension in range(picosLHSLen))).values() # Add two constraints that identify the right hand side Gurobi auxiliary # variables with the PICOS right hand side scalar expressions. gurobiRHSExps = \ self._scalar_affinexp_pic2grb(picosRHS1), \ self._scalar_affinexp_pic2grb(picosRHS2) gurobiRHSCons = self.int.addConstrs( (gurobiRHSVars[i] - gurobiRHSExps[i] == 0 for i in (0, 1))).values() # Add a quadratic constraint over the auxiliary variables that # represents the PICOS second order cone constraint itself. quadExpr = gurobi.QuadExpr() quadExpr.addTerms([1.0] * picosLHSLen, gurobiLHSVars, gurobiLHSVars) gurobiName = picosConstraint.name if picosConstraint.name else "" gurobiQuadCon = self.int.addQConstr(quadExpr, gurobi.GRB.LESS_EQUAL, gurobiRHSVars[0] * gurobiRHSVars[1], gurobiName) gurobiMetaCon = self.GurobiRSOCC( LHSVars=gurobiLHSVars, RHSVars=gurobiRHSVars, LHSCons=gurobiLHSCons, RHSCons=gurobiRHSCons, quadCon=gurobiQuadCon) return gurobiMetaCon def _import_constraint(self, picosConstraint): # Import constraint based on type. if isinstance(picosConstraint, AffineConstraint): self._gurobiLinearConstraints[picosConstraint] = \ self._import_linear_constraint(picosConstraint) elif isinstance(picosConstraint, ConvexQuadraticConstraint): self._gurobiQuadConstraint[picosConstraint] = \ self._import_quad_constraint(picosConstraint) elif isinstance(picosConstraint, SOCConstraint): self._gurobiSOCC[picosConstraint] = \ self._import_socone_constraint(picosConstraint) elif isinstance(picosConstraint, RSOCConstraint): self._gurobiRSOCC[picosConstraint] = \ self._import_rscone_constraint(picosConstraint) else: assert False, "Constraint type belongs to unsupported problem type." def _remove_constraint(self, picosConstraint): if isinstance(picosConstraint, AffineConstraint): self.int.remove( self._gurobiLinearConstraints.pop(picosConstraint)) elif isinstance(picosConstraint, ConvexQuadraticConstraint): self.int.remove( self._gurobiQuadConstraint.pop(picosConstraint)) elif isinstance(picosConstraint, SOCConstraint): c = self._gurobiSOCC.pop(picosConstraint) self.int.remove(c.gurobiLHSCons + [c.gurobiRHSCon] + [c.gurobiQuadCon] + c.gurobiLHSVars + [c.gurobiRHSVar]) elif isinstance(picosConstraint, RSOCConstraint): c = self._gurobiRSOCC.pop(picosConstraint) self.int.remove(c.gurobiLHSCons + c.gurobiRHSCons + [c.gurobiQuadCon] + c.gurobiLHSVars + c.gurobiRHSVars) else: assert False, "Constraint type belongs to unsupported problem type." def _import_objective(self): import gurobipy as gurobi picosSense, picosObjective = self.ext.no # Retrieve objective sense. if picosSense == "min": gurobiSense = gurobi.GRB.MINIMIZE else: assert picosSense == "max" gurobiSense = gurobi.GRB.MAXIMIZE # Retrieve objective function. if isinstance(picosObjective, AffineExpression): gurobiObjective = self._scalar_affinexp_pic2grb(picosObjective) else: assert isinstance(picosObjective, QuadraticExpression) gurobiObjective = self._quadexp_pic2grb(picosObjective) self.int.setObjective(gurobiObjective, gurobiSense) def _import_problem(self): import gurobipy as gurobi # Create a problem instance. if self.ext.options.license_warnings: self.int = gurobi.Model() else: with self._enforced_verbosity(): self.int = gurobi.Model() # Import variables. for variable in self.ext.variables.values(): self._import_variable(variable) # Import constraints. for constraint in self.ext.constraints.values(): self._import_constraint(constraint) # Set objective. self._import_objective() def _update_problem(self): for oldConstraint in self._removed_constraints(): self._remove_constraint(oldConstraint) for oldVariable in self._removed_variables(): self._remove_variable(oldVariable) for newVariable in self._new_variables(): self._import_variable(newVariable) for newConstraint in self._new_constraints(): self._import_constraint(newConstraint) if self._objective_has_changed(): self._import_objective() def _solve(self): import gurobipy as gurobi # Reset options. # NOTE: OutputFlag = 0 prevents resetParams from printing to console. self.int.Params.OutputFlag = 0 self.int.resetParams() # verbosity self.int.Params.OutputFlag = 1 if self.verbosity() > 0 else 0 # abs_prim_fsb_tol if self.ext.options.abs_prim_fsb_tol is not None: self.int.Params.FeasibilityTol = self.ext.options.abs_prim_fsb_tol # abs_dual_fsb_tol if self.ext.options.abs_dual_fsb_tol is not None: self.int.Params.OptimalityTol = self.ext.options.abs_dual_fsb_tol # rel_ipm_opt_tol if self.ext.options.rel_ipm_opt_tol is not None: self.int.Params.BarConvTol = self.ext.options.rel_ipm_opt_tol # HACK: Work around low precision (conic) quadratic duals. self.int.Params.BarQCPConvTol = \ 0.01 * self.ext.options.rel_ipm_opt_tol # abs_bnb_opt_tol if self.ext.options.abs_bnb_opt_tol is not None: self.int.Params.MIPGapAbs = self.ext.options.abs_bnb_opt_tol # rel_bnb_opt_tol if self.ext.options.rel_bnb_opt_tol is not None: self.int.Params.MIPGap = self.ext.options.rel_bnb_opt_tol # integrality_tol if self.ext.options.integrality_tol is not None: self.int.Params.IntFeasTol = self.ext.options.integrality_tol # markowitz_tol if self.ext.options.markowitz_tol is not None: self.int.Params.MarkowitzTol = self.ext.options.markowitz_tol # max_iterations if self.ext.options.max_iterations is not None: self.int.Params.BarIterLimit = self.ext.options.max_iterations self.int.Params.IterationLimit = self.ext.options.max_iterations _lpm = {"interior": 2, "psimplex": 0, "dsimplex": 1} # lp_node_method if self.ext.options.lp_node_method is not None: value = self.ext.options.lp_node_method assert value in _lpm, "Unexpected lp_node_method value." self.int.Params.SiftMethod = _lpm[value] # lp_root_method if self.ext.options.lp_root_method is not None: value = self.ext.options.lp_root_method assert value in _lpm, "Unexpected lp_root_method value." self.int.Params.Method = _lpm[value] # timelimit if self.ext.options.timelimit is not None: self.int.Params.TimeLimit = self.ext.options.timelimit # max_fsb_nodes if self.ext.options.max_fsb_nodes is not None: self.int.Params.SolutionLimit = self.ext.options.max_fsb_nodes # hotstart if self.ext.options.hotstart: self._import_variable_values() else: self._reset_variable_values() # Handle Gurobi-specific options. for option, value in self.ext.options.gurobi_params.items(): if not self.int.getParamInfo(option): self._handle_bad_option_value("gurobi_params", "Gurobi option '{}' does not exist.".format(option)) try: self.int.setParam(option, value) except TypeError as error: self._handle_bad_option_value("gurobi_params", "Invalid value '{}' for Gurobi option '{}': {}" .format(value, option, str(error))) # Handle unsupported options. self._handle_unsupported_option("treememory") # Compute duals also for QPs and QCPs. if self.ext.options.duals is not False and self.ext.is_continuous(): self.int.setParam(gurobi.GRB.Param.QCPDual, 1) # Attempt to solve the problem. with self._header(), self._stopwatch(): try: self.int.optimize() except gurobi.GurobiError as error: if error.errno == gurobi.GRB.Error.Q_NOT_PSD: self._handle_continuous_nonconvex_error(error) else: raise # Retrieve primals. primals = {} if self.ext.options.primals is not False: for picosVar in self.ext.variables.values(): try: value = [] for localIndex in range(picosVar.dim): gurobiVar = self._gurobiVar[picosVar.id_at(localIndex)] scalarValue = gurobiVar.getAttr(gurobi.GRB.Attr.X) value.append(scalarValue) except AttributeError: primals[picosVar] = None else: primals[picosVar] = value # Retrieve duals. duals = {} if self.ext.options.duals is not False and self.ext.is_continuous(): for picosCon in self.ext.constraints.values(): try: if isinstance(picosCon, AffineConstraint): gurobiCons = self._gurobiLinearConstraints[picosCon] gurobiDuals = [] for gurobiCon in gurobiCons: gurobiDuals.append( gurobiCon.getAttr(gurobi.GRB.Attr.Pi)) picosDual = cvxopt.matrix(gurobiDuals, picosCon.size) # Flip sign based on constraint relation. if picosCon.is_decreasing(): picosDual = -picosDual elif isinstance(picosCon, SOCConstraint): gurobiMetaCon = self._gurobiSOCC[picosCon] lb = gurobiMetaCon.RHSCon.getAttr(gurobi.GRB.Attr.Pi) z = [-constraint.getAttr(gurobi.GRB.Attr.Pi) for constraint in gurobiMetaCon.LHSCons] picosDual = cvxopt.matrix([lb] + z) elif isinstance(picosCon, RSOCConstraint): gurobiMetaCon = self._gurobiRSOCC[picosCon] ab = [constraint.getAttr(gurobi.GRB.Attr.Pi) for constraint in gurobiMetaCon.RHSCons] z = [-constraint.getAttr(gurobi.GRB.Attr.Pi) for constraint in gurobiMetaCon.LHSCons] picosDual = cvxopt.matrix(ab + z) elif isinstance(picosCon, ConvexQuadraticConstraint): picosDual = None else: assert False, "Unexpected constraint type." # Flip sign based on objective sense. if picosDual and self.ext.no.direction == "min": picosDual = -picosDual except AttributeError: duals[picosCon] = None else: duals[picosCon] = picosDual # # Retrieve objective value. # try: # objectiveValue = self.int.getAttr(gurobi.GRB.Attr.ObjVal) # except AttributeError: # objectiveValue = None statusCode = self.int.getAttr(gurobi.GRB.Attr.Status) if statusCode == gurobi.GRB.Status.LOADED: raise RuntimeError("Gurobi claims to have just loaded the problem " "while PICOS expects the solution search to have terminated.") elif statusCode == gurobi.GRB.Status.OPTIMAL: primalStatus = SS_OPTIMAL dualStatus = SS_OPTIMAL problemStatus = PS_FEASIBLE elif statusCode == gurobi.GRB.Status.INFEASIBLE: primalStatus = SS_INFEASIBLE dualStatus = SS_UNKNOWN problemStatus = PS_INFEASIBLE elif statusCode == gurobi.GRB.Status.INF_OR_UNBD: primalStatus = SS_UNKNOWN dualStatus = SS_UNKNOWN problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.UNBOUNDED: primalStatus = SS_UNKNOWN dualStatus = SS_INFEASIBLE problemStatus = PS_UNBOUNDED elif statusCode == gurobi.GRB.Status.CUTOFF: # "Optimal objective for model was proven to be worse than the value # specified in the Cutoff parameter. No solution information is # available." primalStatus = SS_PREMATURE dualStatus = SS_PREMATURE problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.ITERATION_LIMIT: primalStatus = SS_PREMATURE dualStatus = SS_PREMATURE problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.NODE_LIMIT: primalStatus = SS_PREMATURE dualStatus = SS_EMPTY # Applies only to mixed integer problems. problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.TIME_LIMIT: primalStatus = SS_PREMATURE dualStatus = SS_PREMATURE problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.SOLUTION_LIMIT: primalStatus = SS_PREMATURE dualStatus = SS_PREMATURE problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.INTERRUPTED: primalStatus = SS_PREMATURE dualStatus = SS_PREMATURE problemStatus = PS_UNKNOWN elif statusCode == gurobi.GRB.Status.NUMERIC: primalStatus = SS_UNKNOWN dualStatus = SS_UNKNOWN problemStatus = PS_UNSTABLE elif statusCode == gurobi.GRB.Status.SUBOPTIMAL: # "Unable to satisfy optimality tolerances; a sub-optimal solution # is available." primalStatus = SS_FEASIBLE dualStatus = SS_FEASIBLE problemStatus = PS_FEASIBLE elif statusCode == gurobi.GRB.Status.INPROGRESS: raise RuntimeError("Gurobi claims solution search to be 'in " "progress' while PICOS expects it to have terminated.") elif statusCode == gurobi.GRB.Status.USER_OBJ_LIMIT: # "User specified an objective limit (a bound on either the best # objective or the best bound), and that limit has been reached." primalStatus = SS_FEASIBLE dualStatus = SS_EMPTY # Applies only to mixed integer problems. problemStatus = PS_FEASIBLE else: primalStatus = SS_UNKNOWN dualStatus = SS_UNKNOWN problemStatus = PS_UNKNOWN return self._make_solution( primals, duals, primalStatus, dualStatus, problemStatus)
# -------------------------------------- __all__ = api_end(_API_START, globals())