Search code examples
pythonlinear-programmingpulp

How do I generate PuLP variables and constrains without using exec?


I have written the following Python Code using the PuLP Library for solving the Knapsack Problem using the Integer Programming formulation. I am using strings to generate the LpVariable commands and add the constraints and then executing them with eval. Is there a way to do this without using eval?

from pulp import *

#Knapsack problem

items = input ('Enter the number of items :')

items = int(items)

#print('Enter %d items one by one')

print ('Enter {0} items profit one by one'.format(items))

obj = []
weight = []
knapweight = 0


for i in range(0,items):
    print('Enter {0} item profit : '.format(i+1))
    obj.append(input())

for i in range(0, items):
    print('The profit at {0} is {1}'.format(i, obj[i]))

print ('\nEnter {0} items weights one by one'.format(items))


for i in range(0, items):
    print('Enter {0} item weight'.format(i+1))
    weight.append(input())

for i in range(0, items):
    print('The profit at {0} is {1}'.format(i, weight[i]))

print ('\nEnter the weight of the knapsack :')
knapweight = input()

print ('The weight of the knapsack is : {0}'.format(knapweight))


#generating variables
for i in range(0, items):
    str = 'x{0} = LpVariable("x{1}", cat=\'Binary\')'.format(i+1,i+1)
    print (str)
    exec(str)

prob = LpProblem('Knapsack', LpMaximize)

print ('\nDynamic Generaion\n')

#weight constraint generation
str = "prob += "

for i in range(0, items):
    if i == (items-1):
        str = str + weight[i] + '*x{0}'.format(i+1)
    else:
        str = str + weight[i] + '*x{0}'.format(i+1) + '+'

str = str + '<=' + knapweight
exec(str)
print(str)

#objective function generation
str = "prob += "

for i in range(0, items):
    if i == (items-1):
        str = str + obj[i] + '*x{0}'.format(i+1)
    else:
        str = str + obj[i] + '*x{0}'.format(i+1) + '+'

exec(str)
print(str)

status = prob.solve()
print(LpStatus[status])

print ('\nThe values of the variables : \n')

for i in range(0, items):
    print('x{0} = {1}'.format(i+1, value(eval('x{0}'.format(i+1)))))

Solution

  • The key is recognizing that it's okay to have an object -- say one in a list or a dictionary -- which you don't have explicitly bound to a name. You can make an object, append it to a list, and only ever refer to it as some_list[2]. Once you allow yourself that freedom, your code can become much simpler.

    Here I've hardcoded the inputs, because that doesn't matter:

    from pulp import *
    
    objs = [2,3,2,5,3]
    weights = [1,2,2,1,3]
    knapweight = 5
    
    prob = LpProblem('Knapsack', LpMaximize)
    xs = [LpVariable("x{}".format(i+1), cat="Binary") for i in range(len(objs))]
    
    # add objective
    total_prof = sum(x * obj for x,obj in zip(xs, objs))
    prob += total_prof
    
    # add constraint
    total_weight = sum(x * w for x,w in zip(xs, weights))
    prob += total_weight <= knapweight
    
    status = prob.solve()
    print(LpStatus[status])
    print("Objective value:", value(prob.objective))
    print ('\nThe values of the variables : \n')
    for v in prob.variables():
        print(v.name, "=", v.varValue)
    

    which gives me

    Optimal
    Objective value: 10.0
    
    The values of the variables : 
    
    x1 = 1.0
    x2 = 1.0
    x3 = 0.0
    x4 = 1.0
    x5 = 0.0
    

    Here I'm building the LpVariables in the list comprehension

    xs = [LpVariable("x{}".format(i+1), cat="Binary") for i in range(len(objs))]
    

    where the objects then just live in the list xs.

    >>> xs
    [x1, x2, x3, x4, x5]
    >>> xs[3]
    x4
    >>> type(xs[3])
    <class 'pulp.pulp.LpVariable'>