3  Optimization Modeling with Gurobi

3.1 Learning Objectives

  • Install gurobipy and check which license it uses.
  • 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 gp

print(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

3.4 A First Model

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.

Our first model is the example from the official tutorial Getting Started with the Gurobi Python API, which is also available as the example mip1.py:

\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 gp
from 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 binary

import gurobipy as gp
from gurobipy import GRB

try:
    # 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}")

except AttributeError:
    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

\begin{aligned} \max \quad & \mathbf{c}^{\top} \mathbf{x} \\ \text{s.t.} \quad & \mathbf{A} \mathbf{x} \leq \mathbf{b} \\ & \mathbf{x} \in \{0, 1\}^3 \end{aligned}

with \mathbf{x} = (x, y, z)^{\top} and

\mathbf{c} = \begin{bmatrix} 1 \\ 1 \\ 2 \end{bmatrix}, \quad \mathbf{A} = \begin{bmatrix} 1 & 2 & 3 \\ -1 & -1 & 0 \end{bmatrix}, \quad \mathbf{b} = \begin{bmatrix} 4 \\ -1 \end{bmatrix}.

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 binary

import gurobipy as gp
from gurobipy import GRB
import numpy as np
import scipy.sparse as sp

try:
    # 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}")

except AttributeError:
    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.
val row col
1.0 0 0
2.0 0 1
3.0 0 2
-1.0 1 0
-1.0 1 1

These entries correspond to the matrix

\mathbf{A} = \begin{bmatrix} 1 & 2 & 3 \\ -1 & -1 & 0 \end{bmatrix}.

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:

\begin{alignedat}{2} \min \quad & \sum_{j \in J} c_j x_j \\ \text{s.t.} \quad & \sum_{j \in J} a_{ij} x_j \geq l_i, &\qquad& i \in I \\ & \sum_{j \in J} a_{ij} x_j \leq u_i, && i \in I \\ & x_j \geq 0, && j \in J \end{alignedat} \tag{3.2}

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.

Table 3.2: The data of diet.py.
Food j Cost c_j Calories Protein Fat Sodium
hamburger 2.49 410 24 26 730
chicken 2.89 420 32 10 1190
hot dog 1.50 560 20 32 1800
fries 1.89 380 4 19 270
macaroni 2.09 320 12 10 930
pizza 1.99 320 15 12 820
salad 2.49 320 31 12 1230
milk 0.89 100 8 2.5 125
ice cream 1.59 330 8 10 180
Lower bound l_i 1800 91 0 0
Upper bound u_i 2200 \infty 65 1779

3.6.1 The Data

The program starts by defining the data:

# Copyright 2026, Gurobi Optimization, LLC

# Solve the classic diet model, showing how to add constraints
# to an existing model.

import gurobipy as gp
from gurobipy import GRB

# Nutrition guidelines, based on
# USDA Dietary Guidelines for Americans, 2005
# http://www.health.gov/DietaryGuidelines/dga2005/

categories, minNutrition, maxNutrition = gp.multidict(
    {
        "calories": [1800, 2200],
        "protein": [91, GRB.INFINITY],
        "fat": [0, 65],
        "sodium": [0, 1779],
    }
)

foods, cost = gp.multidict(
    {
        "hamburger": 2.49,
        "chicken": 2.89,
        "hot dog": 1.50,
        "fries": 1.89,
        "macaroni": 2.09,
        "pizza": 1.99,
        "salad": 2.49,
        "milk": 0.89,
        "ice cream": 1.59,
    }
)

# Nutrition values for the foods
nutritionValues = {
    ("hamburger", "calories"): 410,
    ("hamburger", "protein"): 24,
    ("hamburger", "fat"): 26,
    ("hamburger", "sodium"): 730,
    ("chicken", "calories"): 420,
    ("chicken", "protein"): 32,
    ("chicken", "fat"): 10,
    ("chicken", "sodium"): 1190,
    ("hot dog", "calories"): 560,
    ("hot dog", "protein"): 20,
    ("hot dog", "fat"): 32,
    ("hot dog", "sodium"): 1800,
    ("fries", "calories"): 380,
    ("fries", "protein"): 4,
    ("fries", "fat"): 19,
    ("fries", "sodium"): 270,
    ("macaroni", "calories"): 320,
    ("macaroni", "protein"): 12,
    ("macaroni", "fat"): 10,
    ("macaroni", "sodium"): 930,
    ("pizza", "calories"): 320,
    ("pizza", "protein"): 15,
    ("pizza", "fat"): 12,
    ("pizza", "sodium"): 820,
    ("salad", "calories"): 320,
    ("salad", "protein"): 31,
    ("salad", "fat"): 12,
    ("salad", "sodium"): 1230,
    ("milk", "calories"): 100,
    ("milk", "protein"): 8,
    ("milk", "fat"): 2.5,
    ("milk", "sodium"): 125,
    ("ice cream", "calories"): 330,
    ("ice cream", "protein"): 8,
    ("ice cream", "fat"): 10,
    ("ice cream", "sodium"): 180,
}

gp.multidict(data) takes a Python dictionary as input and returns a list of keys and one dictionary per list position. See the following example:

# Example usage of gp.multidict
data = {
    "key1": [1, 2],
    "key2": [3, 4],
    "key3": [5, 6],
}
keys, dict1, dict2 = gp.multidict(data)
print(keys)   # Output: ['key1', 'key2', 'key3']
print(dict1)  # Output: {'key1': 1, 'key2': 3, 'key3': 5}
print(dict2)  # Output: {'key1': 2, 'key2': 4, 'key3': 6}

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:

# Model
m = gp.Model("diet")

# Create decision variables for the foods to buy
buy = m.addVars(foods, name="buy")

# The objective is to minimize the costs
m.setObjective(buy.prod(cost), GRB.MINIMIZE)

# Nutrition constraints
m.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")


# Solve
m.optimize()
printSolution()

After the solver log, the program prints:

Cost: 11.8289

Buy:
hamburger 0.604514
milk 6.97014
ice cream 2.59132

The cheapest diet costs 11.83. It consists of about 0.60 servings of hamburger, 6.97 servings of milk, and 2.59 servings of ice cream.

3.6.3 Adding Variables with addVars

Recall that foods is a list of food names.

["hamburger", "chicken", "hot dog", "fries", "macaroni", "pizza", "salad", "milk", "ice cream"]

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 = None
for x in [0, 1]:
    for y in [0, 1]:
        for z in [0, 1]:
            # your code here

Method 2: itertools.product

from itertools import product

best = None
for 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.

  1. 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.
  2. 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.

  1. Write down the mathematical model implemented in facility.py, clearly defining the sets, parameters, decision variables, objective function, and constraints.
  2. Run the program and report which plants are open in the optimal solution and the minimum total cost.
  3. 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.

  1. Cite the source, including the relevant chapter, section, or page numbers. Briefly describe the problem and explain why it interests you.
  2. Write down the mathematical model, clearly defining the sets, parameters, decision variables, objective function, and constraints.
  3. 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.
  4. 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.