问题
The code below is used to run a LP minimization problem where we have certain foods, their nutritional value, and their cost. The code currently works in the state presented. I am trying to add one more type of constraint. I have all the foods broken up into their categories (Breakfast, Lunch, Dinner, Snack). I want to create a constraint where Only 1 Breakfast, Lunch, and Dinner item can be chosen. (No limit on snacks). The "1" and "0" correspond to if the item is (Breakfast,Lunch, Dinner, or Snack) depending on where it is in the array.
from pulp import *
Food = ["Bacon", "Eggs", "Pancakes", "Waffles", "Yogurt", "Bagels", "Sausage", "Cheerios",
"Strawberries", "Milk", "OJ", "Oranges", "Apples", "Carrots", "Broccoli","Ham", "Turkey",
"Steak", "Salmon", "Pasta","Chicken", "Pizza", "Rice", "Salad", "Potatoes"]
nutrition = ["Calories", "Protein", "Sugars", "Cholesterol", "Vitamin_A", "Vitamin_B", "Vitamin_C",
"Vitamin_K", "Vitamin_E", "Zinc", "Iron", "Fat", "Sodium", "Carbs", "Fiber",
"Calcium", "Potassium", "Folic_acid", "Thiamin"]
Category = ["Breakfast", "Lunch", "Dinner", "Snack"]
VarCategory = [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
VarCategory = makeDict([Category, Food], VarCategory)
VarNutrition = [[45, 367, 84.3, 212, 250, 72.3, 150, 103, 49, 100, 134, 85.1, 52.8, 5.3, 30.9, 290, 280, 412, 159, 288, 231, 324, 428, 370, 403],
[3, 24, 2.3, 5.3, 10.7, 2.8, 6, 3.2, 1, 8, 1, 1.3, 0.3, 1.1, 2.6, 18, 18, 21, 24.9, 12, 43.4, 13.9, 19.2, 20, 13.7],
[0, 4, 0, 5.2, 46.7, 0, 1, 1.1, 7, 13, 23.3, 16.9, 11.1, 0.7, 1.5, 6, 5, 0, 0, 11, 0, 4.1, 13.8, 1, 0],
[3, 86, 7, 0, 3, 2, 10, 0, 0, 3, 0, 0, 0, 0, 0, 8, 7, 61, 10, 10, 40, 9, 18, 13, 7],
[0, 23, 2, 20, 2, 1, 4, 16, 0, 10, 2, 8, 1, 41, 11, 6, 6, 0, 2, 10, 1, 6, 41, 10, 34],
[0, 20, 1, 19, 12, 1, 0, 27, 2, 0, 31, 2.5, 1, 1, 4, 0, 0, 50, 50, 12, 25, 10, 23, 0, 22],
[0, 1, 1, 2, 3, 0, 0, 11, 149, 0, 62, 139, 7, 1, 135, 35, 35, 0, 0, 10, 0, 0, 17, 30, 81],
[0, 11, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 1, 2, 116, 0, 0, 4, 0, 10, 1, 8, 16, 0, 0],
[0, 12, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 0, 0, 4, 0, 0, 4, 9, 10, 2, 6, 4, 0, 0],
[0, 15, 1, 3, 12, 1, 0, 30, 1, 0, 0, 1, 0, 0, 2, 0, 0, 69, 3, 10, 9, 10, 9, 0, 14],
[0, 15, 4, 20, 1, 6, 4, 49, 3, 0, 2, 1, 0, 1, 4, 20, 20, 28, 6, 10, 8, 16, 7, 8, 18],
[6, 41, 5, 11, 4, 1, 22, 3, 1, 4, 0, 1, 0, 0, 1, 8, 7, 25, 9, 20, 8, 19, 8, 29, 33],
[6, 26, 7, 12, 6, 5, 15, 8, 0, 5, 0, 0, 0, 0, 1, 53, 42, 3, 44, 50, 4, 25, 47, 59, 20],
[0, 2, 4, 11, 16, 5, 0, 7, 4, 4, 11, 7, 5, 0, 2, 15, 15, 0, 0, 10, 0, 13, 25, 10, 16],
[0, 0, 0, 8, 0, 2, 0, 11, 12, 0, 2, 18, 6, 2, 9, 16, 16, 0, 0, 30, 0, 7, 10, 0, 0],
[0, 16, 8, 4, 37, 0, 2, 11, 2, 30, 0, 8, 1, 0, 4, 6, 6, 1, 1, 0, 2, 15, 4, 4, 34],
[0, 9, 1, 4, 14, 1, 0, 5, 7, 0, 3, 9, 3, 1, 8, 0, 0, 14, 7, 10, 10, 6, 12, 0, 41],
[0, 17, 3, 10, 6, 6, 0, 68, 9, 0, 2, 8, 0, 1, 14, 0, 0, 5, 1, 20, 1, 0, 15, 0, 15],
[0, 8, 5, 21, 6, 9, 0, 36, 2, 0, 63, 12, 1, 0, 4, 0, 0, 9, 2, 10, 7, 14, 17, 0, 18]]
VarNutrition = makeDict([nutrition, Food], VarNutrition)
ConstraintsLow = [2000, 72, 0, 85, 100, 100, 100, 100, 100, 0, 0, 0, 0, 90, 100, 100, 100, 100, 100]
ConstraintsLow = makeDict([nutrition],ConstraintsLow)
Cost = [1.22, 1.56, 6.79, 6.79, 1.00, 2.50, 2.00, 0.14, 1.37, 1.69, 1.99, 0.50, 0.50, 0.50, 0.50, 4.25, 4.25, 4.00, 5.00, 7.00, 3.18, 1.25, 5.00, 6.00, 3.00]
Cost = makeDict([Food], Cost)
prob = LpProblem("Nutrition Calculator", LpMinimize)
vars = LpVariable.dicts("Servings of", (Food), 0, None, LpContinuous)
Svars = LpVariable.dicts("Food Chosen", (Category, Food), 0, None, LpBinary)
prob += lpSum(vars[i]*Cost[i] for i in Food )
for j in nutrition:
prob += lpSum([vars[i]*VarNutrition[j][i] for i in Food]) >= ConstraintsLow[j]
for i in Food:
prob += vars[i] >= 0
prob += vars[i] <= 2
print (prob)
prob.writeLP("Nutrition.lp")
prob.solve()
print ("Status:", LpStatus[prob.status])
for v in prob.variables():
print (v.name, "=", v.varValue)
print ("Total Cost = ", value(prob.objective))
The problem I am having is creating such a constraint. I thought to use a binary variable but I am not sure how to implement that. Any help would be appreciated
回答1:
What you should do is have binary variables for each type of food that is selected, and then a constraint that the sum of them is 1 -- meaning exactly one of the binary variables is 1 and the others are 0.
The issue is that making, for example, the breakfast binary variable turn on means that there is an if-then condition in your linear program. If at least one breakfast item is picked, then breakfast is 1, otherwise 0. An if-then statement is not linear, so we need a clever way to make this linear. We can do this with a "big M constraint".
Make python variables representing the sum of decisions for each of types of food, e.g. breakfast_sum
, lunch_sum
etc.
Then make PuLP binary variables breakfast_binary
, lunch_binary
etc.
We'll use a big M constraint to have breakfast_binary
"flip" when breakfast_sum
is greater than 0. Then we'll use another constraint to make sure the sum of the binary variables <= 1.
M is basically a big number. How big does it need to be? Notice that you will never allocate more than 2 servings of each breakfast item, so try making M = 2 * {number of breakfast items}. Now look at this constraint:
M * breakfast_binary >= breakfast_sum
.
If breakfast_sum
is 0, then breakfast_binary
is allowed to be 0. As soon as you allocate a serving of a breakfast item, breakfast_binary
is forced to flip to 1.
Do this with lunch, dinner etc, then have an additional constraint that the sum of the binary variables is <= 1.
This answer paraphrases liberally from Ch 4 "Optimization Modeling" of Data Smart by John Foreman. I highly recommend it.
来源:https://stackoverflow.com/questions/43783848/pulp-lp-minimization-formulating-select-one-type-constraint