source: coopr.plugins/trunk/coopr/plugins/mip/gurobi_direct.py @ 3664

Last change on this file since 3664 was 3664, checked in by wehart, 9 years ago

Adding conditional import of pyutilib.logging

File size: 13.0 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2010 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11try:
12    import pyutilib.logging as logging
13except ImportError:
14    import logging
15
16from coopr.opt.base import *
17from coopr.opt.results import *
18from coopr.opt.solver import *
19
20import mockmip
21from pyutilib.misc import Options
22from pyutilib.component.core import alias
23from pyutilib.services import TempfileManager
24
25Logger = logging.getLogger('coopr.plugins')
26
27try:
28   # import all the glp_* functions
29   from gurobipy import *
30   gurobi_python_api_exists = True
31except ImportError:
32   gurobi_python_api_exists = False
33
34GRB_MAX = -1
35GRB_MIN = 1
36
37class gurobi_direct ( OptSolver ):
38   """The Gurobi optimization solver (direct API plugin)
39
40The gurobi_direct plugin offers an API interface to Gurobi.  It requires the
41Python Gurobi API interface (gurobipy) be in Coopr's lib/ directory.  Generally, if you can run Coopr's Python instance, and execute
42
43>>> import gurobipy
44>>>
45
46with no errors, then this plugin will be enabled.
47
48Because of the direct connection with the Gurobi, no temporary files need be
49written or read.  That ostensibly makes this a faster plugin than the file-based
50Gurobi plugin.  However, you will likely not notice any speed up unless you are
51using the GLPK solver with PySP problems (due to the rapid re-solves).
52
53One downside to the lack of temporary files, is that there is no LP file to
54inspect for clues while debugging a model.  For that, use the 'write' solver
55option:
56
57$ pyomo model.{py,dat} \
58  --solver=gurobi_direct \
59  --solver-options  write=/path/to/some/file.lp
60
61This is a direct interface to Gurobi's Model.write function, the extension of the file is important.  You could, for example, write the file in MPS format:
62
63$ pyomo model.{py,dat} \
64  --solver=gurobi_direct \
65  --solver-options  write=/path/to/some/file.mps
66
67   """
68
69   alias('gurobi_direct', doc='Direct Python interface to the Gurobi optimization solver.')
70
71   def __init__(self, **kwds):
72      #
73      # Call base class constructor
74      #
75      kwds['type'] = 'gurobi_direct'
76      OptSolver.__init__(self, **kwds)
77
78      self._model = None
79
80      # NOTE: eventually both of the following attributes should be migrated
81      # to a common base class.  Is the current solve warm-started?  A
82      # transient data member to communicate state information across the
83      # _presolve, _apply_solver, and _postsolve methods.
84      self.warm_start_solve = False
85
86      # Note: Undefined capabilities default to 'None'
87      self._capabilities = Options()
88      self._capabilities.linear = True
89      self._capabilities.quadratic = True
90      self._capabilities.integer = True
91      self._capabilities.sos1 = True
92      self._capabilities.sos2 = True
93
94
95   def _populate_gurobi_instance ( self, model ):
96      from coopr.pyomo.base import Var, VarStatus, Objective, Constraint, \
97                                   IntegerSet, BooleanSet
98      from coopr.pyomo.expr import is_constant
99
100      try:
101         grbmodel = Model( name=model.name )
102      except Exception, e:
103         msg = 'Unable to create Gurobi model.  Have you installed the Python'\
104         '\n       bindings for Gurobi?\n\n\tError message: %s'
105         raise Exception, msg % e
106
107      objective = sorted( model.active_components( Objective ).values() )[0]
108      sense = GRB_MAX
109      if objective.is_minimizing(): sense = GRB_MIN
110
111      constraint_list = model.active_components( Constraint )
112      variable_list   = model.active_components( Var )
113
114      grbmodel.ModelSense = sense
115
116      objvar_map = dict()
117      var_map    = dict()
118
119      for key in objective:
120         expression = objective[ key ].repn
121         if is_constant( expression ):
122            msg = "Ignoring objective '%s[%s]' which is constant"
123            Logger.warning( msg % (str(objective), str(key)) )
124            continue
125
126         if 1 in expression: # first-order terms
127            keys = expression[1].keys()
128            for var_key in keys:
129               index = var_key.keys()[0]
130               label = expression[-1][ index ].label
131               coef  = expression[ 1][ var_key ]
132               objvar_map[ label ] = coef
133               # the coefficients are attached to the model when creating the
134               # variables, below
135
136
137      # In matrix parlance, variables are columns
138      for name in variable_list.keys():
139         var_set = variable_list[ name ]
140         for ii in var_set.keys():
141            var = var_set[ ii ]
142            if (not var.active) or (var.status is VarStatus.unused) \
143               or (var.fixed is True):
144               continue
145
146            lb = -GRB.INFINITY
147            ub = GRB.INFINITY
148            if var.lb is not None:
149               lb = var.lb()
150            if var.ub is not None:
151               ub = var.ub()
152
153            obj_coef = 0
154            vlabel = var.label
155            if vlabel in objvar_map: obj_coef = objvar_map[ vlabel ]
156
157            # Be sure to impart the integer and binary nature of any variables
158            if isinstance(var.domain, IntegerSet):
159               var_type = GRB.INTEGER
160            elif isinstance(var.domain, BooleanSet):
161               var_type = GRB.BINARY
162            else:
163               var_type = GRB.CONTINUOUS
164
165            var_map[ vlabel ] = grbmodel.addVar(
166              lb=lb,
167              ub=ub,
168              obj=obj_coef,
169              vtype=var_type,
170              name=vlabel
171            )
172
173      grbmodel.update()  # "activate" variables, set up objective function
174
175      for name in constraint_list.keys():
176         constraint_set = constraint_list[ name ]
177         if constraint_set.trivial: continue
178
179         for ii in constraint_set.keys():
180            constraint = constraint_set[ ii ]
181            if not constraint.active: continue
182            elif constraint.lower is None and constraint.upper is None:
183               continue  # not binding at all, don't bother
184
185            expression = constraint.repn
186            if 1 in expression: # first-order terms
187               linear_coefs = list()
188               linear_vars = list()
189
190               keys = expression[1].keys()
191               for var_key in keys:
192                  index = var_key.keys()[0]
193                  var = expression[-1][ index ]
194                  coef  = expression[ 1][ var_key ]
195                  linear_coefs.append( coef )
196                  linear_vars.append( var_map[ var.label ] )
197
198               expr = LinExpr( coeffs=linear_coefs, vars=linear_vars )
199
200            clabel = constraint.label
201            #other_bound = float('inf')
202
203            offset = 0.0
204            if 0 in constraint.repn:
205               offset = constraint.repn[0][None]
206            bound = -offset
207
208            if constraint._equality:
209               sense = GRB.EQUAL    # Fixed
210               bound = constraint.lower() - offset
211               grbmodel.addConstr(
212                  lhs=expr, sense=sense, rhs=bound, name=clabel )
213            else:
214               sense = GRB.LESS_EQUAL
215               if constraint.upper is not None:
216                  bound = constraint.upper() - offset
217                  if bound < float('inf'):
218                     grbmodel.addConstr(
219                       lhs=expr,
220                       sense=sense,
221                       rhs=bound,
222                       name='%s_Upper' % clabel
223                     )
224
225               if constraint.lower is not None:
226                  bound = constraint.lower() - offset
227                  if bound > -float('inf'):
228                     grbmodel.addConstr(
229                       lhs=bound,
230                       sense=sense,
231                       rhs=expr,
232                       name=clabel
233                     )
234
235      grbmodel.update()
236
237      self._gurobi_instance = grbmodel
238
239
240   def warm_start_capable(self):
241      msg = "Gurobi has the ability to use warmstart solutions.  However, it "\
242            "has not yet been implemented into the Coopr gurobi_direct plugin."
243      Logger.info( msg )
244      return False
245
246
247   def warm_start(self, instance):
248      pass
249
250
251   def _presolve(self, *args, **kwargs):
252      from coopr.pyomo.base.PyomoModel import Model
253
254      self.warm_start_solve = kwargs.pop( 'warmstart', False )
255
256      model = args[0]
257      if len(args) != 1:
258         msg = "The gurobi_direct plugin method '_presolve' must be supplied "\
259               "a single problem instance - %s were supplied"
260         raise ValueError, msg % len(args)
261      elif not isinstance(model, Model):
262         raise ValueError, "The problem instance supplied to the "            \
263              "gurobi_direct plugin '_presolve' method must be of type 'Model'"
264
265      self._populate_gurobi_instance( model )
266      grbmodel = self._gurobi_instance
267
268      if 'write' in self.options:
269         fname = self.options.write
270         grbmodel.write( fname )
271
272      # Scaffolding in place
273      if self.warm_start_solve is True:
274
275         if len(args) != 1:
276            msg = "The gurobi_direct _presolve method can only handle a single"\
277                  "problem instance - %s were supplied"
278            raise ValueError, msg % len(args)
279
280         self.warm_start( model )
281
282
283   def _apply_solver(self):
284      # TODO apply appropriate user-specified parameters
285
286      prob = self._gurobi_instance
287      prob.setParam( 'OutputFlag', False )
288
289      # Actually solve the problem.
290      prob.optimize()
291
292
293   def _gurobi_get_solution_status ( self ):
294      status = self._gurobi_instance.Status
295      if   GRB.OPTIMAL         == status: return SolutionStatus.optimal
296      elif GRB.INFEASIBLE      == status: return SolutionStatus.infeasible
297      elif GRB.CUTOFF          == status: return SolutionStatus.other
298      elif GRB.INF_OR_UNBD     == status: return SolutionStatus.other
299      elif GRB.INTERRUPTED     == status: return SolutionStatus.other
300      elif GRB.LOADED          == status: return SolutionStatus.other
301      elif GRB.SUBOPTIMAL      == status: return SolutionStatus.other
302      elif GRB.UNBOUNDED       == status: return SolutionStatus.other
303      elif GRB.ITERATION_LIMIT == status: return SolutionStatus.stoppedByLimit
304      elif GRB.NODE_LIMIT      == status: return SolutionStatus.stoppedByLimit
305      elif GRB.SOLUTION_LIMIT  == status: return SolutionStatus.stoppedByLimit
306      elif GRB.TIME_LIMIT      == status: return SolutionStatus.stoppedByLimit
307      elif GRB.NUMERIC         == status: return SolutionStatus.error
308      raise RuntimeError, 'Unknown solution status returned by Gurobi solver'
309
310
311   def _postsolve(self):
312      gprob = self._gurobi_instance
313      pvars = gprob.getVars()
314      pcons = gprob.getConstrs()
315
316      results = SolverResults()
317      soln = Solution()
318      problem = results.problem
319      solver  = results.solver
320
321      solver.name = "Gurobi %s.%s%s" % gurobi.version()
322      # solver.memory_used =
323      # solver.user_time = None
324      # solver.system_time = None
325      solver.wallclock_time = gprob.Runtime
326      # solver.termination_condition = None
327      # solver.termination_message = None
328
329      problem.name = gprob.ModelName
330      problem.lower_bound = None
331      problem.upper_bound = None
332      problem.number_of_constraints          = gprob.NumConstrs
333      problem.number_of_nonzeros             = gprob.NumNZs
334      problem.number_of_variables            = gprob.NumVars
335      problem.number_of_binary_variables     = gprob.NumBinVars
336      problem.number_of_integer_variables    = gprob.NumIntVars
337      problem.number_of_continuous_variables = gprob.NumVars \
338                                              - gprob.NumIntVars \
339                                              - gprob.NumBinVars
340      problem.number_of_objectives = 1
341      problem.number_of_solutions = gprob.SolCount
342
343      problem.sense = ProblemSense.minimize
344      if problem.sense == GRB_MAX: problem.sense = ProblemSense.maximize
345
346      soln.status = self._gurobi_get_solution_status()
347
348      if soln.status in (SolutionStatus.optimal, SolutionStatus.stoppedByLimit):
349         obj_val = gprob.ObjVal
350         if problem.sense == ProblemSense.minimize:
351            problem.lower_bound = obj_val
352         else:
353            problem.upper_bound = obj_val
354
355         soln.objective['f'].value = obj_val
356
357         for var in pvars:
358            soln.variable[ var.VarName ] = var.X
359
360         # for con in pcons:
361                 # Having an issue correctly getting the constraints
362                 # so punting for now
363            # soln.constraint[ con.ConstrName ] = con.
364
365
366      results.solution.insert(soln)
367
368      self.results = results
369
370      # Done with the model object; free up some memory.
371      del gprob, self._gurobi_instance
372
373      # let the base class deal with returning results.
374      return OptSolver._postsolve(self)
375
376
377if not gurobi_python_api_exists:
378   SolverFactory().deactivate('gurobi_direct')
379   SolverFactory().deactivate('_mock_gurobi_direct')
Note: See TracBrowser for help on using the repository browser.