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:

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:

\[\text{Maximize} \quad x + y \]

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:

\[\begin{split} \begin{aligned} 3 x + 1.50 y & \leq 20 & \\ x & \ge 0 & \\ y & \ge 0 & \\ \end{aligned} \end{split}\]

Putting these together we typically write the final model formulation as:

\[\begin{split} \begin{aligned} \text{Maximize} \quad x + y & \\ 3 x + 1.50 y & \le 20 \\ x & \ge 0 \\ y & \ge 0 \\ \end{aligned} \end{split}\]

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:

  1. You have a total cargo space that can carry up to 100 units.

  2. Each water bottle takes up 2 units of cargo space.

  3. 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#

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