0

I have attempted to build and solve a linear programming problem using python, however I am receiving the following error when it is ran:

KeyError: 0

The following is the code I am using:

bfast = 1
lunchanddinner = 2
mealstypes = list(set(meals_dataset.Type))
constraints = {'breakfast':bfast,'lunch_dinner':lunchanddinner}
meals = meals_dataset.Meal.tolist()
calories = dict( zip( meals, np.array(meals_dataset.Calories.tolist())))
x = pulp.LpVariable.dicts( "x", indexs = meals, lowBound=0, upBound=1, cat='Integer', indexStart=[])
proteins = meals_dataset.Protein.tolist()
def model(minprotein):
  prob = pulp.LpProblem("Meal Plan", LpMinimize)
  prob += pulp.lpSum( [ x[i]*calories[i] for i in meals])
  for i in range(len(mealstypes)):
    type_data = meals_dataset[meals_dataset.Type==mealstypes[i]]
    boundary_condition = np.arange(type_data.index.min(),type_data.index.max())
    const = constraints[mealstypes[i]]
    prob += pulp.lpSum( [ x[j] for j in boundary_condition ] )==const
  prob += pulp.lpSum( [ x[i]*proteins[i] for i in range(len(x))])<= minprotein
  return prob
def find_best_plan(minprotein):
  prob = model(minprotein)
  prob.solve()
  variables = []
  values = []
  for v in prob.variables():
    variable = v.name
    value = v.varValue
    variables.append(variable)
    values.append(value)
  values = np.array(values).astype(int)
  meal_list = pd.DataFrame(np.array([variables,values]).T,columns = ['Variable','Optimal Value'])
  meal_list['Optimal Value'] = meal_list['Optimal Value'].astype(int)
  squad = meal_list[meal_list['Optimal Value']!=0]
  squad_meals = meals_dataset.Meal.loc[np.array(squad.Variable.str.split('_').tolist())[:,1].astype(int)]
  squad_type = meals_dataset.Type.loc[np.array(squad.Variable.str.split('_').tolist())[:,1].astype(int)]
  return pd.DataFrame([squad_meals,squad_type]).T

I then run the following

find_best_plan_80 = find_best_plan(80)

This results in the error

KeyError                                  Traceback (most recent call last)
<ipython-input-38-43f957c4ddba> in <cell line: 1>()
----> 1 find_best_plan_80 = find_best_plan(80)

2 frames
<ipython-input-36-07827a9fc028> in <listcomp>(.0)
     10     boundary_condition = np.arange(type_data.index.min(),type_data.index.max())
     11     const = constraints[mealstypes[i]]
---> 12     prob += pulp.lpSum( [ x[j] for j in boundary_condition ] )==const
     13   prob += pulp.lpSum( [ x[i]*proteins[i] for i in range(len(x))])<= minprotein
     14   return prob

KeyError: 0

I am stuck here and cannot work out what my issue is. For reference, this is the database I am using:

                        Meal  Calories   Protein             Type
0   weetabix_milk_applejuice   298.125   6.47025        breakfast
1           eggs_bacon_toast   635.500  32.41750        breakfast
2     proteinyog_raspberries   238.600  43.71750        breakfast
3             pancakes_syrup   418.500   9.61200        breakfast
4       turkeybacon_omelette   374.000  26.75750        breakfast
5      chicken_rice_broccoli   519.200  51.88300  lunch or dinner
6             prawn_stir_fry   559.200  37.99650  lunch or dinner
7      steak_fries_asparagus   867.100  67.16000  lunch or dinner
8       chicken_caesar_salad   242.900  35.78700  lunch or dinner
9            chicken_fajitas   804.560  58.12480  lunch or dinner
10     beefpie_mash_broccoli   736.200  23.33600  lunch or dinner
11                   tuna_jp   425.600  32.28300  lunch or dinner
12                beef_pasta   652.250  55.38000  lunch or dinner
13                       blt   513.000  15.46350  lunch or dinner
14          chicken_sandwich   397.200  29.76750  lunch or dinner

Any help would be greatly appreciated.

2 Answers2

1

You are making some fundamental mistakes with indexing a variable. If you look at the error, it is telling you that x[0] doesn't exist (key error)....

You are defining x to be indexed by the set of Meals like {tuna_jp, blt, ...} but then you start indexing with integers from boundary_condition and then with range(len(x)) in the next line. Neither of those will work. Either pick the names or the integers.

My strong suggestion that will alleviate a ton of confusion as you get started is to ditch pandas and numpy and just use basic python dictionaries. It is much cleaner and you will be able to focus on the modeling. Then, if the problem gets bigger or you have a large datafile, then maybe pandas.

AirSquid
  • 10,214
  • 2
  • 7
  • 31
1

Pandas is actually a pretty reasonable way of doing things with Pulp - better IMHO than bare Pulp - but it needs to be done correctly, and it hasn't been in the original code.

from io import StringIO

import pandas as pd
import pulp

dstr = '''          Meal,Calories, Protein,           Type
weetabix_milk_applejuice, 298.125, 6.47025,      breakfast
        eggs_bacon_toast, 635.500,32.41750,      breakfast
  proteinyog_raspberries, 238.600,43.71750,      breakfast
          pancakes_syrup, 418.500, 9.61200,      breakfast
    turkeybacon_omelette, 374.000,26.75750,      breakfast
   chicken_rice_broccoli, 519.200,51.88300,lunch or dinner
          prawn_stir_fry, 559.200,37.99650,lunch or dinner
   steak_fries_asparagus, 867.100,67.16000,lunch or dinner
    chicken_caesar_salad, 242.900,35.78700,lunch or dinner
         chicken_fajitas, 804.560,58.12480,lunch or dinner
   beefpie_mash_broccoli, 736.200,23.33600,lunch or dinner
                 tuna_jp, 425.600,32.28300,lunch or dinner
              beef_pasta, 652.250,55.38000,lunch or dinner
                     blt, 513.000,15.46350,lunch or dinner
        chicken_sandwich, 397.200,29.76750,lunch or dinner
'''

with StringIO(dstr) as f:
    df = pd.read_csv(f, skipinitialspace=True, index_col='Meal')


def make_select(row: pd.Series) -> pulp.LpVariable:
    return pulp.LpVariable(name='assign_' + row.name, cat=pulp.LpBinary)


df['Select'] = df.apply(make_select, axis=1)
prob = pulp.LpProblem(name='meal_plan', sense=pulp.LpMinimize)
prob.objective = df.Calories.dot(df.Select)

prob.addConstraint(
    name='min_protein',
    constraint=df.Protein.dot(df.Select) >= 80,
)
prob.addConstraint(
    name='breakfasts',
    constraint=df[df.Type == 'breakfast'].Select.sum() == 1,
)
prob.addConstraint(
    name='dinners',
    constraint=df[df.Type == 'lunch or dinner'].Select.sum() == 2,
)
print(prob)

prob.solve()
assert prob.status == pulp.LpStatusOptimal
df['Select'] = df.Select.apply(pulp.LpVariable.value)
print(df)
meal_plan:
MINIMIZE
652.25*assign_beef_pasta + 736.2*assign_beefpie_mash_broccoli + 513.0*assign_blt + 242.9*assign_chicken_caesar_salad + 804.56*assign_chicken_fajitas + 519.2*assign_chicken_rice_broccoli + 397.2*assign_chicken_sandwich + 635.5*assign_eggs_bacon_toast + 418.5*assign_pancakes_syrup + 559.2*assign_prawn_stir_fry + 238.6*assign_proteinyog_raspberries + 867.1*assign_steak_fries_asparagus + 425.6*assign_tuna_jp + 374.0*assign_turkeybacon_omelette + 298.125*assign_weetabix_milk_applejuice + 0.0
SUBJECT TO
min_protein: 55.38 assign_beef_pasta + 23.336 assign_beefpie_mash_broccoli
 + 15.4635 assign_blt + 35.787 assign_chicken_caesar_salad
 + 58.1248 assign_chicken_fajitas + 51.883 assign_chicken_rice_broccoli
 + 29.7675 assign_chicken_sandwich + 32.4175 assign_eggs_bacon_toast
 + 9.612 assign_pancakes_syrup + 37.9965 assign_prawn_stir_fry
 + 43.7175 assign_proteinyog_raspberries + 67.16 assign_steak_fries_asparagus
 + 32.283 assign_tuna_jp + 26.7575 assign_turkeybacon_omelette
 + 6.47025 assign_weetabix_milk_applejuice >= 80
breakfasts: assign_eggs_bacon_toast + assign_pancakes_syrup
 + assign_proteinyog_raspberries + assign_turkeybacon_omelette
 + assign_weetabix_milk_applejuice = 1
dinners: assign_beef_pasta + assign_beefpie_mash_broccoli + assign_blt
 + assign_chicken_caesar_salad + assign_chicken_fajitas
 + assign_chicken_rice_broccoli + assign_chicken_sandwich
 + assign_prawn_stir_fry + assign_steak_fries_asparagus + assign_tuna_jp = 2
VARIABLES
0 <= assign_beef_pasta <= 1 Integer
0 <= assign_beefpie_mash_broccoli <= 1 Integer
0 <= assign_blt <= 1 Integer
0 <= assign_chicken_caesar_salad <= 1 Integer
0 <= assign_chicken_fajitas <= 1 Integer
0 <= assign_chicken_rice_broccoli <= 1 Integer
0 <= assign_chicken_sandwich <= 1 Integer
0 <= assign_eggs_bacon_toast <= 1 Integer
0 <= assign_pancakes_syrup <= 1 Integer
0 <= assign_prawn_stir_fry <= 1 Integer
0 <= assign_proteinyog_raspberries <= 1 Integer
0 <= assign_steak_fries_asparagus <= 1 Integer
0 <= assign_tuna_jp <= 1 Integer
0 <= assign_turkeybacon_omelette <= 1 Integer
0 <= assign_weetabix_milk_applejuice <= 1 Integer
Welcome to the CBC MILP Solver 
Version: 2.10.3 
Build Date: Dec 15 2019 

Result - Optimal solution found
Objective value:                878.70000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.01
Time (Wallclock seconds):       0.01
Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.01   (Wallclock seconds):       0.01

                          Calories   Protein             Type  Select
Meal                                                                 
weetabix_milk_applejuice   298.125   6.47025        breakfast     0.0
eggs_bacon_toast           635.500  32.41750        breakfast     0.0
proteinyog_raspberries     238.600  43.71750        breakfast     1.0
pancakes_syrup             418.500   9.61200        breakfast     0.0
turkeybacon_omelette       374.000  26.75750        breakfast     0.0
chicken_rice_broccoli      519.200  51.88300  lunch or dinner     0.0
prawn_stir_fry             559.200  37.99650  lunch or dinner     0.0
steak_fries_asparagus      867.100  67.16000  lunch or dinner     0.0
chicken_caesar_salad       242.900  35.78700  lunch or dinner     1.0
chicken_fajitas            804.560  58.12480  lunch or dinner     0.0
beefpie_mash_broccoli      736.200  23.33600  lunch or dinner     0.0
tuna_jp                    425.600  32.28300  lunch or dinner     0.0
beef_pasta                 652.250  55.38000  lunch or dinner     0.0
blt                        513.000  15.46350  lunch or dinner     0.0
chicken_sandwich           397.200  29.76750  lunch or dinner     1.0
Reinderien
  • 11,755
  • 5
  • 49
  • 77
  • 1
    I enjoy counter-opinions, esp. when backed up with nice code. I'm giving it an upvote just for the slick StringIO => data frame implementation. Learned something there! ;) – AirSquid Jun 27 '23 at 04:33