Comparing SimpleModel, PuLP and Pyomo

This section illustrates differences between SimpleModel, PuLP and regular Pyomo models on the knapsack problem. This problem can be represented as a integer program, which all three of these modeling tools can easily represent.

Knapsack Problem

The Knapsack Problem considers the problem of selecting a set of items whose weight is not greater than a specified limit while maximizing the total value of the selected items. This problem is inspired by the challenge of filling a knapsack (or rucksack) with the most valuable items that can be carried.

A common version of this problem is the 0-1 knapsack problem, where each item is distinct and can be selected once. Suppose there are \(n\) items with positive values \(v_1, \ldots, v_n\) and weights \(w_1, \ldots, w_n\). Let \(x_1, \ldots, x_n\) be decision variables that can take values 0 or 1. Let W be the weight capacity of the knapsack.

The following optimization formulation represents this problem as an integer program:

\begin{equation*} \begin{array}{ll} \max & \sum _{i=1}^{n} v_{i} x_{i} \\ \textrm{s.t.} & \sum _{i=1}^{n} w_{i} x_{i}\leq W \\ & x_{i}\in \{0,1\} \end{array} \end{equation*}

The following sections illustrate how this optimization problem can be formulated with (1) SimpleModel, (2) PuLP, and (3) Pyomo.

SimpleModel Formulation

The following script executes the following steps to create and solve a knapsack problem:

  1. Import pyomo_simplemodel
  2. Construct a SimpleModel class
  3. Declare variables, the objective and the constraint
  4. Perform optimization
  5. Summarize the optimal solution
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# knapsack.py

from pyomo_simplemodel import *

v = {'hammer':8, 'wrench':3, 'screwdriver':6, 'towel':11}
w = {'hammer':5, 'wrench':7, 'screwdriver':4, 'towel':3}
limit = 14
items = list(sorted(v.keys()))

# Create model
m = SimpleModel(maximize=True)

# Variables
x = m.var('m', items, within=Binary)

# Objective
m += sum(v[i]*x[i] for i in items)

# Constraint
m += sum(w[i]*x[i] for i in items) <= limit


# Optimize
status = m.solve('glpk')

# Print the status of the solved LP
print("Status = %s" % status.solver.termination_condition)

# Print the value of the variables at the optimum
for i in items:
    print("%s = %f" % (x[i], value(x[i])))

# Print the value of the objective
print("Objective = %f" % value(m.objective()))

In this example, the model object m is used to manage the problem definition. Decision variables are declared with the var() method, the objective and constraint are added with the += operator, and the solve() method is used to perform optimization. After optimization, the solution is stored in the variable objects, and the objective value can be accessed with using the objective() method.

PuLP Formulation

The following script executes the same steps as above to create and solve a knapsack problem using PuLP:

# knapsack-pulp.py

from pulp import *

v = {'hammer':8, 'wrench':3, 'screwdriver':6, 'towel':11}
w = {'hammer':5, 'wrench':7, 'screwdriver':4, 'towel':3}
limit = 14
items = list(sorted(v.keys()))

# Create model
m = LpProblem("Knapsack", LpMaximize)

# Variables
x = LpVariable.dicts('x', items, lowBound=0, upBound=1, cat=LpInteger)

# Objective
m += sum(v[i]*x[i] for i in items)

# Constraint
m += sum(w[i]*x[i] for i in items) <= limit

# Optimize
m.solve()

# Print the status of the solved LP
print("Status = %s" % LpStatus[m.status])

# Print the value of the variables at the optimum
for i in items:
    print("%s = %f" % (x[i].name, x[i].varValue))

# Print the value of the objective
print("Objective = %f" % value(m.objective))

This script is very similar to the SimpleModel script. Both scripts declare a problem class that is used to declare variables, the objective and constraint, and to perform optimization.

Pyomo Formulation

The following script executes the same steps as above to create and solve a knapsack problem using Pyomo:

# knapsack-pyomo.py

from pyomo.environ import *

v = {'hammer':8, 'wrench':3, 'screwdriver':6, 'towel':11}
w = {'hammer':5, 'wrench':7, 'screwdriver':4, 'towel':3}
limit = 14
items = list(sorted(v.keys()))

# Create model
m = ConcreteModel()

# Variables
m.x = Var(items, within=Binary)

# Objective
m.value = Objective(expr=sum(v[i]*m.x[i] for i in items), sense=maximize)

# Constraint
m.weight = Constraint(expr=sum(w[i]*m.x[i] for i in items) <= limit)


# Optimize
solver = SolverFactory('glpk')
status = solver.solve(m)

# Print the status of the solved LP
print("Status = %s" % status.solver.termination_condition)

# Print the value of the variables at the optimum
for i in items:
    print("%s = %f" % (m.x[i], value(m.x[i])))

# Print the value of the objective
print("Objective = %f" % value(m.value))

This script is similar to the SimpleModel and PuLP scripts, but Pyomo models are created with an object-oriented design. Thus, elements of the optimization problem are declared with variable, objective and constraint components, which are Pyomo objects. As a consequence, the objective and constraint expressions reference variable components within the model (e.g. m.x) instead of variable objects directly (e.g. x). Thus, modeling in Pyomo is more verbose (especially when long model names are used).