Translate a mathematical model into Python code: decision variables, objective, and constraints.
Build indexed models with addVars, addConstrs, and quicksum.
Check the optimization status and solution availability before reading a solution.
3.2 What Is Gurobi?
An optimization model describes a problem. To solve it, one should always try to use an optimization solver first.
The Gurobi Optimizer is a commercial solver for linear programming (LP), mixed-integer programming (MIP), quadratic programming (QP), nonlinear problems, multi-objective problems, and so on.
Gurobi provides APIs for Python, C, C++, Java, MATLAB, and R. This chapter introduces how to use the Python API, which is called gurobipy. The latest version is Gurobi 13.0.3 (Sep 2026), and it supports Python 3.10 to 3.14.
3.3 Installation
Install gurobipy with pip:
python-m pip install gurobipy
In Google Colab, run %pip install gurobipy in a code cell instead.
To check the installation, print the version of Gurobi:
import gurobipy as gpprint(gp.gurobi.version())
(13, 0, 3)
The pip package comes with a size-limited license, which allows models with up to 2000 variables and 2000 linear constraints (200 variables if the model has quadratic terms). This is enough for every example in this chapter. When the first model is created, Gurobi reports the license it uses:
Restricted license - for non-production use only - expires 2027-11-29
TipAcademic Licenses
Students and faculty can get a free academic license without the size limit from the Gurobi Academic Program.
An Academic Named-User license is activated with the grbgetkey command, which must be run on the university network. It is valid for up to one year. The pip package does not include grbgetkey, so install the full Gurobi Optimizer or the license tools first.
An Academic WLS license works on any computer with an internet connection, including cloud platforms such as Google Colab.
TipUseful Links
To learn more about Gurobi, visit the following links:
Here we follow the official documentation (https://docs.gurobi.com/current/). Most of the code is taken from the official Gurobi examples, and all outputs were produced with Gurobi 13.0.3.
\begin{aligned}
\max \quad & x + y + 2z \\
\text{s.t.} \quad & x + 2y + 3z \leq 4 \\
& x + y \geq 1 \\
& x, y, z \in \{0, 1\}
\end{aligned}
\tag{3.1}
A gurobipy program follows the structure of the mathematical model. We build it step by step, and then show the complete program. Figure 3.1 illustrates the structure of the program.
%%{init: {"flowchart": {"curve": "linear", "nodeSpacing": 25, "rankSpacing": 30}, "themeVariables": {"fontSize": "16px"}, "themeCSS": ".nodeLabel code { font-family: monospace; font-size: 14px; padding: 0; background: transparent; color: inherit; white-space: pre; }"}}%%
flowchart LR
accTitle: The structure of a gurobipy program
accDescr: Import the API and create a model; add variables, an objective, and constraints; optimize, check the status, and read the optimal solution.
subgraph Setup["1. Initialize"]
direction TB
Import["Import the API<br><code>import gurobipy as gp</code><br><code>from gurobipy import GRB</code>"]
CreateModel["Create a model<br><code>m = gp.Model()</code>"]
Import --> CreateModel
end
subgraph Build["2. Build the model"]
direction TB
AddVars["Add decision variables<br><code>m.addVar()</code>"]
SetObjective["Set the objective<br><code>m.setObjective()</code>"]
AddConstrs["Add constraints<br><code>m.addConstr()</code>"]
AddVars --> SetObjective --> AddConstrs
end
subgraph Solve["3. Solve and inspect"]
direction TB
Optimize["Optimize the model<br><code>m.optimize()</code>"]
Check["Check the status<br><code>m.Status</code>"]
Report["Read the solution<br><code>v.X</code>, <code>m.ObjVal</code>"]
Optimize --> Check
Check -->|Optimal| Report
end
Setup --> Build --> Solve
classDef setup fill:#f1f5f9,stroke:#64748b,color:#1e293b
classDef model fill:#eaf2f8,stroke:#2c5f8a,color:#173b57
classDef solve fill:#edf6f0,stroke:#3f7555,color:#234631
classDef check fill:#fff6df,stroke:#a97820,color:#674910
class Import,CreateModel setup
class AddVars,SetObjective,AddConstrs model
class Optimize,Report solve
class Check check
style Setup fill:#fbfcfe,stroke:#cbd5e1,color:#1e293b
style Build fill:#f7fafd,stroke:#a8bfd2,color:#173b57
style Solve fill:#f7fbf8,stroke:#b3cdbd,color:#234631
Figure 3.1: The structure of a gurobipy program.
3.4.1 Importing gurobipy
At the beginning of the script, we import the gurobipy module with the alias gp, so that we can access its classes and functions using the prefix gp.. Then, we import the class GRB, which will be used to specify variable types and the sense of the objective.
import gurobipy as gpfrom gurobipy import GRB
3.4.2 Creating a Model
A Model object consists of a set of decision variables, a set of constraints, and an objective function. In the next sections, we will add variables, constraints, and an objective to the model.
We set the name of the model to "mip1". Note that names that contain spaces or special characters are not recommended.
m = gp.Model("mip1")
The Model(name='', env=None) constructor also has an optional argument env. Here, to keep it simple, we do not need to specify it.
3.4.3 Adding Variables
Model.addVar adds one decision variable to the model. The argument vtype is the variable type, such as GRB.CONTINUOUS, GRB.BINARY, or GRB.INTEGER. The name identifies the variable in model files and in outputs such as v.VarName.
The following code adds three binary variables x, y, and z to the model:
x = m.addVar(vtype=GRB.BINARY, name="x")y = m.addVar(vtype=GRB.BINARY, name="y")z = m.addVar(vtype=GRB.BINARY, name="z")
The signature of addVar is addVar(lb=0.0, ub=float('inf'), obj=0.0, vtype=GRB.CONTINUOUS, name='', column=None). Without arguments, a variable is continuous with a lower bound of 0 and an upper bound of inf.
Another way to add variables is Model.addVars, which adds multiple variables at once. We will illustrate it later.
After the variables are added, the objective function and constraints can be added to the model using these variables.
3.4.4 Setting the Objective
The following code sets the objective to x + y + 2z and maximizes it:
m.setObjective(x + y +2* z, GRB.MAXIMIZE)
m.setObjective(expr, sense=None) sets the objective to the linear or quadratic expression expr. The optional sense argument specifies GRB.MAXIMIZE or GRB.MINIMIZE. If sense is omitted, the existing model sense is retained; a new model minimizes by default.
3.4.5 Adding Constraints
Model.addConstr adds one constraint, written with one of the comparison operators <=, >=, or ==. The second argument is the name of the constraint.
m.addConstr(x +2* y +3* z <=4, "c0")m.addConstr(x + y >=1, "c1")
The signature is addConstr(constr, name='').
Warning
A constraint must have a single comparison operator. A constraint such as 0 <= x <= 1 is not allowed.
3.4.6 Optimizing and Reporting Results
So far, we have built the model. The next step is to solve it with Model.optimize(). After the solver finishes, we can retrieve the solution and the objective value, using Model.getVars() and Model.ObjVal. The following code prints the name and value of each variable, and the objective value:
m.optimize()for v in m.getVars():print(f"{v.VarName}{v.X:g}")print(f"Obj: {m.ObjVal:g}")
The results are then available as attributes: X is the value of a variable in the solution, VarName is its name, and ObjVal is the objective value.
3.4.7 The Complete Program
The official mip1.py puts these steps inside a try statement. gurobipy reports errors, such as a license problem, with the exception gp.GurobiError, and it raises an AttributeError when an attribute such as X or ObjVal cannot be retrieved.
# Copyright 2026, Gurobi Optimization, LLC# This example formulates and solves the following simple MIP model:# maximize# x + y + 2 z# subject to# x + 2 y + 3 z <= 4# x + y >= 1# x, y, z binaryimport gurobipy as gpfrom gurobipy import GRBtry:# Create a new model m = gp.Model("mip1")# Create variables x = m.addVar(vtype=GRB.BINARY, name="x") y = m.addVar(vtype=GRB.BINARY, name="y") z = m.addVar(vtype=GRB.BINARY, name="z")# Set objective m.setObjective(x + y +2* z, GRB.MAXIMIZE)# Add constraint: x + 2 y + 3 z <= 4 m.addConstr(x +2* y +3* z <=4, "c0")# Add constraint: x + y >= 1 m.addConstr(x + y >=1, "c1")# Optimize model m.optimize()for v in m.getVars():print(f"{v.VarName}{v.X:g}")print(f"Obj: {m.ObjVal:g}")except gp.GurobiError as e:print(f"Error code {e.errno}: {e}")exceptAttributeError:print("Encountered an attribute error")
Running the program prints the solver log followed by the solution:
Restricted license - for non-production use only - expires 2027-11-29
Gurobi Optimizer version 13.0.3 build v13.0.3rc0 (mac64[arm] - Darwin 27.0.0 26A428)
CPU model: Apple M2 Max
Thread count: 12 physical cores, 12 logical processors, using up to 12 threads
Optimize a model with 2 rows, 3 columns and 5 nonzeros (Max)
Model fingerprint: 0x98886187
Model has 3 linear objective coefficients
Variable types: 0 continuous, 3 integer (3 binary)
Coefficient statistics:
Matrix range [1e+00, 3e+00]
Objective range [1e+00, 2e+00]
Bounds range [1e+00, 1e+00]
RHS range [1e+00, 4e+00]
Found heuristic solution: objective 2.0000000
Presolve removed 2 rows and 3 columns
Presolve time: 0.00s
Presolve: All rows and columns removed
Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)
Thread count was 1 (of 12 available processors)
Solution count 2: 3 2
Optimal solution found (tolerance 1.00e-04)
Best objective 3.000000000000e+00, best bound 3.000000000000e+00, gap 0.0000%
x 1
y 0
z 1
Obj: 3
The optimal solution is (x, y, z) = (1, 0, 1) with objective value 3.
TipChecking a Model with an LP File
Model.write saves the model in a human-readable format when the file name ends with .lp:
m.write("mip1.lp")
The file mip1.lp contains:
\ Model mip1
\ LP format - for model browsing. Use MPS format to capture full model detail.
Maximize
x + y + 2 z
Subject To
c0: x + 2 y + 3 z <= 4
c1: x + y >= 1
Bounds
Binaries
x y z
End
Comparing the LP file with the mathematical model line by line is the quickest way to find a typo in a constraint. The official examples workforce1.py and facility.py also write LP files.
3.5 The Matrix API
gurobipy also offers a matrix-oriented interface that works with NumPy arrays and SciPy sparse matrices. In matrix form, Equation 3.1 reads
The constraint x + y \geq 1 is multiplied by -1 to obtain -x - y \leq -1, so the model has only \leq constraints. The matrix interface is convenient when the data are already stored in arrays.
The official example matrix1.py solves this model:
# Copyright 2026, Gurobi Optimization, LLC# This example formulates and solves the following simple MIP model# using the matrix API:# maximize# x + y + 2 z# subject to# x + 2 y + 3 z <= 4# x + y >= 1# x, y, z binaryimport gurobipy as gpfrom gurobipy import GRBimport numpy as npimport scipy.sparse as sptry:# Create a new model m = gp.Model("matrix1")# Create variables x = m.addMVar(shape=3, vtype=GRB.BINARY, name="x")# Set objective obj = np.array([1.0, 1.0, 2.0]) m.setObjective(obj @ x, GRB.MAXIMIZE)# Build (sparse) constraint matrix val = np.array([1.0, 2.0, 3.0, -1.0, -1.0]) row = np.array([0, 0, 0, 1, 1]) col = np.array([0, 1, 2, 0, 1]) A = sp.csr_matrix((val, (row, col)), shape=(2, 3))# Build rhs vector rhs = np.array([4.0, -1.0])# Add constraints m.addConstr(A @ x <= rhs, name="c")# Optimize model m.optimize()print(x.X)print(f"Obj: {m.ObjVal:g}")except gp.GurobiError as e:print(f"Error code {e.errno}: {e}")exceptAttributeError:print("Encountered an attribute error")
After the solver log, the program prints:
[1. 0. 1.]
Obj: 3
First, m.addMVar(shape=3, vtype=GRB.BINARY, name="x") creates a vector of three binary variables. The variables are named x[0], x[1], and x[2].
Then, obj is a NumPy array that holds the coefficients and corresponds to \mathbf{c} in the mathematical model. The objective is set with m.setObjective(obj @ x, GRB.MAXIMIZE), where obj @ x is the linear expression \mathbf{c}^{\top} \mathbf{x}.
We construct a sparse matrix using scipy.sparse.csr_matrix. The array val stores the nonzero values of \mathbf{A}, while row and col store their corresponding row and column indices, starting from zero. Each row in Table 3.1 represents one nonzero entry.
Table 3.1: The nonzero entries of \mathbf{A} stored in val, row, and col.
Only nonzero entries are stored explicitly. For example, a_{23} = 0 is omitted from the arrays. The call sp.csr_matrix((val, (row, col)), shape=(2, 3)) reconstructs the matrix from these arrays, with shape=(2, 3) specifying two rows and three columns.
The right-hand side vector \mathbf{b} is stored in the NumPy array rhs. The constraints are added with m.addConstr(A @ x <= rhs, name="c"), which is equivalent to \mathbf{A} \mathbf{x} \leq \mathbf{b}.
The matrix interface is convenient when the data are already stored in arrays.
3.6 An Example: The Diet Problem
Practical models may have many variables and constraints. We use the diet problem to illustrate how gurobipy builds them with a few statements.
The official example diet.py solves a version of the diet problem, in which each nutrition category i has a lower bound l_i and an upper bound u_i:
Here, J is the set of foods, I is the set of nutrition categories, c_j is the cost of one serving of food j, a_{ij} is the amount of nutrient i in one serving of food j, and x_j is the number of servings of food j to buy. Table 3.2 shows the data.
In the diet example, categories is the list ["calories", "protein", "fat", "sodium"], and minNutrition["protein"] is 91. GRB.INFINITY stands for \infty.
The dictionary nutritionValues corresponds to the matrix \mathbf{A} in Equation 3.2. For example, nutritionValues["hamburger", "calories"] is 410, which is the number of calories in one serving of hamburger.
The lists categories and foods, the dictionaries minNutrition, maxNutrition, and cost, and the dictionary nutritionValues are used to build the model in the next section.
3.6.2 The Model
The model is built and solved as follows:
# Modelm = gp.Model("diet")# Create decision variables for the foods to buybuy = m.addVars(foods, name="buy")# The objective is to minimize the costsm.setObjective(buy.prod(cost), GRB.MINIMIZE)# Nutrition constraintsm.addConstrs( ( gp.quicksum( nutritionValues[f, c] * buy[f]for f in foods )== [minNutrition[c], maxNutrition[c]]for c in categories ),"_",)def printSolution():if m.status == GRB.OPTIMAL:print(f"\nCost: {m.ObjVal:g}")print("\nBuy:")for f in foods:if buy[f].X >0.0001:print(f"{f}{buy[f].X:g}")else:print("No optimal solution found")# Solvem.optimize()printSolution()
m.addVars(foods, name="buy") adds one variable for each element of foods. The variables are named buy[hamburger], buy[chicken], and so on. The equivalent code using addVar in a loop is:
buy = gp.tupledict()for f in foods: buy[f] = m.addVar(name=f"buy[{f}]")
In detail, m.addVars returns a tupledict, a dictionary of variables such as buy["milk"].
3.6.4 Setting the Objective
buy.prod(cost) is the sum of cost[f] * buy[f] over all keys, that is, \sum_{j \in J} c_j x_j.
The equivalent code is:
m.setObjective(sum(buy[f]*cost[f] for f in foods), GRB.MINIMIZE)
3.6.5 Adding Constraints with addConstrs
gp.quicksum works like Python’s sum, but it is much more efficient for building large expressions. The following code computes the sum of nutritionValues[f, c] * buy[f] over all foods f for a given category c:
gp.quicksum( nutritionValues[f, c] * buy[f] for f in foods)
This is \sum_{j \in J} a_{ij} x_j in Equation 3.2. Overall, m.addConstrs adds one constraint for each category c, and the constraints are range constraints, l_i \leq \sum_{j \in J} a_{ij} x_j \leq u_i.
The following code is equivalent:
for c in categories: m.addRange(sum(nutritionValues[f, c] * buy[f] for f in foods), minNutrition[c], maxNutrition[c], c)
3.6.6 Optimization Status
m.optimize() does not always end with an optimal solution: the model may be infeasible or unbounded, or the solver may stop at a time limit. The attribute Status tells what happened. The most common values are listed in Table 3.3.
Table 3.3: Common values of the Status attribute.
Status
Value
Meaning
GRB.OPTIMAL
2
An optimal solution was found.
GRB.INFEASIBLE
3
The model is infeasible.
GRB.INF_OR_UNBD
4
The model is infeasible or unbounded.
GRB.UNBOUNDED
5
The model is unbounded.
GRB.TIME_LIMIT
9
The time limit was reached.
Always check Status before reading a solution, as printSolution in diet.py does with if m.status == GRB.OPTIMAL. Attribute names are not case-sensitive, so m.status and m.Status are the same.
A nonoptimal status does not necessarily mean that no feasible solution was found. For example, after reaching a time limit, m.SolCount > 0 indicates that a solution is available, although optimality has not been established. The printSolution function above only prints a solution when the status is GRB.OPTIMAL.
When no solution is available, reading X usually raises an error:
AttributeError: Unable to retrieve attribute 'X'
3.7 Summary
A gurobipy program follows the structure of the mathematical model, as summarized in Table 3.4.
Table 3.4: The steps of a gurobipy program.
Step
gurobipy
Create a model
m = gp.Model("name")
Add decision variables
m.addVar(), m.addVars(), m.addMVar()
Set the objective
m.setObjective(expr, GRB.MINIMIZE)
Add constraints
m.addConstr(), m.addConstrs()
Optimize and read the results
m.optimize(), m.Status, v.X, m.ObjVal
Sums over index sets are built with gp.quicksum or with the tupledict methods sum and prod.
Check m.Status before reading a solution.
The Example Tour of the official documentation contains many more examples, and the Python API Reference describes every class and method.
3.8 Exercises
Exercise 3.1 (Solving mip1 by Enumeration) Verify the optimal solution and objective value of Equation 3.1 by enumerating all 2^3 = 8 binary assignments to (x, y, z). Check each assignment for feasibility and compare the objective values of the feasible assignments. Complete either of the following code templates.
Method 1: Nested for loops
best =Nonefor x in [0, 1]:for y in [0, 1]:for z in [0, 1]:# your code here
Method 2: itertools.product
from itertools import productbest =Nonefor x, y, z in product([0, 1], repeat=3):# your code here
Exercise 3.2 (The Diet Problem with Whole Servings) Consider the diet model introduced in this chapter. Suppose that all foods must be purchased in whole servings.
Modify the food quantity variables in diet.py to be nonnegative integers, and solve the model. Report the minimum cost and the number of servings of each food in an optimal diet.
Round each food quantity in the original LP solution to the nearest integer. Does the resulting diet satisfy all the nutritional bounds? If not, identify the violated constraints.
Exercise 3.3 (Understanding facility.py) The official example facility.py determines which of five plants to keep open and how much to ship from each plant to four warehouses, minimizing total fixed and transportation costs.
When reading the code, note that the obj argument of addVars sets the objective coefficients of the new variables, while m.ModelSense = GRB.MINIMIZE sets the optimization direction to minimization. The assignments to Start and m.Params.Method guide the solver without changing the mathematical model.
Write down the mathematical model implemented in facility.py, clearly defining the sets, parameters, decision variables, objective function, and constraints.
Run the program and report which plants are open in the optimal solution and the minimum total cost.
Compare this model with the uncapacitated facility location problem (UFLP) in the previous chapter. What are the main differences in the decision variables and constraints?
Exercise 3.4 (An Optimization Problem of Your Choice) Choose an optimization problem that interests you from a book or research paper, and solve an instance of it using Gurobi.
Cite the source, including the relevant chapter, section, or page numbers. Briefly describe the problem and explain why it interests you.
Write down the mathematical model, clearly defining the sets, parameters, decision variables, objective function, and constraints.
Implement the model in gurobipy and solve a numerical instance. You may use data from the source or construct a small instance of your own. Provide the code and data needed to reproduce your results.
Report the optimization status and, if a solution is available, its objective value and decision variable values. State whether optimality has been established, and interpret the results in the context of the problem.