A Quick Introduction to Optimization Modeling#
Before we dive into a few examples, here is a list of resources that might prove helpful for this section:
Blog: Optimization Modeling: The art of not making it an art
Video series: Modeling with Gurobi Python
Collection of Jupyter notebook modeling examples
Now, let’s take a general look at how optimization works. Optimization models are broken down into two main parts: The objective function (what we want to solve), and the constraints (what limits our input). Each of these are composed of decision variables, the main building blocks of mathematical optimization models are represent the decisions or actions we have available.
The objective function tells the program what we want to do—think maximizing profit or minimizing travel time. Let’s say in this example, you’re buying \(x\) hotdogs and \(y\) soda cans. You like both equally, and you want as much of each as possible. So, our objective function would look like this:
We obviously would want to just make both x and y infinite, but in the real world that obviously isn’t allowed, so we need a way to constrain the objective function. Suppose in this example a hotdog cost $3 and a soda costs $1.50 and you don’t want to spend more than $20. We would write:
Putting these together we typically write the final model formulation as:
If you’re intimidated by the math, don’t worry—the notation is a formal way to ask how much to buy. Just like machine learning algorithms, actually applying the underlying math is much easier, as most of that is abstracted away by the API.
Building a Model - Example 1#
So how do we go about solving this? There are five steps:
1. Instantiate The Solver#
import gurobipy as gp
from gurobipy import GRB
model_1 = gp.Model()
model_1.Params.OutputFlag = 0
Restricted license - for non-production use only - expires 2026-11-23
The OutputFlag parameter will prevent the typical Gurobi output from printing here. If you are running this notebook file externally, remove this setting to see the output if you would like.
2. Add the Decision Variables#
Note that we add bounds for the variables directly in their definition. In this way, we do not need constraints to define them.
# Create variables
x = model_1.addVar(vtype=GRB.INTEGER, lb=0, name="hotdogs")
y = model_1.addVar(vtype=GRB.INTEGER, lb=0, name="sodacans")
3. Add the Constraints#
model_1.addConstr(3 * x + 1.5 * y <= 20)
<gurobi.Constr *Awaiting Model Update*>
4. Add the Objective Function#
# Set objective
model_1.setObjective(x + y, GRB.MAXIMIZE)
5. Solve!#
# Optimize model
model_1.optimize()
From here we can get all the variables in the model and print their values:
# Show Solution
for v in model_1.getVars():
print("%s: %g" % (v.VarName, v.X))
print("Obj: %g" % model_1.ObjVal)
hotdogs: -0
sodacans: 13
Obj: 13
Building a Model - Example 2#
The solution to Example 1 is optimal in the sense that we got the maximal number of items. However, we only got soda cans. This is actually not surprising, since a soda can is cheaper than a hot dog, so we can get more items with our budget of 20 dollars.
There are a few ways to adapt the model to obtain at least some hot dogs:
We could add an upper limit of 5 on the number of hot dogs and soda cans.
We could favor the selection of hot dogs by giving it a 3x larger reward in the objective function.
Both changes are reflected in the following model:
model_2 = gp.Model()
model_2.Params.OutputFlag = 0
# Create variables
x = model_2.addVar(vtype=GRB.INTEGER, lb=0, ub=5, name="x")
y = model_2.addVar(vtype=GRB.INTEGER, lb=0, ub=5, name="y")
# add constraints
model_2.addConstr(3 * x + 1.5 * y <= 20, "c1")
model_2.setObjective(3 * x + y, GRB.MAXIMIZE)
model_2.optimize()
# Show Solution
for v in model_2.getVars():
print("%s %g" % (v.VarName, v.X))
print("Obj: %g" % model_2.ObjVal)
x 5
y 3
Obj: 18
Now, we got both hot dogs and soda cans!
Now Your Turn!#
Now that we’ve introduced the basics, let’s try some more problems. They look slightly different from our first example, but hopefully, they’ll help you to identify the objective function and constraints. There are some practice questions to help you along as you go, so don’t be afraid to make guesses and experiment!
Practice Problem 1#
Problem Statement: Imagine you are managing disaster relief efforts, and you need to allocate resources between two critical supplies: water bottles (\(w\)) and emergency food packs (\(f\)). Your goal is to maximize the number of relief items delivered to an affected area.
Each water bottle provides essential hydration and is valued at 3 units of relief.
Each food pack provides vital nutrition and is valued at 5 units of relief.
However, you face the following constraints:
You have a total cargo space that can carry up to 100 units.
Each water bottle takes up 2 units of cargo space.
Each food pack takes up 4 units of cargo space.
Now try to implement it! Remember you can always look back at the last section if you get stuck. We also have the answer key available as you scroll further down (so try not to read ahead)!
1. Instantiate The Solver#
# Instantiate Here
2. Add the Decision Variables#
# Add Vars Here
3. Add the Constraints#
# Add Constraints Here
4. Add the Objective Function#
# Add Obj. Func. Here
5. Solve!#
# Solve Here!
Practice Problem 1 Answer Key#
Remember, this is one possible solution, just because your code doesn’t look identical, doesn’t mean it’s wrong!
# 1 Instantiate The Solver
model = gp.Model("DisasterReliefAllocation")
model.Params.OutputFlag = 0
# 2 Add the variables for the number of water bottles (w) and food packs (f)
w = model.addVar(vtype=GRB.INTEGER, lb=0, name="WaterBottles")
f = model.addVar(vtype=GRB.INTEGER, lb=0, name="FoodPacks")
# 3 Add the constraint for the total cargo space
model.addConstr(2 * w + 4 * f <= 100, "CargoSpace")
# 4 Add the objective function: Maximize the total value of relief items
model.setObjective(3 * w + 5 * f, GRB.MAXIMIZE)
# 5 Optimize the model
model.optimize()
# Print the optimal solution
if model.status == GRB.OPTIMAL:
print(f"Optimal number of Water Bottles (w): {int(w.x)}")
print(f"Optimal number of Food Packs (f): {int(f.x)}")
print(f"Maximum Relief Value: {model.objVal}")
else:
print("No optimal solution found.")
Optimal number of Water Bottles (w): 50
Optimal number of Food Packs (f): 0
Maximum Relief Value: 150.0
Practice Problem 2#
Note: The following example is based on examples from Gurobi’s MOOC for Udemy
A non-governmental organization (NGO) and a manufacturing firm are partnering up to produce supplies in preparation for hurricane season. The NGO predicts that there will be a maximum demand of up to 100 packs of blankets, 70 packs of towels, and 40 packs of sleeping bags. Let’s say making these items requires two precision machining steps: weaving and packaging. The table below shows how many minutes are needed to produce each type of disaster relief item:
Weaving |
Packaging |
|
|---|---|---|
Blankets |
10 |
15 |
Towels |
8 |
18 |
Sleeping Bags |
12 |
17 |
To allow for maintenance and downtime, the company does not want to run its machines beyond a certain limit. The total time available on the machines is 2,500 hours for weaving and 2,000 hours for packaging. Once items are manufactured, they go to a quality control tester. These testers are contracted to test exactly 150 items for this upcoming season, no more and no less. Therefore, the company must manufacture exactly 150 items. Now, for the final step, let’s consider how many people each pack of items can serve:
People Served |
|
|---|---|
Blankets |
6 |
Towels |
7 |
Sleeping Bags |
10 |
# Implement your solution here
Problem 2 Answer Key#
m = gp.Model("instrument")
m.Params.OutputFlag = 0
# make decision variables: this is the qunatity of each instrument
x = m.addVars(3, vtype=GRB.INTEGER, name="x")
m.setObjective(6 * x[0] + 7 * x[1] + 10 * x[2], GRB.MAXIMIZE)
m.addConstr(10 * x[0] + 8 * x[1] + 12 * x[2] <= 2000, name="Weaving")
m.addConstr(15 * x[0] + 18 * x[1] + 17 * x[2] <= 2400, name="Packaging")
m.addConstr(x.sum() == 150, name="Total_Supplies")
m.addConstr(x[0] <= 100, name="max_100_Blankets_demand")
m.addConstr(x[1] <= 70, name="max_70_Towels_demand")
m.addConstr(x[2] <= 40, name="max_40_SleepBags_demand")
m.optimize()
# Show Solution
n_total = 0
for v in m.getVars():
print("%s %g" % (v.VarName, v.X))
n_total += v.X
print("Obj: %g" % m.ObjVal)
print(f"Number of items: {n_total}")
x[0] 87
x[1] 23
x[2] 40
Obj: 1083
Number of items: 150.0