How to convert static variables into arrays for use with PuLP

I have the following code in Python and PuLP which uses static variables. I want to know how to solve the problem by converting all of the LpVariable parts into an array, as well as the constraints. The idea is to be able to add new elements to the LpVariable and constraints array and having the solver run through the arrays rather than having to manually code new lines if another type of bicycle needs to be produced or additional parts are added as constraints.

    #Bicycle Manufacturing Schedule
from pulp import *

# The challenge is to maximise the profit by producing an optimum number of each type of bicycle
# A - "Mountain Bike", profit margin of $$45 per unit # B - "Street Bike", profit margin of$$60 per unit
# C - "Racing Bike", profit margin of $$55 per unit # D - "Commuter bike", profit margin of$$50 per unit
#
model = LpProblem("Profit Maximising", LpMaximize)

#We can't sell half a bike, so the category for each bicycle type is an integer.
#A >= 0, B >= 0, C >= 0, D >= 0 : Input (Changing) cells
A = LpVariable('A', lowBound=0, cat='Integer')
B = LpVariable('B', lowBound=0, cat='Integer')
C = LpVariable('C', lowBound=0, cat='Integer')
D = LpVariable('D', lowBound=0, cat='Integer')

#Our objective is to maximise the profit
model += 45 * A + 60 * B + 55 * C + 50 * D, "Total Profit"

#Setup the constraints (parts available in stock)
wheels = 2 * A + 2 * B + 2 * C + 2 * D
alloy_chassis = 1 * A + 1 * C
steel_chassis = 1 * B + 1 * D
hub_gears = 1 * A + 1 * D
derailleur_gears = 1 * B + 1 * C

model += wheels <=180
model += alloy_chassis <= 40
model += steel_chassis <= 60
model += hub_gears <= 50
model += derailleur_gears <= 40

#Print the problem
print (model)

#Solve the problem
model.solve()
print ("Status : ", LpStatus[model.status])

#Print our changing cells
print ("Units of Mountain Bike   = ", A.varValue)
print ("Units of Street Bike     = ", B.varValue)
print ("Units of Racing Bike     = ", C.varValue)
print ("Units of Commuter Bike   = ", D.varValue)

#Print our objective function value - Result (Target) cell
print ("Total Profit             = ", value(model.objective))


EDIT 01-FEB-2021

I have converted the code to use arrays instead of fixed variables.

    #Bicycle Manufacturing Schedule
from pulp import *

# The challenge is to maximise the profit by producing an optimum number of each type of bicycle
# A, profit margin of $$45 per unit # B, profit margin of$$60 per unit
# C, profit margin of $$55 per unit # D, profit margin of$$50 per unit

bike_types = ["A", "B", "C", "D"]
bike_profit = [45, 60, 55, 50]

parts_name = ["wheels", "alloy_chassis", "steel_chassis", "hub_gears", "derailleur_gears"]
parts_stock = [180, 40, 60, 50, 40]
bike_parts = [  #part_names
#"wheels", "alloy_chassis", "steel_chassis", "hub_gears", "derailleur_gears"
[2,1,0,1,0],#Bike type A
[2,0,1,0,1],#Bike type B
[2,1,0,0,1],#Bike type C
[2,0,1,1,0] #Bike type D
]

prob = LpProblem("Profit Maximising", LpMaximize)

n = len(bike_profit)
N = range(n)

#We can't sell half a bike, so the category for each bicycle type is an integer.
x = LpVariable.dicts("x", N, lowBound=0, cat='Integer')

#Our objective is to maximise the profit
prob += lpSum([bike_profit[i]*x[i] for i in N]), "Total Profit"

#Constraints on available bike parts
for p in range(len(parts_name)):
prob += lpSum(bike_parts[i][p]*x[i] for i in N) <= parts_stock[p], parts_name[p]

#Print the problem
print (prob)

#Solve the problem
prob.solve()
print ("Status : ", LpStatus[prob.status])

#Print our changing cells
for i in range(len(bike_types)):
print ("Units of ", x[i],"= ", x[i].varValue)

#Print our objective function value - Result (Target) cell
print ("Total Profit             = ", value(prob.objective))


• You can use pulp.LpVariable.dicts (see for example here). One of the parameters is an array of objects. Jan 31 at 17:26
• Thanks for the example. I have edited the question to include the code using arrays. As I am new to this, I was wondering whether there was a more "elegant" solution with regards to the coding ? Feb 1 at 11:56
• It looks pretty good. But maybe instead of creating variables x_1, x_2, x_3, x_4, you could use variables x_A, x_B, c_C, x_D with x = LpVariable.dicts("x", bike_types, lowBound=0, cat='Integer'). I think this way it is more readable, and you can get rid of N. Feb 1 at 12:08
• Tried your suggestion, but am getting a KeyError:0 in the line prob += lpSum([bike_profit[i]*x[i] for i in N]), "Total Profit" KeyError: 0 Feb 1 at 12:23
• Well, yes you have to adapt the rest of the code to get entirely rid of N. I will post a detailed solution when I can. Feb 1 at 13:04

Your code is correct, you have used pulp.LpVariable.dicts correctly.

For better readability, it is often better to use dictionaries. So the idea is to convert your data from lists to dicts as follows:

# convert data into dicts
dict_bike_profit = dict(zip(bike_types, bike_profit)) # simple dict
dict_bike_stock = dict(zip(part_names, parts_stock)) # simple dict
dict_bike_parts = makeDict([part_names, bike_types], bike_parts) # nested dict


I suggest you print the above dicts to see what they are equal to, with your own eyes. Once you have such objects, writing the constraints is easier. The code below yields a total profit of 4750.

# Bicycle Manufacturing Schedule
from pulp import *

# The challenge is to maximise the profit by producing an optimum number of each type of bicycle
# A, profit margin of $$45 per unit # B, profit margin of$$60 per unit
# C, profit margin of $$55 per unit # D, profit margin of$$50 per unit

bike_types = ["A", "B", "C", "D"]
bike_profit = [45, 60, 55, 50]
parts_stock = [180, 40, 60, 50, 40]
part_names = [
"wheels",
"alloy_chassis",
"steel_chassis",
"hub_gears",
"derailleur_gears",
]
bike_parts = [  # part_names
# "wheels", "alloy_chassis", "steel_chassis", "hub_gears", "derailleur_gears"
[2, 1, 0, 1, 0],  # Bike type A
[2, 0, 1, 0, 1],  # Bike type B
[2, 1, 0, 0, 1],  # Bike type C
[2, 0, 1, 1, 0],  # Bike type D
]
# transpose the above array (you need to do this to write your constraint correctly)
bike_parts = list(map(list, zip(*bike_parts)))

# convert data into dicts
dict_bike_profit = dict(zip(bike_types, bike_profit))
dict_bike_stock = dict(zip(part_names, parts_stock))
dict_bike_parts = makeDict([part_names, bike_types], bike_parts)

prob = LpProblem("Profit Maximising", LpMaximize)

# We can't sell half a bike, so the category for each bicycle type is an integer.
x = LpVariable.dicts("x", bike_types, lowBound=0, cat="Integer")

# Our objective is to maximise the profit
prob += lpSum([dict_bike_profit[i] * x[i] for i in dict_bike_profit]), "Total Profit"

# Constraints on available bike parts
for i in dict_bike_parts:
prob += (
lpSum(dict_bike_parts[i][p] * x[p] for p in dict_bike_parts[i])
<= dict_bike_stock[i],
"availability_%s" % (i),
)

# Print the problem
print(prob)

# Solve the problem
prob.solve()
print("Status : ", LpStatus[prob.status])

# Print our changing cells
for i in x:
print("Units of ", x[i], "= ", x[i].varValue)

# Print our objective function value - Result (Target) cell
print("Total Profit             = ", value(prob.objective))

• I think there is a problem with the dict_bike_stock. When running the solver ... availability_A_wheels: 2 x_A + 2 x_B + 2 x_C + 2 x_D <= 180 ... availability_B_wheels: 2 x_A + 2 x_B + 2 x_C + 2 x_D <= 40... Both wheels should be <= 180 with the optimal profit working out to 4750.0 Feb 1 at 14:34
• Yes you are right, I misread/misunderstood. I edited accordingly. Feb 1 at 14:48
• Works perfectly. Thank you. Feb 1 at 14:58