Gurobi Cookbook - Basics (1/5)

This is a collection of short, idiomatic python code for working with the Gurobi solver using its python interface.

from __future__ import print_function
# get a sample mps file from the benchmark repository
import requests
import os
import toolz
benchmark_url = 'http://miplib.zib.de/download/'
sample_mps = 'cov1075.mps.gz' # "timtab1.mps.gz"
infile = requests.get(benchmark_url + sample_mps)
if not os.path.exists(sample_mps):
    with open(sample_mps, 'w') as outfile:
       outfile.write(infile.content)

Setting up Gurobi

If you will be using the python interface, the best way to install Gurobi and keep it up to date is to use the Anaconda python distribution. After this in installed, you need to import the gurobi library. We usually use the aliases grb for gurobipy, and GRB for the namespace where gurobi keeps its constants.

import gurobipy as grb
GRB = grb.GRB

Checking the gurobi version

under the gurobi library, there is a gurobi class which contains some utilities that aren't part of typical modeling modeling. The gurobi object has a version function which returns a tuple containing the major, minor, and patch level of the gurobi installation.

grb.gurobi.version()
(6, 5, 2)

Reading a saved model

The gurobi python module has a read function that will create an in-memory model from a model saved in an mps, lp, or several other formats. It can also read directly from several standard compressed formats, like, zip, gzip, and bzip.

m = grb.read(sample_mps)

commentary

In this case the variable m is a reference to the in-memory model equivalent to what was stored in the file. Gurobi uses the extension to determine the format of the file. mps files the best way to save a representation of the model. They can be used to compare performance of different solvers, or a single solver with different parameters. In this notebook, we are using a benchmark problem. While the mps file can store an exact copy of the model, it is difficult for a human to read. The lp format is more readable version of the model but it is lossy, so you may get different results reading from an lp file, especially if you have large or small coefficients in your model.

for cplex users

If you are used to the interactive cplex, this is the rough equivalent if read problem. The cplex interactive shell has an implicit model in its global namespace, and all actions operate on the global model. The model

Solving a model

A gurobi model object has an optimize function that will start the solve process.

m.optimize()
Optimize a model with 637 rows, 120 columns and 14280 nonzeros
Coefficient statistics:
  Matrix range    [1e+00, 1e+00]
  Objective range [1e+00, 1e+00]
  Bounds range    [1e+00, 1e+00]
  RHS range       [1e+00, 1e+01]
Found heuristic solution: objective 56
Presolve time: 0.01s
Presolved: 637 rows, 120 columns, 14280 nonzeros
Variable types: 0 continuous, 120 integer (120 binary)
Presolved: 120 rows, 757 columns, 14400 nonzeros


Root relaxation: objective 1.714286e+01, 414 iterations, 0.04 seconds

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

     0     0   17.14286    0  120   56.00000   17.14286  69.4%     -    0s
H    0     0                      35.0000000   17.14286  51.0%     -    0s
H    0     0                      27.0000000   17.14286  36.5%     -    0s
H    0     0                      25.0000000   17.14286  31.4%     -    0s
     0     0   17.25000    0  109   25.00000   17.25000  31.0%     -    0s
     0     0   17.28125    0  112   25.00000   17.28125  30.9%     -    0s
     0     0   17.28125    0  111   25.00000   17.28125  30.9%     -    0s
H    0     0                      24.0000000   17.28125  28.0%     -    0s
     0     0   17.29164    0  112   24.00000   17.29164  28.0%     -    0s
H    0     0                      23.0000000   17.29164  24.8%     -    0s
     0     0   17.29941    0  111   23.00000   17.29941  24.8%     -    0s
     0     0   17.30239    0  109   23.00000   17.30239  24.8%     -    0s
     0     0   17.30241    0  111   23.00000   17.30241  24.8%     -    0s
     0     0   17.30959    0  109   23.00000   17.30959  24.7%     -    0s
     0     0   17.31158    0  112   23.00000   17.31158  24.7%     -    0s
     0     0   17.31295    0  108   23.00000   17.31295  24.7%     -    0s
H    0     0                      21.0000000   17.31295  17.6%     -    0s
     0     0   17.31380    0  110   21.00000   17.31380  17.6%     -    0s
     0     0   17.31524    0  111   21.00000   17.31524  17.5%     -    0s
     0     0   17.31562    0  110   21.00000   17.31562  17.5%     -    0s
     0     0   17.31630    0  111   21.00000   17.31630  17.5%     -    0s
     0     0   17.31655    0  114   21.00000   17.31655  17.5%     -    0s
     0     0   17.31739    0  113   21.00000   17.31739  17.5%     -    0s
     0     0   17.31752    0  114   21.00000   17.31752  17.5%     -    0s
     0     0   17.31772    0  113   21.00000   17.31772  17.5%     -    0s
H    0     0                      20.0000000   17.31772  13.4%     -    0s
     0     0   17.31792    0  113   20.00000   17.31792  13.4%     -    0s
     0     0   17.31878    0  113   20.00000   17.31878  13.4%     -    0s
     0     0   17.31933    0  112   20.00000   17.31933  13.4%     -    1s
     0     0   17.31990    0  114   20.00000   17.31990  13.4%     -    1s
     0     0   17.32008    0  112   20.00000   17.32008  13.4%     -    1s
     0     0   17.32011    0  112   20.00000   17.32011  13.4%     -    1s
     0     0   17.32057    0  112   20.00000   17.32057  13.4%     -    1s
     0     0   17.32068    0  113   20.00000   17.32068  13.4%     -    1s
     0     0   17.32111    0  112   20.00000   17.32111  13.4%     -    1s
     0     2   17.32111    0  112   20.00000   17.32111  13.4%     -    1s

Cutting planes:
  Gomory: 5
  Zero half: 16

Explored 301 nodes (20763 simplex iterations) in 3.57 seconds
Thread count was 8 (of 8 available processors)

Optimal solution found (tolerance 1.00e-04)
Best objective 2.000000000000e+01, best bound 2.000000000000e+01, gap 0.0%

commentary

Gurobi will work solving the model until either

  • It finds a provably optimal solution (within tolerance)
  • It proves that the model in infeasible or unbounded
  • It proves no solution exists with an objective better than the cutoff target.
  • It hits a limit
    • Wall clock time
    • Number of solutions found (MIPs only)
    • Number of search nodes explored (MIPs only)
    • Number of Simplex Iterations (rarely used)
  • It receives an interrupt signal
  • It encounters an error
cplex users

This is equivalent to solve in interactive cplex

Checking if Gurobi found a solution

From the log, we can see that an optimal solution was found. In application code, it's important to explicitly check the model status after each solve. The cleaneast way to determine if Gurobi found a feasible solution is to query the SolCount attribute. If the solution count is at least 1, then Gurobi will have a feasible solution available. If not, querying for the values in the solution will through an exception.

print(m.SolCount, m.Status)
8 2

The Status attribute has good information as well, but it is not obvious from the status if there is a feasible solution available. In this case, an optimal solution. A status of optimal or suboptimal imply the existence of a solution and a status of loaded, infeasible, inf_or_unbd, unbounded, cutoff, or numeric definitely mean that no feasible solution was found. However, the statuses iteration_limit, node_limit, time_limit, solution_limit or interrupted refer to why the solve terminated. For these cases, all you know is that the solution, if any, isn't optimal according to the optimality criteria. That's why checking the solution count is recommended.

Its always a good idea to explitly check for feasibility in any production application, even if you reliably get feasible solutions from Gurobi. Unless you have complete control over the data you receive, inevitibly your application will receive data that results in an infeasible model. If you don't check for fesibility, your application will throw the unintuive exception.

GurobiError: Unable to retrieve attribute 'X'

Getting the objective value of a solution

If a solution does exist, you will usually be interested in the objective value. Most often, the objective value, as this is usually tied to some business metric. This can be done with the ObjVal attribute. Gurobi also computes a bound on the best possible objective in a feasible solution, which can be queried with the ObjBound attribute.

m.ObjVal, m.ObjBound
(20.0, 20.0)

Getting solution quality information

Gurobi works with floating point arithmetic, which has inevitible round-off errors. Even if you have a pure integer problem with all integer coeficients, Gurobi still uses floating point numbers to solve the LP subproblems. Gurobi can produce solutions that mathematically violate constraints in what are usually very small amounts. For each variable bound, and each constraint, there is potentially a small violation. By default, Gurobi allows variables and constraints to be violated by 0.000001, but for most problems, the violation is much smaller.

m.BoundVioSum, m.ConstrVioSum, m.BoundVio, m.ConstrVio
(0.0, 0.0, 0.0, 0.0)

Getting solution values

Of course, you will want to get the actual values of the solution. In any non-trivial model, there will be a set of decision variables. The complete list of deicision variables can be obtained using the function getVars. If the variables have all been given unique names, you can get an individual variable with the getVarByName function. Values can be obtained by querying the X attribute of a variable. The name of the variable.

dvars = m.getVars()
dvars[0].VarName, dvars[0].X
('x0', 1.0)