{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Question 15.2.1\n", " \n", "In the videos, we saw the “diet problem”. (The diet problem is one of the first large-scale optimization problems to be studied in practice. Back in the 1930’s and 40’s, the Army wanted to meet the nutritional requirements of its soldiers while minimizing the cost.) In this homework you get to solve a diet problem with real data. The data is given in the file diet.xls. \n", " \n", "1. Formulate an optimization model (a linear program) to find the cheapest diet that satisfies the maximum and minimum daily nutrition constraints, and solve it using PuLP. Turn in your code and the solution. (The optimal solution should be a diet of air-popped popcorn, poached eggs, oranges, raw iceberg lettuce, raw celery, and frozen broccoli. UGH!) " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import logging\n", "from pulp import *" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "#Create input datasets for the homework\n", "diet = pd.read_excel('./diet.xls')\n", "diet_large = pd.read_excel('./diet_large.xls',header=1)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cholesterol mg 240\n", "Total_Fat g 70\n", "Sodium mg 2000\n", "Carbohydrates g 450\n", "Dietary_Fiber g 250\n", "Protein g 100\n", "Vit_A IU 10000\n", "Vit_C IU 5000\n", "Calcium mg 1500\n", "Iron mg 40\n", "Name: 66, dtype: object\n", "\n", "\n", "Cholesterol mg 30\n", "Total_Fat g 20\n", "Sodium mg 800\n", "Carbohydrates g 130\n", "Dietary_Fiber g 125\n", "Protein g 60\n", "Vit_A IU 1000\n", "Vit_C IU 400\n", "Calcium mg 700\n", "Iron mg 10\n", "Name: 65, dtype: object\n" ] } ], "source": [ "#Based on the office hours review we can see that the minimum and maximum nutritional constraints was not a part of the question, but\n", "#but it's the last two rows at the end of the dataframe that contain the data for these constraints\n", "max_constraints = diet.iloc[len(diet)-1][4:]\n", "print(max_constraints)\n", "print('\\n')\n", "min_constraints = diet.iloc[len(diet)-2][4:]\n", "print(min_constraints)\n", "\n", "diet = diet.iloc[0:64]" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "from pandas import DataFrame\n", "def create_input_dictionaries(df: DataFrame, fooditems: list):\n", " try:\n", " #Create parent dict variable which will contain all of the nutrient dictionaries\n", " parent_dict = {}\n", " \n", " #Take all of the nutrients columns and filter dataframe to only include nutrient columns\n", " nutrients = df.columns[4:]\n", " nutrients_df = df[nutrients]\n", " \n", " #Iterate through the columns and zip the contents of all food and nutritional elements\n", " for col in nutrients_df.columns:\n", " parent_dict[col] = dict(zip(fooditems,df[col]))\n", " return parent_dict\n", " except Exception as e:\n", " raise(e)\n", " \n", "# Create a list of the food items\n", "food_items = list(diet['Foods'])\n", "\n", "nutritional_dict = create_input_dictionaries(diet,food_items)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "#Create variables required for the model\n", "food_vars = LpVariable.dicts(\"Food\",food_items,lowBound=0,cat='Continuous')\n", "\n", "\n", "# Create a dictinary of costs for all food items\n", "costs = dict(zip(food_items,diet['Price/ Serving']))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\users\\mjpearl\\desktop\\omsa\\isye-6501-oan\\hw12\\env_hw12\\lib\\site-packages\\pulp\\pulp.py:1137: UserWarning: Spaces are not permitted in the name. Converted to '_'\n", " warnings.warn(\"Spaces are not permitted in the name. Converted to '_'\")\n" ] } ], "source": [ "prob = LpProblem(\"Military Diet Problem\",LpMinimize)\n", "\n", "#Create the objective function which is meant to contain the \n", "prob += lpSum([costs[i]*food_vars[i] for i in food_items])\n", "\n", "#Loops through and create the linear program constraints\n", "def create_constraints(linear_model: object, nutr_dict: dict):\n", " try:\n", " for nutrient, nutrient_values in nutr_dict.items():\n", " linear_model += lpSum([nutrient_values[f] * food_vars[f] for f in food_items]) >= min_constraints[nutrient]\n", " linear_model += lpSum([nutrient_values[f] * food_vars[f] for f in food_items]) <= max_constraints[nutrient]\n", " \n", " return linear_model\n", " \n", " except Exception as e:\n", " raise(e)\n", " \n", " \n", "prob_final = create_constraints(prob, nutritional_dict)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prob_final.solve()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Status: Optimal\n" ] } ], "source": [ "# The status of the solution is printed to the screen\n", "print(\"Status:\", LpStatus[prob_final.status])" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Food_Celery,_Raw = 52.64371\n", "Food_Frozen_Broccoli = 0.25960653\n", "Food_Lettuce,Iceberg,Raw = 63.988506\n", "Food_Oranges = 2.2929389\n", "Food_Poached_Eggs = 0.14184397\n", "Food_Popcorn,Air_Popped = 13.869322\n" ] } ], "source": [ "for v in prob_final.variables():\n", " if v.varValue>0:\n", " print(v.name, \"=\", v.varValue)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the variables we see in the output are aligned with the expected output. Therefore, our solver is working correctly." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Question 15.2.2 \n", "2. Please add to your model the following constraints (which might require adding more variables) and solve the new model: \n", " \n", " a. If a food is selected, then a minimum of 1/10 serving must be chosen. (Hint: now you will need two variables for each food i: whether it is chosen, and how much is part of the diet. You’ll also need to write a constraint to link them.)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "#a) Additional variables to be created as part of problem 2\n", "#Create variables required for the model\n", "food_chosen = LpVariable.dicts(\"Food_Chosen\",food_items,0,1,cat='Binary')\n", "#Create constraint for the new model\n", "\n", "for i in food_items:\n", " # Sets minimum to 0.1\n", " prob_final += food_vars[i] >= 0.1*food_chosen[i]\n", " \n", " # Ties food value with binary value\n", " prob_final += food_vars[i] <= 9001*food_chosen[i]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "b.) Frozen Broccoli and Raw Celery are mutually exclusive" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "prob_final += food_chosen['Frozen Broccoli'] + food_chosen['Celery, Raw'] == 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "c.) At least 3 proteins required which don't seem ambiguous" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# List of necessary proteins containing either meat/poultry/fish/eggs\n", "proteins = [\n", " 'Bologna,Turkey', 'Frankfurter, Beef','Ham,Sliced,Extralean',\n", " 'Hamburger W/Toppings', 'Hotdog, Plain', 'Kielbasa,Prk',\n", " 'Pizza W/Pepperoni', 'Poached Eggs',\n", " 'Pork', 'Roasted Chicken', 'Sardines in Oil',\n", " 'Scrambled Eggs',\n", " 'Splt Pea&Hamsoup', 'Vegetbeef Soup',\n", " 'White Tuna in Water']" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# Build constraint for protein\n", "prob_final += lpSum([food_chosen[p] for p in proteins]) >= 3" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prob_final.solve()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Food_Celery,_Raw = 38.632607\n", "Food_Chosen_Celery,_Raw = 1.0\n", "Food_Chosen_Kielbasa,Prk = 1.0\n", "Food_Chosen_Lettuce,Iceberg,Raw = 1.0\n", "Food_Chosen_Oranges = 1.0\n", "Food_Chosen_Peanut_Butter = 1.0\n", "Food_Chosen_Poached_Eggs = 1.0\n", "Food_Chosen_Popcorn,Air_Popped = 1.0\n", "Food_Chosen_Scrambled_Eggs = 1.0\n", "Food_Kielbasa,Prk = 0.1\n", "Food_Lettuce,Iceberg,Raw = 86.960747\n", "Food_Oranges = 3.1807762\n", "Food_Peanut_Butter = 2.7388077\n", "Food_Poached_Eggs = 0.1\n", "Food_Popcorn,Air_Popped = 13.083035\n", "Food_Scrambled_Eggs = 0.1\n" ] } ], "source": [ "for v in prob_final.variables():\n", " if v.varValue>0:\n", " print(v.name, \"=\", v.varValue)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Therefore, we've calculated the optimal solution for adding the additional constraints to the existing model. We now have more variables introduced as part of the constraints being added." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Optional Part\n", "If you want to see what a more full-sized problem would look like, try solving your models for the file diet_large.xls, which is a low-cholesterol diet model (rather than minimizing cost, the goal is to minimize cholesterol intake). I don’t know anyone who’d want to eat this diet – the optimal solution includes dried chrysanthemum garland, raw beluga whale flipper, freeze-dried parsley, etc. – which shows why it’s necessary to add additional constraints beyond the basic ones we saw in the video! [Note: there are many optimal solutions, all with zero cholesterol, so you might get a different one. It probably won’t be much more appetizing than mine.]" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Carbohydrate, by difference 1000000\n", "Energy 1000000\n", "Water 1000000\n", "Energy.1 1e+06\n", "Calcium, Ca 2500\n", "Iron, Fe 45\n", "Magnesium, Mg 400\n", "Phosphorus, P 4000\n", "Potassium, K 1000000\n", "Sodium, Na 2300\n", "Zinc, Zn 40\n", "Copper, Cu 10\n", "Manganese, Mn 11\n", "Selenium, Se 400\n", "Vitamin A, RAE 3000\n", "Vitamin E (alpha-tocopherol) 1000\n", "Vitamin D 2000\n", "Vitamin C, total ascorbic acid 2000\n", "Thiamin 1000000\n", "Riboflavin 1000000\n", "Niacin 35\n", "Pantothenic acid 1000000\n", "Vitamin B-6 100\n", "Folate, total 1000\n", "Vitamin B-12 1000000\n", "Vitamin K (phylloquinone) 1000000\n", "Cholesterol NaN\n", "Fatty acids, total trans NaN\n", "Fatty acids, total saturated NaN\n", "Name: 7149, dtype: object\n", "\n", "\n", "Carbohydrate, by difference 130\n", "Energy 2400\n", "Water 3700\n", "Energy.1 2400\n", "Calcium, Ca 1000\n", "Iron, Fe 8\n", "Magnesium, Mg 270\n", "Phosphorus, P 700\n", "Potassium, K 4700\n", "Sodium, Na 1500\n", "Zinc, Zn 11\n", "Copper, Cu 0.9\n", "Manganese, Mn 2.3\n", "Selenium, Se 55\n", "Vitamin A, RAE 900\n", "Vitamin E (alpha-tocopherol) 15\n", "Vitamin D 200\n", "Vitamin C, total ascorbic acid 90\n", "Thiamin 0.0012\n", "Riboflavin 1.3\n", "Niacin 16\n", "Pantothenic acid 5\n", "Vitamin B-6 1.3\n", "Folate, total 400\n", "Vitamin B-12 2.4\n", "Vitamin K (phylloquinone) 120\n", "Cholesterol NaN\n", "Fatty acids, total trans NaN\n", "Fatty acids, total saturated NaN\n", "Name: 7147, dtype: object\n" ] } ], "source": [ "#Build min and max constraints df for the diet_large dataset\n", "max_constraints_large = diet_large.iloc[len(diet_large)-1][2:]\n", "print(max_constraints_large)\n", "print('\\n')\n", "min_constraints_large = diet_large.iloc[len(diet_large)-3][2:]\n", "print(min_constraints_large)\n", "\n", "diet_large_v2 = diet_large.iloc[0:len(diet_large)-4]" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "#Now apply the constraints required for the large dataset\n", "prob_large = LpProblem(\"Military Diet Problem Large Dataset\",LpMinimize)\n", "diet_large_v2\n", "\n", "# Create a list of the food items and dictionary \n", "food_items_large = list(diet_large_v2['Long_Desc'])\n", "nutritional_dict_large = create_input_dictionaries(diet_large_v2,food_items_large)\n", "\n", "#Create variables required for the model\n", "food_vars_large = LpVariable.dicts(\"Food\",food_items_large,lowBound=0,cat='Continuous')\n", "\n", "# Create a dictionary for cholesterol to minimize\n", "cholesterol = dict(zip(food_items_large,diet_large_v2['Cholesterol']))\n", "\n", "#Create the objective function which is meant to contain the \n", "prob_large += lpSum([cholesterol[i]*food_vars_large[i] for i in food_items_large])\n", "\n", "#prob_large\n", "#nutritional_dict_large.keys()\n", "#food_items_large\n", "#prob_large_final = create_constraints(prob_large, nutritional_dict_large)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }