├── .gitignore ├── Balanced_Diet_Problem_Complex.ipynb ├── Balanced_Diet_Problem_Medium.ipynb ├── Balanced_Diet_Problem_Simple.ipynb ├── Data ├── Readme.md ├── diet - medium.xls ├── diet.xls └── monthly_prices.csv ├── LICENSE ├── Portfolio_optimization.ipynb ├── PuLP_practice.ipynb ├── README.md ├── SciPy_optimization.ipynb ├── Scipy-Linear-Programming.ipynb ├── Simulation-interpolation.ipynb └── images ├── Markowitz_quote.jpeg ├── Readme.md └── Simu-interpolate-Header.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Balanced_Diet_Problem_Complex.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "from pulp import *" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "### Read the given nutrition dataset into a Pandas DataFrame object\n", 18 | "Note we are reading only the first 64 rows with `nrows=64` argument because we just want to read all the nutrients informtion and not the maximum/minimum bounds in the dataset. We will enter those bounds in the optimization problem separately." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "df = pd.read_excel(\"diet.xls\",nrows=64)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "### Show first 5 rows of the dataset" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 3, 40 | "metadata": {}, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "text/html": [ 45 | "
\n", 46 | "\n", 59 | "\n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | "
FoodsPrice/ ServingServing SizeCaloriesCholesterol mgTotal_Fat gSodium mgCarbohydrates gDietary_Fiber gProtein gVit_A IUVit_C IUCalcium mgIron mg
0Frozen Broccoli0.1610 Oz Pkg73.80.00.868.213.68.58.05867.4160.2159.02.3
1Carrots,Raw0.071/2 Cup Shredded23.70.00.119.25.61.60.615471.05.114.90.3
2Celery, Raw0.041 Stalk6.40.00.134.81.50.70.353.62.816.00.2
3Frozen Corn0.181/2 Cup72.20.00.62.517.12.02.5106.65.23.30.3
4Lettuce,Iceberg,Raw0.021 Leaf2.60.00.01.80.40.30.266.00.83.80.1
\n", 167 | "
" 168 | ], 169 | "text/plain": [ 170 | " Foods Price/ Serving Serving Size Calories \\\n", 171 | "0 Frozen Broccoli 0.16 10 Oz Pkg 73.8 \n", 172 | "1 Carrots,Raw 0.07 1/2 Cup Shredded 23.7 \n", 173 | "2 Celery, Raw 0.04 1 Stalk 6.4 \n", 174 | "3 Frozen Corn 0.18 1/2 Cup 72.2 \n", 175 | "4 Lettuce,Iceberg,Raw 0.02 1 Leaf 2.6 \n", 176 | "\n", 177 | " Cholesterol mg Total_Fat g Sodium mg Carbohydrates g Dietary_Fiber g \\\n", 178 | "0 0.0 0.8 68.2 13.6 8.5 \n", 179 | "1 0.0 0.1 19.2 5.6 1.6 \n", 180 | "2 0.0 0.1 34.8 1.5 0.7 \n", 181 | "3 0.0 0.6 2.5 17.1 2.0 \n", 182 | "4 0.0 0.0 1.8 0.4 0.3 \n", 183 | "\n", 184 | " Protein g Vit_A IU Vit_C IU Calcium mg Iron mg \n", 185 | "0 8.0 5867.4 160.2 159.0 2.3 \n", 186 | "1 0.6 15471.0 5.1 14.9 0.3 \n", 187 | "2 0.3 53.6 2.8 16.0 0.2 \n", 188 | "3 2.5 106.6 5.2 3.3 0.3 \n", 189 | "4 0.2 66.0 0.8 3.8 0.1 " 190 | ] 191 | }, 192 | "execution_count": 3, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "df.head()" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "### Create the `PuLP` problem variable. Since it is a cost minimization problem, we need to use `LpMinimize`" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 4, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "# Create the 'prob' variable to contain the problem data\n", 215 | "prob = LpProblem(\"Simple Diet Problem\",LpMinimize)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "### Create a list of food items from the dataset" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": 5, 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [ 231 | "# Creates a list of the Ingredients\n", 232 | "food_items = list(df['Foods'])" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 6, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "So, the food items to consdier, are\n", 245 | "----------------------------------------------------------------------------------------------------\n", 246 | "Frozen Broccoli, Carrots,Raw, Celery, Raw, Frozen Corn, Lettuce,Iceberg,Raw, Peppers, Sweet, Raw, Potatoes, Baked, Tofu, Roasted Chicken, Spaghetti W/ Sauce, Tomato,Red,Ripe,Raw, Apple,Raw,W/Skin, Banana, Grapes, Kiwifruit,Raw,Fresh, Oranges, Bagels, Wheat Bread, White Bread, Oatmeal Cookies, Apple Pie, Chocolate Chip Cookies, Butter,Regular, Cheddar Cheese, 3.3% Fat,Whole Milk, 2% Lowfat Milk, Skim Milk, Poached Eggs, Scrambled Eggs, Bologna,Turkey, Frankfurter, Beef, Ham,Sliced,Extralean, Kielbasa,Prk, Cap'N Crunch, Cheerios, Corn Flks, Kellogg'S, Raisin Brn, Kellg'S, Rice Krispies, Special K, Oatmeal, Malt-O-Meal,Choc, Pizza W/Pepperoni, Taco, Hamburger W/Toppings, Hotdog, Plain, Couscous, White Rice, Macaroni,Ckd, Peanut Butter, Pork, Sardines in Oil, White Tuna in Water, Popcorn,Air-Popped, Potato Chips,Bbqflvr, Pretzels, Tortilla Chip, Chicknoodl Soup, Splt Pea&Hamsoup, Vegetbeef Soup, Neweng Clamchwd, Tomato Soup, New E Clamchwd,W/Mlk, Crm Mshrm Soup,W/Mlk, Beanbacn Soup,W/Watr, " 247 | ] 248 | } 249 | ], 250 | "source": [ 251 | "print(\"So, the food items to consdier, are\\n\"+\"-\"*100)\n", 252 | "for f in food_items:\n", 253 | " print(f,end=', ')" 254 | ] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "metadata": {}, 259 | "source": [ 260 | "### Create a dictinary of costs for all food items" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 7, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "costs = dict(zip(food_items,df['Price/ Serving']))" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 8, 275 | "metadata": {}, 276 | "outputs": [ 277 | { 278 | "data": { 279 | "text/plain": [ 280 | "{'2% Lowfat Milk': 0.23,\n", 281 | " '3.3% Fat,Whole Milk': 0.16,\n", 282 | " 'Apple Pie': 0.16,\n", 283 | " 'Apple,Raw,W/Skin': 0.24,\n", 284 | " 'Bagels': 0.16,\n", 285 | " 'Banana': 0.15,\n", 286 | " 'Beanbacn Soup,W/Watr': 0.67,\n", 287 | " 'Bologna,Turkey': 0.15,\n", 288 | " 'Butter,Regular': 0.05,\n", 289 | " \"Cap'N Crunch\": 0.31,\n", 290 | " 'Carrots,Raw': 0.07,\n", 291 | " 'Celery, Raw': 0.04,\n", 292 | " 'Cheddar Cheese': 0.25,\n", 293 | " 'Cheerios': 0.28,\n", 294 | " 'Chicknoodl Soup': 0.39,\n", 295 | " 'Chocolate Chip Cookies': 0.03,\n", 296 | " \"Corn Flks, Kellogg'S\": 0.28,\n", 297 | " 'Couscous': 0.39,\n", 298 | " 'Crm Mshrm Soup,W/Mlk': 0.65,\n", 299 | " 'Frankfurter, Beef': 0.27,\n", 300 | " 'Frozen Broccoli': 0.16,\n", 301 | " 'Frozen Corn': 0.18,\n", 302 | " 'Grapes': 0.32,\n", 303 | " 'Ham,Sliced,Extralean': 0.33,\n", 304 | " 'Hamburger W/Toppings': 0.83,\n", 305 | " 'Hotdog, Plain': 0.31,\n", 306 | " 'Kielbasa,Prk': 0.15,\n", 307 | " 'Kiwifruit,Raw,Fresh': 0.49,\n", 308 | " 'Lettuce,Iceberg,Raw': 0.02,\n", 309 | " 'Macaroni,Ckd': 0.17,\n", 310 | " 'Malt-O-Meal,Choc': 0.52,\n", 311 | " 'New E Clamchwd,W/Mlk': 0.99,\n", 312 | " 'Neweng Clamchwd': 0.75,\n", 313 | " 'Oatmeal': 0.82,\n", 314 | " 'Oatmeal Cookies': 0.09,\n", 315 | " 'Oranges': 0.15,\n", 316 | " 'Peanut Butter': 0.07,\n", 317 | " 'Peppers, Sweet, Raw': 0.53,\n", 318 | " 'Pizza W/Pepperoni': 0.44,\n", 319 | " 'Poached Eggs': 0.08,\n", 320 | " 'Popcorn,Air-Popped': 0.04,\n", 321 | " 'Pork': 0.81,\n", 322 | " 'Potato Chips,Bbqflvr': 0.22,\n", 323 | " 'Potatoes, Baked': 0.06,\n", 324 | " 'Pretzels': 0.12,\n", 325 | " \"Raisin Brn, Kellg'S\": 0.34,\n", 326 | " 'Rice Krispies': 0.32,\n", 327 | " 'Roasted Chicken': 0.84,\n", 328 | " 'Sardines in Oil': 0.45,\n", 329 | " 'Scrambled Eggs': 0.11,\n", 330 | " 'Skim Milk': 0.13,\n", 331 | " 'Spaghetti W/ Sauce': 0.78,\n", 332 | " 'Special K': 0.38,\n", 333 | " 'Splt Pea&Hamsoup': 0.67,\n", 334 | " 'Taco': 0.59,\n", 335 | " 'Tofu': 0.31,\n", 336 | " 'Tomato Soup': 0.39,\n", 337 | " 'Tomato,Red,Ripe,Raw': 0.27,\n", 338 | " 'Tortilla Chip': 0.19,\n", 339 | " 'Vegetbeef Soup': 0.71,\n", 340 | " 'Wheat Bread': 0.05,\n", 341 | " 'White Bread': 0.06,\n", 342 | " 'White Rice': 0.08,\n", 343 | " 'White Tuna in Water': 0.69}" 344 | ] 345 | }, 346 | "execution_count": 8, 347 | "metadata": {}, 348 | "output_type": "execute_result" 349 | } 350 | ], 351 | "source": [ 352 | "costs" 353 | ] 354 | }, 355 | { 356 | "cell_type": "markdown", 357 | "metadata": {}, 358 | "source": [ 359 | "### Create a dictionary of calories for all food items" 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": 9, 365 | "metadata": {}, 366 | "outputs": [], 367 | "source": [ 368 | "calories = dict(zip(food_items,df['Calories']))" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "metadata": {}, 374 | "source": [ 375 | "### Create a dictionary of cholesterol for all food items" 376 | ] 377 | }, 378 | { 379 | "cell_type": "code", 380 | "execution_count": 10, 381 | "metadata": {}, 382 | "outputs": [], 383 | "source": [ 384 | "cholesterol = dict(zip(food_items,df['Cholesterol mg']))" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "metadata": {}, 390 | "source": [ 391 | "### Create a dictionary of total fat for all food items" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 11, 397 | "metadata": {}, 398 | "outputs": [], 399 | "source": [ 400 | "fat = dict(zip(food_items,df['Total_Fat g']))" 401 | ] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "metadata": {}, 406 | "source": [ 407 | "### Create a dictionary of sodium for all food items" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 12, 413 | "metadata": {}, 414 | "outputs": [], 415 | "source": [ 416 | "sodium = dict(zip(food_items,df['Sodium mg']))" 417 | ] 418 | }, 419 | { 420 | "cell_type": "markdown", 421 | "metadata": {}, 422 | "source": [ 423 | "### Create a dictionary of carbohydrates for all food items" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 13, 429 | "metadata": {}, 430 | "outputs": [], 431 | "source": [ 432 | "carbs = dict(zip(food_items,df['Carbohydrates g']))" 433 | ] 434 | }, 435 | { 436 | "cell_type": "markdown", 437 | "metadata": {}, 438 | "source": [ 439 | "### Create a dictionary of dietary fiber for all food items" 440 | ] 441 | }, 442 | { 443 | "cell_type": "code", 444 | "execution_count": 14, 445 | "metadata": {}, 446 | "outputs": [], 447 | "source": [ 448 | "fiber = dict(zip(food_items,df['Dietary_Fiber g']))" 449 | ] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "metadata": {}, 454 | "source": [ 455 | "### Create a dictionary of protein for all food items" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 15, 461 | "metadata": {}, 462 | "outputs": [], 463 | "source": [ 464 | "protein = dict(zip(food_items,df['Protein g']))" 465 | ] 466 | }, 467 | { 468 | "cell_type": "markdown", 469 | "metadata": {}, 470 | "source": [ 471 | "### Create a dictionary of vitamin A for all food items" 472 | ] 473 | }, 474 | { 475 | "cell_type": "code", 476 | "execution_count": 16, 477 | "metadata": {}, 478 | "outputs": [], 479 | "source": [ 480 | "vit_A = dict(zip(food_items,df['Vit_A IU']))" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": {}, 486 | "source": [ 487 | "### Create a dictionary of vitamin C for all food items" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 17, 493 | "metadata": {}, 494 | "outputs": [], 495 | "source": [ 496 | "vit_C = dict(zip(food_items,df['Vit_C IU']))" 497 | ] 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "metadata": {}, 502 | "source": [ 503 | "### Create a dictionary of calcium for all food items" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": 18, 509 | "metadata": {}, 510 | "outputs": [], 511 | "source": [ 512 | "calcium = dict(zip(food_items,df['Calcium mg']))" 513 | ] 514 | }, 515 | { 516 | "cell_type": "markdown", 517 | "metadata": {}, 518 | "source": [ 519 | "### Create a dictionary of iron for all food items" 520 | ] 521 | }, 522 | { 523 | "cell_type": "code", 524 | "execution_count": 19, 525 | "metadata": {}, 526 | "outputs": [], 527 | "source": [ 528 | "iron = dict(zip(food_items,df['Iron mg']))" 529 | ] 530 | }, 531 | { 532 | "cell_type": "markdown", 533 | "metadata": {}, 534 | "source": [ 535 | "### Create a dictionary of food portion with lower bound 0 - these are the main optimization variables" 536 | ] 537 | }, 538 | { 539 | "cell_type": "code", 540 | "execution_count": 20, 541 | "metadata": {}, 542 | "outputs": [], 543 | "source": [ 544 | "# A dictionary called 'food_vars' is created to contain the referenced Variables\n", 545 | "food_vars = LpVariable.dicts(\"Portion\",food_items,0,cat='Continuous')" 546 | ] 547 | }, 548 | { 549 | "cell_type": "markdown", 550 | "metadata": {}, 551 | "source": [ 552 | "### Create another set of variables for each food, with integer 0 or 1. These are indicator variables" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": 21, 558 | "metadata": {}, 559 | "outputs": [], 560 | "source": [ 561 | "# A dictionary called 'food_vars' is created to contain the referenced Variables\n", 562 | "food_chosen = LpVariable.dicts(\"Chosen\",food_items,0,1,cat='Integer')" 563 | ] 564 | }, 565 | { 566 | "cell_type": "markdown", 567 | "metadata": {}, 568 | "source": [ 569 | "### Adding the objective function to the problem" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": 22, 575 | "metadata": {}, 576 | "outputs": [], 577 | "source": [ 578 | "# The objective function is added to 'prob' first\n", 579 | "prob += lpSum([costs[i]*food_vars[i] for i in food_items]), \"Total Cost of the balanced diet\"" 580 | ] 581 | }, 582 | { 583 | "cell_type": "markdown", 584 | "metadata": {}, 585 | "source": [ 586 | "### Adding the calorie constraints to the problem" 587 | ] 588 | }, 589 | { 590 | "cell_type": "code", 591 | "execution_count": 23, 592 | "metadata": {}, 593 | "outputs": [], 594 | "source": [ 595 | "prob += lpSum([calories[f] * food_vars[f] for f in food_items]) >= 1500.0, \"CalorieMinimum\"\n", 596 | "prob += lpSum([calories[f] * food_vars[f] for f in food_items]) <= 2500.0, \"CalorieMaximum\"" 597 | ] 598 | }, 599 | { 600 | "cell_type": "markdown", 601 | "metadata": {}, 602 | "source": [ 603 | "### Adding other nutrient constraints to the problem one by one..." 604 | ] 605 | }, 606 | { 607 | "cell_type": "code", 608 | "execution_count": 24, 609 | "metadata": {}, 610 | "outputs": [], 611 | "source": [ 612 | "# Cholesterol\n", 613 | "prob += lpSum([cholesterol[f] * food_vars[f] for f in food_items]) >= 30.0, \"CholesterolMinimum\"\n", 614 | "prob += lpSum([cholesterol[f] * food_vars[f] for f in food_items]) <= 240.0, \"CholesterolMaximum\"\n", 615 | "\n", 616 | "# Fat\n", 617 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) >= 20.0, \"FatMinimum\"\n", 618 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) <= 70.0, \"FatMaximum\"\n", 619 | "\n", 620 | "# Sodium\n", 621 | "prob += lpSum([sodium[f] * food_vars[f] for f in food_items]) >= 800.0, \"SodiumMinimum\"\n", 622 | "prob += lpSum([sodium[f] * food_vars[f] for f in food_items]) <= 2000.0, \"SodiumMaximum\"\n", 623 | "\n", 624 | "# Carbs\n", 625 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) >= 130.0, \"CarbsMinimum\"\n", 626 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) <= 450.0, \"CarbsMaximum\"\n", 627 | "\n", 628 | "# Fiber\n", 629 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) >= 125.0, \"FiberMinimum\"\n", 630 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) <= 250.0, \"FiberMaximum\"\n", 631 | "\n", 632 | "# Protein\n", 633 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) >= 60.0, \"ProteinMinimum\"\n", 634 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) <= 100.0, \"ProteinMaximum\"\n", 635 | "\n", 636 | "# Vitamin A\n", 637 | "prob += lpSum([vit_A[f] * food_vars[f] for f in food_items]) >= 1000.0, \"VitaminAMinimum\"\n", 638 | "prob += lpSum([vit_A[f] * food_vars[f] for f in food_items]) <= 10000.0, \"VitaminAMaximum\"\n", 639 | "\n", 640 | "# Vitamin C\n", 641 | "prob += lpSum([vit_C[f] * food_vars[f] for f in food_items]) >= 400.0, \"VitaminCMinimum\"\n", 642 | "prob += lpSum([vit_C[f] * food_vars[f] for f in food_items]) <= 5000.0, \"VitaminCMaximum\"\n", 643 | "\n", 644 | "# Calcium\n", 645 | "prob += lpSum([calcium[f] * food_vars[f] for f in food_items]) >= 700.0, \"CalciumMinimum\"\n", 646 | "prob += lpSum([calcium[f] * food_vars[f] for f in food_items]) <= 1500.0, \"CalciumMaximum\"\n", 647 | "\n", 648 | "# Iron\n", 649 | "prob += lpSum([iron[f] * food_vars[f] for f in food_items]) >= 10.0, \"IronMinimum\"\n", 650 | "prob += lpSum([iron[f] * food_vars[f] for f in food_items]) <= 40.0, \"IronMaximum\"" 651 | ] 652 | }, 653 | { 654 | "cell_type": "markdown", 655 | "metadata": {}, 656 | "source": [ 657 | "### Adding constraint linking `food_vars` and `food_chosen`" 658 | ] 659 | }, 660 | { 661 | "cell_type": "code", 662 | "execution_count": 25, 663 | "metadata": {}, 664 | "outputs": [], 665 | "source": [ 666 | "for f in food_items:\n", 667 | " prob += food_vars[f]>= food_chosen[f]*0.1\n", 668 | " prob += food_vars[f]<= food_chosen[f]*1e5" 669 | ] 670 | }, 671 | { 672 | "cell_type": "markdown", 673 | "metadata": {}, 674 | "source": [ 675 | "### Adding constraint of celery and frozen broccoli" 676 | ] 677 | }, 678 | { 679 | "cell_type": "code", 680 | "execution_count": 26, 681 | "metadata": {}, 682 | "outputs": [], 683 | "source": [ 684 | "prob += food_chosen['Frozen Broccoli']+food_chosen['Celery, Raw']<=1" 685 | ] 686 | }, 687 | { 688 | "cell_type": "markdown", 689 | "metadata": {}, 690 | "source": [ 691 | "### Adding constraint of at least 3 types of meat/poultry/fish/eggs in every diet" 692 | ] 693 | }, 694 | { 695 | "cell_type": "code", 696 | "execution_count": 27, 697 | "metadata": {}, 698 | "outputs": [], 699 | "source": [ 700 | "protein_choices = ['Beanbacn Soup,W/Watr','Bologna,Turkey','Frankfurter, Beef','Ham,Sliced,Extralean',\n", 701 | " 'Hamburger W/Toppings','Hotdog, Plain','Kielbasa,Prk','Neweng Clamchwd','Pizza W/Pepperoni',\n", 702 | " 'Poached Eggs','Pork','Roasted Chicken','Sardines in Oil','Scrambled Eggs','Vegetbeef Soup',\n", 703 | " 'White Tuna in Water']" 704 | ] 705 | }, 706 | { 707 | "cell_type": "code", 708 | "execution_count": 28, 709 | "metadata": {}, 710 | "outputs": [], 711 | "source": [ 712 | "prob += lpSum([food_chosen[p] for p in protein_choices]) >= 3.0" 713 | ] 714 | }, 715 | { 716 | "cell_type": "markdown", 717 | "metadata": {}, 718 | "source": [ 719 | "### Writing problem data to a `.lp` file" 720 | ] 721 | }, 722 | { 723 | "cell_type": "code", 724 | "execution_count": 29, 725 | "metadata": {}, 726 | "outputs": [], 727 | "source": [ 728 | "# The problem data is written to an .lp file\n", 729 | "prob.writeLP(\"SimpleDietProblem.lp\")" 730 | ] 731 | }, 732 | { 733 | "cell_type": "markdown", 734 | "metadata": {}, 735 | "source": [ 736 | "### Run the solver" 737 | ] 738 | }, 739 | { 740 | "cell_type": "code", 741 | "execution_count": 30, 742 | "metadata": {}, 743 | "outputs": [ 744 | { 745 | "data": { 746 | "text/plain": [ 747 | "1" 748 | ] 749 | }, 750 | "execution_count": 30, 751 | "metadata": {}, 752 | "output_type": "execute_result" 753 | } 754 | ], 755 | "source": [ 756 | "# The problem is solved using PuLP's choice of Solver\n", 757 | "prob.solve()" 758 | ] 759 | }, 760 | { 761 | "cell_type": "markdown", 762 | "metadata": {}, 763 | "source": [ 764 | "### Print the problem solution status `'optimal'`, `'infeasible'`, `'unbounded'` etc..." 765 | ] 766 | }, 767 | { 768 | "cell_type": "code", 769 | "execution_count": 31, 770 | "metadata": {}, 771 | "outputs": [ 772 | { 773 | "name": "stdout", 774 | "output_type": "stream", 775 | "text": [ 776 | "Status: Optimal\n" 777 | ] 778 | } 779 | ], 780 | "source": [ 781 | "# The status of the solution is printed to the screen\n", 782 | "print(\"Status:\", LpStatus[prob.status])" 783 | ] 784 | }, 785 | { 786 | "cell_type": "markdown", 787 | "metadata": {}, 788 | "source": [ 789 | "### Scan through the problem variables and print out only if the variable quanity is positive i.e. it is included in the optimal solution" 790 | ] 791 | }, 792 | { 793 | "cell_type": "code", 794 | "execution_count": 39, 795 | "metadata": {}, 796 | "outputs": [ 797 | { 798 | "name": "stdout", 799 | "output_type": "stream", 800 | "text": [ 801 | "The optimal (least cost) balanced diet with additional constraints (e.g. at least 3 types of animal protein sources, consists of\n", 802 | "--------------------------------------------------------------------------------------------------------------\n", 803 | "Portion_Celery,_Raw = 42.399358\n", 804 | "Portion_Kielbasa,Prk = 0.1\n", 805 | "Portion_Lettuce,Iceberg,Raw = 82.802586\n", 806 | "Portion_Oranges = 3.0771841\n", 807 | "Portion_Peanut_Butter = 1.9429716\n", 808 | "Portion_Poached_Eggs = 0.1\n", 809 | "Portion_Popcorn,Air_Popped = 13.223294\n", 810 | "Portion_Scrambled_Eggs = 0.1\n" 811 | ] 812 | } 813 | ], 814 | "source": [ 815 | "print(\"The optimal (least cost) balanced diet with additional constraints \\\n", 816 | "(e.g. at least 3 types of animal protein sources, consists of\\n\"+\"-\"*110)\n", 817 | "for v in prob.variables():\n", 818 | " if v.varValue>0 and v.name[0]=='P':\n", 819 | " print(v.name, \"=\", v.varValue)" 820 | ] 821 | }, 822 | { 823 | "cell_type": "markdown", 824 | "metadata": {}, 825 | "source": [ 826 | "### Print the optimal diet cost" 827 | ] 828 | }, 829 | { 830 | "cell_type": "code", 831 | "execution_count": 33, 832 | "metadata": {}, 833 | "outputs": [ 834 | { 835 | "name": "stdout", 836 | "output_type": "stream", 837 | "text": [ 838 | "The total cost of this balanced diet is: $4.51\n" 839 | ] 840 | } 841 | ], 842 | "source": [ 843 | "print(\"The total cost of this balanced diet is: ${}\".format(round(value(prob.objective),2)))" 844 | ] 845 | } 846 | ], 847 | "metadata": { 848 | "kernelspec": { 849 | "display_name": "Python 3", 850 | "language": "python", 851 | "name": "python3" 852 | }, 853 | "language_info": { 854 | "codemirror_mode": { 855 | "name": "ipython", 856 | "version": 3 857 | }, 858 | "file_extension": ".py", 859 | "mimetype": "text/x-python", 860 | "name": "python", 861 | "nbconvert_exporter": "python", 862 | "pygments_lexer": "ipython3", 863 | "version": "3.6.2" 864 | }, 865 | "latex_envs": { 866 | "LaTeX_envs_menu_present": true, 867 | "autoclose": false, 868 | "autocomplete": true, 869 | "bibliofile": "biblio.bib", 870 | "cite_by": "apalike", 871 | "current_citInitial": 1, 872 | "eqLabelWithNumbers": true, 873 | "eqNumInitial": 1, 874 | "hotkeys": { 875 | "equation": "Ctrl-E", 876 | "itemize": "Ctrl-I" 877 | }, 878 | "labels_anchors": false, 879 | "latex_user_defs": false, 880 | "report_style_numbering": false, 881 | "user_envs_cfg": false 882 | } 883 | }, 884 | "nbformat": 4, 885 | "nbformat_minor": 2 886 | } 887 | -------------------------------------------------------------------------------- /Balanced_Diet_Problem_Medium.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 38, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "from pulp import *" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "### Read the given nutrition dataset into a Pandas DataFrame object\n", 18 | "Note we are reading only the first 64 rows with `nrows=64` argument because we just want to read all the nutrients informtion and not the maximum/minimum bounds in the dataset. We will enter those bounds in the optimization problem separately." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 39, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "df = pd.read_excel(\"diet - medium.xls\",nrows=17)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "### Show the dataset" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 40, 40 | "metadata": {}, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "text/html": [ 45 | "
\n", 46 | "\n", 59 | "\n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | "
FoodsPrice/ServingServing SizeCaloriesCholesterol (mg)Total_Fat (g)Sodium (mg)Carbohydrates (g)Dietary_Fiber (g)Protein (g)Vit_A (IU)Vit_C (IU)Calcium (mg)Iron (mg)
0Frozen Broccoli0.4810 Oz Pkg73.80.00.868.213.68.58.05867.4160.2159.02.3
1Frozen Corn0.541/2 Cup72.20.00.62.517.12.02.5106.65.23.30.3
2Raw Lettuce Iceberg0.061 Leaf2.60.00.01.80.40.30.266.00.83.80.1
3Baked Potatoes0.181/2 Cup171.50.00.215.239.93.23.70.015.622.74.3
4Tofu0.931/4 block88.20.05.58.12.21.49.498.60.1121.86.2
5Roasted Chicken2.521 lb chicken277.4129.910.8125.60.00.042.277.40.021.91.8
6Spaghetti W/ Sauce2.341 1/2 Cup358.20.012.31237.158.311.68.23055.227.980.22.3
7Raw Apple0.721 Fruit,3/Lb,Wo/Rf81.40.00.50.021.03.70.373.17.99.70.2
8Banana0.451 Fruit,Wo/Skn&Seeds104.90.00.51.126.72.71.292.310.46.80.4
9Wheat Bread0.151 Sl65.00.01.0134.512.41.32.20.00.010.80.7
10White Bread0.181 Sl65.00.01.0132.511.81.12.30.00.026.20.8
11Oatmeal Cookies0.271 Cookie81.00.03.368.912.40.61.12.90.16.70.5
12Apple Pie0.481 Oz67.20.03.175.49.60.50.535.20.93.10.1
13Scrambled Eggs0.331 Egg99.6211.27.3168.01.30.06.7409.20.142.60.7
14Turkey Bologna0.451 Oz56.428.14.3248.90.30.03.90.00.023.80.4
15Beef Frankfurter0.811 Frankfurter141.827.412.8461.70.80.05.40.010.89.00.6
16Chocolate Chip Cookies0.091 Cookie78.15.14.557.89.30.00.9101.80.06.20.4
\n", 371 | "
" 372 | ], 373 | "text/plain": [ 374 | " Foods Price/Serving Serving Size Calories \\\n", 375 | "0 Frozen Broccoli 0.48 10 Oz Pkg 73.8 \n", 376 | "1 Frozen Corn 0.54 1/2 Cup 72.2 \n", 377 | "2 Raw Lettuce Iceberg 0.06 1 Leaf 2.6 \n", 378 | "3 Baked Potatoes 0.18 1/2 Cup 171.5 \n", 379 | "4 Tofu 0.93 1/4 block 88.2 \n", 380 | "5 Roasted Chicken 2.52 1 lb chicken 277.4 \n", 381 | "6 Spaghetti W/ Sauce 2.34 1 1/2 Cup 358.2 \n", 382 | "7 Raw Apple 0.72 1 Fruit,3/Lb,Wo/Rf 81.4 \n", 383 | "8 Banana 0.45 1 Fruit,Wo/Skn&Seeds 104.9 \n", 384 | "9 Wheat Bread 0.15 1 Sl 65.0 \n", 385 | "10 White Bread 0.18 1 Sl 65.0 \n", 386 | "11 Oatmeal Cookies 0.27 1 Cookie 81.0 \n", 387 | "12 Apple Pie 0.48 1 Oz 67.2 \n", 388 | "13 Scrambled Eggs 0.33 1 Egg 99.6 \n", 389 | "14 Turkey Bologna 0.45 1 Oz 56.4 \n", 390 | "15 Beef Frankfurter 0.81 1 Frankfurter 141.8 \n", 391 | "16 Chocolate Chip Cookies 0.09 1 Cookie 78.1 \n", 392 | "\n", 393 | " Cholesterol (mg) Total_Fat (g) Sodium (mg) Carbohydrates (g) \\\n", 394 | "0 0.0 0.8 68.2 13.6 \n", 395 | "1 0.0 0.6 2.5 17.1 \n", 396 | "2 0.0 0.0 1.8 0.4 \n", 397 | "3 0.0 0.2 15.2 39.9 \n", 398 | "4 0.0 5.5 8.1 2.2 \n", 399 | "5 129.9 10.8 125.6 0.0 \n", 400 | "6 0.0 12.3 1237.1 58.3 \n", 401 | "7 0.0 0.5 0.0 21.0 \n", 402 | "8 0.0 0.5 1.1 26.7 \n", 403 | "9 0.0 1.0 134.5 12.4 \n", 404 | "10 0.0 1.0 132.5 11.8 \n", 405 | "11 0.0 3.3 68.9 12.4 \n", 406 | "12 0.0 3.1 75.4 9.6 \n", 407 | "13 211.2 7.3 168.0 1.3 \n", 408 | "14 28.1 4.3 248.9 0.3 \n", 409 | "15 27.4 12.8 461.7 0.8 \n", 410 | "16 5.1 4.5 57.8 9.3 \n", 411 | "\n", 412 | " Dietary_Fiber (g) Protein (g) Vit_A (IU) Vit_C (IU) Calcium (mg) \\\n", 413 | "0 8.5 8.0 5867.4 160.2 159.0 \n", 414 | "1 2.0 2.5 106.6 5.2 3.3 \n", 415 | "2 0.3 0.2 66.0 0.8 3.8 \n", 416 | "3 3.2 3.7 0.0 15.6 22.7 \n", 417 | "4 1.4 9.4 98.6 0.1 121.8 \n", 418 | "5 0.0 42.2 77.4 0.0 21.9 \n", 419 | "6 11.6 8.2 3055.2 27.9 80.2 \n", 420 | "7 3.7 0.3 73.1 7.9 9.7 \n", 421 | "8 2.7 1.2 92.3 10.4 6.8 \n", 422 | "9 1.3 2.2 0.0 0.0 10.8 \n", 423 | "10 1.1 2.3 0.0 0.0 26.2 \n", 424 | "11 0.6 1.1 2.9 0.1 6.7 \n", 425 | "12 0.5 0.5 35.2 0.9 3.1 \n", 426 | "13 0.0 6.7 409.2 0.1 42.6 \n", 427 | "14 0.0 3.9 0.0 0.0 23.8 \n", 428 | "15 0.0 5.4 0.0 10.8 9.0 \n", 429 | "16 0.0 0.9 101.8 0.0 6.2 \n", 430 | "\n", 431 | " Iron (mg) \n", 432 | "0 2.3 \n", 433 | "1 0.3 \n", 434 | "2 0.1 \n", 435 | "3 4.3 \n", 436 | "4 6.2 \n", 437 | "5 1.8 \n", 438 | "6 2.3 \n", 439 | "7 0.2 \n", 440 | "8 0.4 \n", 441 | "9 0.7 \n", 442 | "10 0.8 \n", 443 | "11 0.5 \n", 444 | "12 0.1 \n", 445 | "13 0.7 \n", 446 | "14 0.4 \n", 447 | "15 0.6 \n", 448 | "16 0.4 " 449 | ] 450 | }, 451 | "execution_count": 40, 452 | "metadata": {}, 453 | "output_type": "execute_result" 454 | } 455 | ], 456 | "source": [ 457 | "df" 458 | ] 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "metadata": {}, 463 | "source": [ 464 | "### Create the `PuLP` problem variable. Since it is a cost minimization problem, we need to use `LpMinimize`" 465 | ] 466 | }, 467 | { 468 | "cell_type": "code", 469 | "execution_count": 166, 470 | "metadata": {}, 471 | "outputs": [], 472 | "source": [ 473 | "# Create the 'prob' variable to contain the problem data\n", 474 | "prob = LpProblem(\"Simple Diet Problem\",LpMinimize)" 475 | ] 476 | }, 477 | { 478 | "cell_type": "markdown", 479 | "metadata": {}, 480 | "source": [ 481 | "### Create a list of food items from the dataset" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": 167, 487 | "metadata": {}, 488 | "outputs": [], 489 | "source": [ 490 | "# Creates a list of the Ingredients\n", 491 | "food_items = list(df['Foods'])" 492 | ] 493 | }, 494 | { 495 | "cell_type": "code", 496 | "execution_count": 168, 497 | "metadata": {}, 498 | "outputs": [ 499 | { 500 | "name": "stdout", 501 | "output_type": "stream", 502 | "text": [ 503 | "So, the food items to consdier, are\n", 504 | "----------------------------------------------------------------------------------------------------\n", 505 | "Frozen Broccoli, Frozen Corn, Raw Lettuce Iceberg, Baked Potatoes, Tofu, Roasted Chicken, Spaghetti W/ Sauce, Raw Apple, Banana, Wheat Bread, White Bread, Oatmeal Cookies, Apple Pie, Scrambled Eggs, Turkey Bologna, Beef Frankfurter, Chocolate Chip Cookies, " 506 | ] 507 | } 508 | ], 509 | "source": [ 510 | "print(\"So, the food items to consdier, are\\n\"+\"-\"*100)\n", 511 | "for f in food_items:\n", 512 | " print(f,end=', ')" 513 | ] 514 | }, 515 | { 516 | "cell_type": "markdown", 517 | "metadata": {}, 518 | "source": [ 519 | "### Create a dictinary of costs for all food items" 520 | ] 521 | }, 522 | { 523 | "cell_type": "code", 524 | "execution_count": 169, 525 | "metadata": {}, 526 | "outputs": [], 527 | "source": [ 528 | "costs = dict(zip(food_items,df['Price/Serving']))" 529 | ] 530 | }, 531 | { 532 | "cell_type": "code", 533 | "execution_count": 170, 534 | "metadata": {}, 535 | "outputs": [ 536 | { 537 | "data": { 538 | "text/plain": [ 539 | "{' Baked Potatoes': 0.18,\n", 540 | " 'Apple Pie': 0.48,\n", 541 | " 'Banana': 0.44999999999999996,\n", 542 | " 'Beef Frankfurter': 0.81,\n", 543 | " 'Chocolate Chip Cookies': 0.09,\n", 544 | " 'Frozen Broccoli': 0.48,\n", 545 | " 'Frozen Corn': 0.54,\n", 546 | " 'Oatmeal Cookies': 0.27,\n", 547 | " 'Raw Apple': 0.72,\n", 548 | " 'Raw Lettuce Iceberg': 0.06,\n", 549 | " 'Roasted Chicken': 2.52,\n", 550 | " 'Scrambled Eggs': 0.33,\n", 551 | " 'Spaghetti W/ Sauce': 2.34,\n", 552 | " 'Tofu': 0.9299999999999999,\n", 553 | " 'Turkey Bologna': 0.44999999999999996,\n", 554 | " 'Wheat Bread': 0.15000000000000002,\n", 555 | " 'White Bread': 0.18}" 556 | ] 557 | }, 558 | "execution_count": 170, 559 | "metadata": {}, 560 | "output_type": "execute_result" 561 | } 562 | ], 563 | "source": [ 564 | "costs" 565 | ] 566 | }, 567 | { 568 | "cell_type": "markdown", 569 | "metadata": {}, 570 | "source": [ 571 | "### Create a dictionary of calories for all food items" 572 | ] 573 | }, 574 | { 575 | "cell_type": "code", 576 | "execution_count": 171, 577 | "metadata": {}, 578 | "outputs": [], 579 | "source": [ 580 | "calories = dict(zip(food_items,df['Calories']))" 581 | ] 582 | }, 583 | { 584 | "cell_type": "markdown", 585 | "metadata": {}, 586 | "source": [ 587 | "### Create a dictionary of cholesterol for all food items" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": 172, 593 | "metadata": {}, 594 | "outputs": [], 595 | "source": [ 596 | "cholesterol = dict(zip(food_items,df['Cholesterol (mg)']))" 597 | ] 598 | }, 599 | { 600 | "cell_type": "markdown", 601 | "metadata": {}, 602 | "source": [ 603 | "### Create a dictionary of total fat for all food items" 604 | ] 605 | }, 606 | { 607 | "cell_type": "code", 608 | "execution_count": 173, 609 | "metadata": {}, 610 | "outputs": [], 611 | "source": [ 612 | "fat = dict(zip(food_items,df['Total_Fat (g)']))" 613 | ] 614 | }, 615 | { 616 | "cell_type": "markdown", 617 | "metadata": {}, 618 | "source": [ 619 | "### Create a dictionary of sodium for all food items" 620 | ] 621 | }, 622 | { 623 | "cell_type": "code", 624 | "execution_count": 174, 625 | "metadata": {}, 626 | "outputs": [], 627 | "source": [ 628 | "sodium = dict(zip(food_items,df['Sodium (mg)']))" 629 | ] 630 | }, 631 | { 632 | "cell_type": "markdown", 633 | "metadata": {}, 634 | "source": [ 635 | "### Create a dictionary of carbohydrates for all food items" 636 | ] 637 | }, 638 | { 639 | "cell_type": "code", 640 | "execution_count": 175, 641 | "metadata": {}, 642 | "outputs": [], 643 | "source": [ 644 | "carbs = dict(zip(food_items,df['Carbohydrates (g)']))" 645 | ] 646 | }, 647 | { 648 | "cell_type": "markdown", 649 | "metadata": {}, 650 | "source": [ 651 | "### Create a dictionary of dietary fiber for all food items" 652 | ] 653 | }, 654 | { 655 | "cell_type": "code", 656 | "execution_count": 176, 657 | "metadata": {}, 658 | "outputs": [], 659 | "source": [ 660 | "fiber = dict(zip(food_items,df['Dietary_Fiber (g)']))" 661 | ] 662 | }, 663 | { 664 | "cell_type": "markdown", 665 | "metadata": {}, 666 | "source": [ 667 | "### Create a dictionary of protein for all food items" 668 | ] 669 | }, 670 | { 671 | "cell_type": "code", 672 | "execution_count": 177, 673 | "metadata": {}, 674 | "outputs": [], 675 | "source": [ 676 | "protein = dict(zip(food_items,df['Protein (g)']))" 677 | ] 678 | }, 679 | { 680 | "cell_type": "markdown", 681 | "metadata": {}, 682 | "source": [ 683 | "### Create a dictionary of vitamin A for all food items" 684 | ] 685 | }, 686 | { 687 | "cell_type": "code", 688 | "execution_count": 178, 689 | "metadata": {}, 690 | "outputs": [], 691 | "source": [ 692 | "vit_A = dict(zip(food_items,df['Vit_A (IU)']))" 693 | ] 694 | }, 695 | { 696 | "cell_type": "markdown", 697 | "metadata": {}, 698 | "source": [ 699 | "### Create a dictionary of vitamin C for all food items" 700 | ] 701 | }, 702 | { 703 | "cell_type": "code", 704 | "execution_count": 179, 705 | "metadata": {}, 706 | "outputs": [], 707 | "source": [ 708 | "vit_C = dict(zip(food_items,df['Vit_C (IU)']))" 709 | ] 710 | }, 711 | { 712 | "cell_type": "markdown", 713 | "metadata": {}, 714 | "source": [ 715 | "### Create a dictionary of calcium for all food items" 716 | ] 717 | }, 718 | { 719 | "cell_type": "code", 720 | "execution_count": 180, 721 | "metadata": {}, 722 | "outputs": [], 723 | "source": [ 724 | "calcium = dict(zip(food_items,df['Calcium (mg)']))" 725 | ] 726 | }, 727 | { 728 | "cell_type": "markdown", 729 | "metadata": {}, 730 | "source": [ 731 | "### Create a dictionary of iron for all food items" 732 | ] 733 | }, 734 | { 735 | "cell_type": "code", 736 | "execution_count": 181, 737 | "metadata": {}, 738 | "outputs": [], 739 | "source": [ 740 | "iron = dict(zip(food_items,df['Iron (mg)']))" 741 | ] 742 | }, 743 | { 744 | "cell_type": "markdown", 745 | "metadata": {}, 746 | "source": [ 747 | "### Create a dictionary of food items with lower bound" 748 | ] 749 | }, 750 | { 751 | "cell_type": "code", 752 | "execution_count": 182, 753 | "metadata": {}, 754 | "outputs": [], 755 | "source": [ 756 | "# A dictionary called 'food_vars' is created to contain the referenced Variables\n", 757 | "food_vars = LpVariable.dicts(\"Food\",food_items,0,cat='Continuous')" 758 | ] 759 | }, 760 | { 761 | "cell_type": "code", 762 | "execution_count": 183, 763 | "metadata": {}, 764 | "outputs": [ 765 | { 766 | "data": { 767 | "text/plain": [ 768 | "{' Baked Potatoes': Food__Baked_Potatoes,\n", 769 | " 'Apple Pie': Food_Apple_Pie,\n", 770 | " 'Banana': Food_Banana,\n", 771 | " 'Beef Frankfurter': Food_Beef_Frankfurter,\n", 772 | " 'Chocolate Chip Cookies': Food_Chocolate_Chip_Cookies,\n", 773 | " 'Frozen Broccoli': Food_Frozen_Broccoli,\n", 774 | " 'Frozen Corn': Food_Frozen_Corn,\n", 775 | " 'Oatmeal Cookies': Food_Oatmeal_Cookies,\n", 776 | " 'Raw Apple': Food_Raw_Apple,\n", 777 | " 'Raw Lettuce Iceberg': Food_Raw_Lettuce_Iceberg,\n", 778 | " 'Roasted Chicken': Food_Roasted_Chicken,\n", 779 | " 'Scrambled Eggs': Food_Scrambled_Eggs,\n", 780 | " 'Spaghetti W/ Sauce': Food_Spaghetti_W__Sauce,\n", 781 | " 'Tofu': Food_Tofu,\n", 782 | " 'Turkey Bologna': Food_Turkey_Bologna,\n", 783 | " 'Wheat Bread': Food_Wheat_Bread,\n", 784 | " 'White Bread': Food_White_Bread}" 785 | ] 786 | }, 787 | "execution_count": 183, 788 | "metadata": {}, 789 | "output_type": "execute_result" 790 | } 791 | ], 792 | "source": [ 793 | "food_vars" 794 | ] 795 | }, 796 | { 797 | "cell_type": "markdown", 798 | "metadata": {}, 799 | "source": [ 800 | "### Adding the objective function to the problem" 801 | ] 802 | }, 803 | { 804 | "cell_type": "code", 805 | "execution_count": 184, 806 | "metadata": {}, 807 | "outputs": [], 808 | "source": [ 809 | "# The objective function is added to 'prob' first\n", 810 | "prob += lpSum([costs[i]*food_vars[i] for i in food_items]), \"Total Cost of the balanced diet\"" 811 | ] 812 | }, 813 | { 814 | "cell_type": "markdown", 815 | "metadata": {}, 816 | "source": [ 817 | "### Adding the calorie constraints to the problem" 818 | ] 819 | }, 820 | { 821 | "cell_type": "code", 822 | "execution_count": 185, 823 | "metadata": {}, 824 | "outputs": [], 825 | "source": [ 826 | "prob += lpSum([calories[f] * food_vars[f] for f in food_items]) >= 800.0, \"CalorieMinimum\"\n", 827 | "prob += lpSum([calories[f] * food_vars[f] for f in food_items]) <= 1300.0, \"CalorieMaximum\"" 828 | ] 829 | }, 830 | { 831 | "cell_type": "markdown", 832 | "metadata": {}, 833 | "source": [ 834 | "### Adding other nutrient constraints to the problem one by one..." 835 | ] 836 | }, 837 | { 838 | "cell_type": "markdown", 839 | "metadata": {}, 840 | "source": [ 841 | "# Cholesterol\n", 842 | "prob += lpSum([cholesterol[f] * food_vars[f] for f in food_items]) >= 30.0, \"CholesterolMinimum\"\n", 843 | "prob += lpSum([cholesterol[f] * food_vars[f] for f in food_items]) <= 240.0, \"CholesterolMaximum\"\n", 844 | "\n", 845 | "# Fat\n", 846 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) >= 40.0, \"FatMinimum\"\n", 847 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) <= 100.0, \"FatMaximum\"\n", 848 | "\n", 849 | "# Sodium\n", 850 | "prob += lpSum([sodium[f] * food_vars[f] for f in food_items]) >= 500.0, \"SodiumMinimum\"\n", 851 | "prob += lpSum([sodium[f] * food_vars[f] for f in food_items]) <= 2000.0, \"SodiumMaximum\"\n", 852 | "\n", 853 | "# Carbs\n", 854 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) >= 130.0, \"CarbsMinimum\"\n", 855 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) <= 450.0, \"CarbsMaximum\"\n", 856 | "\n", 857 | "# Fiber\n", 858 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) >= 125.0, \"FiberMinimum\"\n", 859 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) <= 250.0, \"FiberMaximum\"\n", 860 | "\n", 861 | "# Protein\n", 862 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) >= 60.0, \"ProteinMinimum\"\n", 863 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) <= 100.0, \"ProteinMaximum\"\n", 864 | "\n", 865 | "# Vitamin A\n", 866 | "prob += lpSum([vit_A[f] * food_vars[f] for f in food_items]) >= 1000.0, \"VitaminAMinimum\"\n", 867 | "prob += lpSum([vit_A[f] * food_vars[f] for f in food_items]) <= 10000.0, \"VitaminAMaximum\"\n", 868 | "\n", 869 | "# Vitamin C\n", 870 | "prob += lpSum([vit_C[f] * food_vars[f] for f in food_items]) >= 400.0, \"VitaminCMinimum\"\n", 871 | "prob += lpSum([vit_C[f] * food_vars[f] for f in food_items]) <= 5000.0, \"VitaminCMaximum\"\n", 872 | "\n", 873 | "# Calcium\n", 874 | "prob += lpSum([calcium[f] * food_vars[f] for f in food_items]) >= 300.0, \"CalciumMinimum\"\n", 875 | "prob += lpSum([calcium[f] * food_vars[f] for f in food_items]) <= 1500.0, \"CalciumMaximum\"\n", 876 | "\n", 877 | "# Iron\n", 878 | "prob += lpSum([iron[f] * food_vars[f] for f in food_items]) >= 10.0, \"IronMinimum\"\n", 879 | "prob += lpSum([iron[f] * food_vars[f] for f in food_items]) <= 40.0, \"IronMaximum\"" 880 | ] 881 | }, 882 | { 883 | "cell_type": "code", 884 | "execution_count": 186, 885 | "metadata": {}, 886 | "outputs": [], 887 | "source": [ 888 | "# Fat\n", 889 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) >= 20.0, \"FatMinimum\"\n", 890 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) <= 50.0, \"FatMaximum\"\n", 891 | "\n", 892 | "# Carbs\n", 893 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) >= 130.0, \"CarbsMinimum\"\n", 894 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) <= 200.0, \"CarbsMaximum\"\n", 895 | "\n", 896 | "# Fiber\n", 897 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) >= 60.0, \"FiberMinimum\"\n", 898 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) <= 125.0, \"FiberMaximum\"\n", 899 | "\n", 900 | "# Protein\n", 901 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) >= 100.0, \"ProteinMinimum\"\n", 902 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) <= 150.0, \"ProteinMaximum\"" 903 | ] 904 | }, 905 | { 906 | "cell_type": "markdown", 907 | "metadata": {}, 908 | "source": [ 909 | "### Writing problem data to a `.lp` file" 910 | ] 911 | }, 912 | { 913 | "cell_type": "code", 914 | "execution_count": 187, 915 | "metadata": {}, 916 | "outputs": [], 917 | "source": [ 918 | "# The problem data is written to an .lp file\n", 919 | "prob.writeLP(\"SimpleDietProblem.lp\")" 920 | ] 921 | }, 922 | { 923 | "cell_type": "markdown", 924 | "metadata": {}, 925 | "source": [ 926 | "### Run the solver" 927 | ] 928 | }, 929 | { 930 | "cell_type": "code", 931 | "execution_count": 188, 932 | "metadata": {}, 933 | "outputs": [ 934 | { 935 | "data": { 936 | "text/plain": [ 937 | "1" 938 | ] 939 | }, 940 | "execution_count": 188, 941 | "metadata": {}, 942 | "output_type": "execute_result" 943 | } 944 | ], 945 | "source": [ 946 | "# The problem is solved using PuLP's choice of Solver\n", 947 | "prob.solve()" 948 | ] 949 | }, 950 | { 951 | "cell_type": "markdown", 952 | "metadata": {}, 953 | "source": [ 954 | "### Print the problem solution status `'optimal'`, `'infeasible'`, `'unbounded'` etc..." 955 | ] 956 | }, 957 | { 958 | "cell_type": "code", 959 | "execution_count": 189, 960 | "metadata": {}, 961 | "outputs": [ 962 | { 963 | "name": "stdout", 964 | "output_type": "stream", 965 | "text": [ 966 | "Status: Optimal\n" 967 | ] 968 | } 969 | ], 970 | "source": [ 971 | "# The status of the solution is printed to the screen\n", 972 | "print(\"Status:\", LpStatus[prob.status])" 973 | ] 974 | }, 975 | { 976 | "cell_type": "markdown", 977 | "metadata": {}, 978 | "source": [ 979 | "### Scan through the problem variables and print out only if the variable quanity is positive i.e. it is included in the optimal solution" 980 | ] 981 | }, 982 | { 983 | "cell_type": "code", 984 | "execution_count": 190, 985 | "metadata": {}, 986 | "outputs": [ 987 | { 988 | "name": "stdout", 989 | "output_type": "stream", 990 | "text": [ 991 | "Therefore, the optimal (least cost) balanced diet consists of\n", 992 | "--------------------------------------------------------------------------------------------------------------\n", 993 | "Food_Frozen_Broccoli = 6.9242113\n", 994 | "Food_Scrambled_Eggs = 6.060891\n", 995 | "Food__Baked_Potatoes = 1.0806324\n" 996 | ] 997 | } 998 | ], 999 | "source": [ 1000 | "print(\"Therefore, the optimal (least cost) balanced diet consists of\\n\"+\"-\"*110)\n", 1001 | "for v in prob.variables():\n", 1002 | " if v.varValue>0:\n", 1003 | " print(v.name, \"=\", v.varValue)" 1004 | ] 1005 | }, 1006 | { 1007 | "cell_type": "markdown", 1008 | "metadata": {}, 1009 | "source": [ 1010 | "### Print the optimal diet cost" 1011 | ] 1012 | }, 1013 | { 1014 | "cell_type": "code", 1015 | "execution_count": 191, 1016 | "metadata": {}, 1017 | "outputs": [ 1018 | { 1019 | "name": "stdout", 1020 | "output_type": "stream", 1021 | "text": [ 1022 | "The total cost of this balanced diet is: $5.52\n" 1023 | ] 1024 | } 1025 | ], 1026 | "source": [ 1027 | "print(\"The total cost of this balanced diet is: ${}\".format(round(value(prob.objective),2)))" 1028 | ] 1029 | }, 1030 | { 1031 | "cell_type": "code", 1032 | "execution_count": null, 1033 | "metadata": {}, 1034 | "outputs": [], 1035 | "source": [] 1036 | } 1037 | ], 1038 | "metadata": { 1039 | "kernelspec": { 1040 | "display_name": "Python 3", 1041 | "language": "python", 1042 | "name": "python3" 1043 | }, 1044 | "language_info": { 1045 | "codemirror_mode": { 1046 | "name": "ipython", 1047 | "version": 3 1048 | }, 1049 | "file_extension": ".py", 1050 | "mimetype": "text/x-python", 1051 | "name": "python", 1052 | "nbconvert_exporter": "python", 1053 | "pygments_lexer": "ipython3", 1054 | "version": "3.6.2" 1055 | }, 1056 | "latex_envs": { 1057 | "LaTeX_envs_menu_present": true, 1058 | "autoclose": false, 1059 | "autocomplete": true, 1060 | "bibliofile": "biblio.bib", 1061 | "cite_by": "apalike", 1062 | "current_citInitial": 1, 1063 | "eqLabelWithNumbers": true, 1064 | "eqNumInitial": 1, 1065 | "hotkeys": { 1066 | "equation": "Ctrl-E", 1067 | "itemize": "Ctrl-I" 1068 | }, 1069 | "labels_anchors": false, 1070 | "latex_user_defs": false, 1071 | "report_style_numbering": false, 1072 | "user_envs_cfg": false 1073 | } 1074 | }, 1075 | "nbformat": 4, 1076 | "nbformat_minor": 2 1077 | } 1078 | -------------------------------------------------------------------------------- /Balanced_Diet_Problem_Simple.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "from pulp import *" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "### Read the given nutrition dataset into a Pandas DataFrame object\n", 18 | "Note we are reading only the first 64 rows with `nrows=64` argument because we just want to read all the nutrients informtion and not the maximum/minimum bounds in the dataset. We will enter those bounds in the optimization problem separately." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "df = pd.read_excel(\"diet.xls\",nrows=64)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "### Show first 5 rows of the dataset" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 3, 40 | "metadata": {}, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "text/html": [ 45 | "
\n", 46 | "\n", 59 | "\n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | "
FoodsPrice/ ServingServing SizeCaloriesCholesterol mgTotal_Fat gSodium mgCarbohydrates gDietary_Fiber gProtein gVit_A IUVit_C IUCalcium mgIron mg
0Frozen Broccoli0.1610 Oz Pkg73.80.00.868.213.68.58.05867.4160.2159.02.3
1Carrots,Raw0.071/2 Cup Shredded23.70.00.119.25.61.60.615471.05.114.90.3
2Celery, Raw0.041 Stalk6.40.00.134.81.50.70.353.62.816.00.2
3Frozen Corn0.181/2 Cup72.20.00.62.517.12.02.5106.65.23.30.3
4Lettuce,Iceberg,Raw0.021 Leaf2.60.00.01.80.40.30.266.00.83.80.1
\n", 167 | "
" 168 | ], 169 | "text/plain": [ 170 | " Foods Price/ Serving Serving Size Calories \\\n", 171 | "0 Frozen Broccoli 0.16 10 Oz Pkg 73.8 \n", 172 | "1 Carrots,Raw 0.07 1/2 Cup Shredded 23.7 \n", 173 | "2 Celery, Raw 0.04 1 Stalk 6.4 \n", 174 | "3 Frozen Corn 0.18 1/2 Cup 72.2 \n", 175 | "4 Lettuce,Iceberg,Raw 0.02 1 Leaf 2.6 \n", 176 | "\n", 177 | " Cholesterol mg Total_Fat g Sodium mg Carbohydrates g Dietary_Fiber g \\\n", 178 | "0 0.0 0.8 68.2 13.6 8.5 \n", 179 | "1 0.0 0.1 19.2 5.6 1.6 \n", 180 | "2 0.0 0.1 34.8 1.5 0.7 \n", 181 | "3 0.0 0.6 2.5 17.1 2.0 \n", 182 | "4 0.0 0.0 1.8 0.4 0.3 \n", 183 | "\n", 184 | " Protein g Vit_A IU Vit_C IU Calcium mg Iron mg \n", 185 | "0 8.0 5867.4 160.2 159.0 2.3 \n", 186 | "1 0.6 15471.0 5.1 14.9 0.3 \n", 187 | "2 0.3 53.6 2.8 16.0 0.2 \n", 188 | "3 2.5 106.6 5.2 3.3 0.3 \n", 189 | "4 0.2 66.0 0.8 3.8 0.1 " 190 | ] 191 | }, 192 | "execution_count": 3, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "df.head()" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "### Create the `PuLP` problem variable. Since it is a cost minimization problem, we need to use `LpMinimize`" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 4, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "# Create the 'prob' variable to contain the problem data\n", 215 | "prob = LpProblem(\"Simple Diet Problem\",LpMinimize)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "### Create a list of food items from the dataset" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": 5, 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [ 231 | "# Creates a list of the Ingredients\n", 232 | "food_items = list(df['Foods'])" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 6, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "So, the food items to consdier, are\n", 245 | "----------------------------------------------------------------------------------------------------\n", 246 | "Frozen Broccoli, Carrots,Raw, Celery, Raw, Frozen Corn, Lettuce,Iceberg,Raw, Peppers, Sweet, Raw, Potatoes, Baked, Tofu, Roasted Chicken, Spaghetti W/ Sauce, Tomato,Red,Ripe,Raw, Apple,Raw,W/Skin, Banana, Grapes, Kiwifruit,Raw,Fresh, Oranges, Bagels, Wheat Bread, White Bread, Oatmeal Cookies, Apple Pie, Chocolate Chip Cookies, Butter,Regular, Cheddar Cheese, 3.3% Fat,Whole Milk, 2% Lowfat Milk, Skim Milk, Poached Eggs, Scrambled Eggs, Bologna,Turkey, Frankfurter, Beef, Ham,Sliced,Extralean, Kielbasa,Prk, Cap'N Crunch, Cheerios, Corn Flks, Kellogg'S, Raisin Brn, Kellg'S, Rice Krispies, Special K, Oatmeal, Malt-O-Meal,Choc, Pizza W/Pepperoni, Taco, Hamburger W/Toppings, Hotdog, Plain, Couscous, White Rice, Macaroni,Ckd, Peanut Butter, Pork, Sardines in Oil, White Tuna in Water, Popcorn,Air-Popped, Potato Chips,Bbqflvr, Pretzels, Tortilla Chip, Chicknoodl Soup, Splt Pea&Hamsoup, Vegetbeef Soup, Neweng Clamchwd, Tomato Soup, New E Clamchwd,W/Mlk, Crm Mshrm Soup,W/Mlk, Beanbacn Soup,W/Watr, " 247 | ] 248 | } 249 | ], 250 | "source": [ 251 | "print(\"So, the food items to consdier, are\\n\"+\"-\"*100)\n", 252 | "for f in food_items:\n", 253 | " print(f,end=', ')" 254 | ] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "metadata": {}, 259 | "source": [ 260 | "### Create a dictinary of costs for all food items" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 7, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "costs = dict(zip(food_items,df['Price/ Serving']))" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 8, 275 | "metadata": {}, 276 | "outputs": [ 277 | { 278 | "data": { 279 | "text/plain": [ 280 | "{'2% Lowfat Milk': 0.23,\n", 281 | " '3.3% Fat,Whole Milk': 0.16,\n", 282 | " 'Apple Pie': 0.16,\n", 283 | " 'Apple,Raw,W/Skin': 0.24,\n", 284 | " 'Bagels': 0.16,\n", 285 | " 'Banana': 0.15,\n", 286 | " 'Beanbacn Soup,W/Watr': 0.67,\n", 287 | " 'Bologna,Turkey': 0.15,\n", 288 | " 'Butter,Regular': 0.05,\n", 289 | " \"Cap'N Crunch\": 0.31,\n", 290 | " 'Carrots,Raw': 0.07,\n", 291 | " 'Celery, Raw': 0.04,\n", 292 | " 'Cheddar Cheese': 0.25,\n", 293 | " 'Cheerios': 0.28,\n", 294 | " 'Chicknoodl Soup': 0.39,\n", 295 | " 'Chocolate Chip Cookies': 0.03,\n", 296 | " \"Corn Flks, Kellogg'S\": 0.28,\n", 297 | " 'Couscous': 0.39,\n", 298 | " 'Crm Mshrm Soup,W/Mlk': 0.65,\n", 299 | " 'Frankfurter, Beef': 0.27,\n", 300 | " 'Frozen Broccoli': 0.16,\n", 301 | " 'Frozen Corn': 0.18,\n", 302 | " 'Grapes': 0.32,\n", 303 | " 'Ham,Sliced,Extralean': 0.33,\n", 304 | " 'Hamburger W/Toppings': 0.83,\n", 305 | " 'Hotdog, Plain': 0.31,\n", 306 | " 'Kielbasa,Prk': 0.15,\n", 307 | " 'Kiwifruit,Raw,Fresh': 0.49,\n", 308 | " 'Lettuce,Iceberg,Raw': 0.02,\n", 309 | " 'Macaroni,Ckd': 0.17,\n", 310 | " 'Malt-O-Meal,Choc': 0.52,\n", 311 | " 'New E Clamchwd,W/Mlk': 0.99,\n", 312 | " 'Neweng Clamchwd': 0.75,\n", 313 | " 'Oatmeal': 0.82,\n", 314 | " 'Oatmeal Cookies': 0.09,\n", 315 | " 'Oranges': 0.15,\n", 316 | " 'Peanut Butter': 0.07,\n", 317 | " 'Peppers, Sweet, Raw': 0.53,\n", 318 | " 'Pizza W/Pepperoni': 0.44,\n", 319 | " 'Poached Eggs': 0.08,\n", 320 | " 'Popcorn,Air-Popped': 0.04,\n", 321 | " 'Pork': 0.81,\n", 322 | " 'Potato Chips,Bbqflvr': 0.22,\n", 323 | " 'Potatoes, Baked': 0.06,\n", 324 | " 'Pretzels': 0.12,\n", 325 | " \"Raisin Brn, Kellg'S\": 0.34,\n", 326 | " 'Rice Krispies': 0.32,\n", 327 | " 'Roasted Chicken': 0.84,\n", 328 | " 'Sardines in Oil': 0.45,\n", 329 | " 'Scrambled Eggs': 0.11,\n", 330 | " 'Skim Milk': 0.13,\n", 331 | " 'Spaghetti W/ Sauce': 0.78,\n", 332 | " 'Special K': 0.38,\n", 333 | " 'Splt Pea&Hamsoup': 0.67,\n", 334 | " 'Taco': 0.59,\n", 335 | " 'Tofu': 0.31,\n", 336 | " 'Tomato Soup': 0.39,\n", 337 | " 'Tomato,Red,Ripe,Raw': 0.27,\n", 338 | " 'Tortilla Chip': 0.19,\n", 339 | " 'Vegetbeef Soup': 0.71,\n", 340 | " 'Wheat Bread': 0.05,\n", 341 | " 'White Bread': 0.06,\n", 342 | " 'White Rice': 0.08,\n", 343 | " 'White Tuna in Water': 0.69}" 344 | ] 345 | }, 346 | "execution_count": 8, 347 | "metadata": {}, 348 | "output_type": "execute_result" 349 | } 350 | ], 351 | "source": [ 352 | "costs" 353 | ] 354 | }, 355 | { 356 | "cell_type": "markdown", 357 | "metadata": {}, 358 | "source": [ 359 | "### Create a dictionary of calories for all food items" 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": 9, 365 | "metadata": {}, 366 | "outputs": [], 367 | "source": [ 368 | "calories = dict(zip(food_items,df['Calories']))" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "metadata": {}, 374 | "source": [ 375 | "### Create a dictionary of cholesterol for all food items" 376 | ] 377 | }, 378 | { 379 | "cell_type": "code", 380 | "execution_count": 10, 381 | "metadata": {}, 382 | "outputs": [], 383 | "source": [ 384 | "cholesterol = dict(zip(food_items,df['Cholesterol mg']))" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "metadata": {}, 390 | "source": [ 391 | "### Create a dictionary of total fat for all food items" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 11, 397 | "metadata": {}, 398 | "outputs": [], 399 | "source": [ 400 | "fat = dict(zip(food_items,df['Total_Fat g']))" 401 | ] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "metadata": {}, 406 | "source": [ 407 | "### Create a dictionary of sodium for all food items" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 12, 413 | "metadata": {}, 414 | "outputs": [], 415 | "source": [ 416 | "sodium = dict(zip(food_items,df['Sodium mg']))" 417 | ] 418 | }, 419 | { 420 | "cell_type": "markdown", 421 | "metadata": {}, 422 | "source": [ 423 | "### Create a dictionary of carbohydrates for all food items" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 13, 429 | "metadata": {}, 430 | "outputs": [], 431 | "source": [ 432 | "carbs = dict(zip(food_items,df['Carbohydrates g']))" 433 | ] 434 | }, 435 | { 436 | "cell_type": "markdown", 437 | "metadata": {}, 438 | "source": [ 439 | "### Create a dictionary of dietary fiber for all food items" 440 | ] 441 | }, 442 | { 443 | "cell_type": "code", 444 | "execution_count": 14, 445 | "metadata": {}, 446 | "outputs": [], 447 | "source": [ 448 | "fiber = dict(zip(food_items,df['Dietary_Fiber g']))" 449 | ] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "metadata": {}, 454 | "source": [ 455 | "### Create a dictionary of protein for all food items" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 15, 461 | "metadata": {}, 462 | "outputs": [], 463 | "source": [ 464 | "protein = dict(zip(food_items,df['Protein g']))" 465 | ] 466 | }, 467 | { 468 | "cell_type": "markdown", 469 | "metadata": {}, 470 | "source": [ 471 | "### Create a dictionary of vitamin A for all food items" 472 | ] 473 | }, 474 | { 475 | "cell_type": "code", 476 | "execution_count": 16, 477 | "metadata": {}, 478 | "outputs": [], 479 | "source": [ 480 | "vit_A = dict(zip(food_items,df['Vit_A IU']))" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": {}, 486 | "source": [ 487 | "### Create a dictionary of vitamin C for all food items" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 17, 493 | "metadata": {}, 494 | "outputs": [], 495 | "source": [ 496 | "vit_C = dict(zip(food_items,df['Vit_C IU']))" 497 | ] 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "metadata": {}, 502 | "source": [ 503 | "### Create a dictionary of calcium for all food items" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": 18, 509 | "metadata": {}, 510 | "outputs": [], 511 | "source": [ 512 | "calcium = dict(zip(food_items,df['Calcium mg']))" 513 | ] 514 | }, 515 | { 516 | "cell_type": "markdown", 517 | "metadata": {}, 518 | "source": [ 519 | "### Create a dictionary of iron for all food items" 520 | ] 521 | }, 522 | { 523 | "cell_type": "code", 524 | "execution_count": 19, 525 | "metadata": {}, 526 | "outputs": [], 527 | "source": [ 528 | "iron = dict(zip(food_items,df['Iron mg']))" 529 | ] 530 | }, 531 | { 532 | "cell_type": "markdown", 533 | "metadata": {}, 534 | "source": [ 535 | "### Create a dictionary of food items with lower bound" 536 | ] 537 | }, 538 | { 539 | "cell_type": "code", 540 | "execution_count": 20, 541 | "metadata": {}, 542 | "outputs": [], 543 | "source": [ 544 | "# A dictionary called 'food_vars' is created to contain the referenced Variables\n", 545 | "food_vars = LpVariable.dicts(\"Food\",food_items,0,cat='Continuous')" 546 | ] 547 | }, 548 | { 549 | "cell_type": "code", 550 | "execution_count": 21, 551 | "metadata": {}, 552 | "outputs": [ 553 | { 554 | "data": { 555 | "text/plain": [ 556 | "{'2% Lowfat Milk': Food_2%_Lowfat_Milk,\n", 557 | " '3.3% Fat,Whole Milk': Food_3.3%_Fat,Whole_Milk,\n", 558 | " 'Apple Pie': Food_Apple_Pie,\n", 559 | " 'Apple,Raw,W/Skin': Food_Apple,Raw,W_Skin,\n", 560 | " 'Bagels': Food_Bagels,\n", 561 | " 'Banana': Food_Banana,\n", 562 | " 'Beanbacn Soup,W/Watr': Food_Beanbacn_Soup,W_Watr,\n", 563 | " 'Bologna,Turkey': Food_Bologna,Turkey,\n", 564 | " 'Butter,Regular': Food_Butter,Regular,\n", 565 | " \"Cap'N Crunch\": Food_Cap'N_Crunch,\n", 566 | " 'Carrots,Raw': Food_Carrots,Raw,\n", 567 | " 'Celery, Raw': Food_Celery,_Raw,\n", 568 | " 'Cheddar Cheese': Food_Cheddar_Cheese,\n", 569 | " 'Cheerios': Food_Cheerios,\n", 570 | " 'Chicknoodl Soup': Food_Chicknoodl_Soup,\n", 571 | " 'Chocolate Chip Cookies': Food_Chocolate_Chip_Cookies,\n", 572 | " \"Corn Flks, Kellogg'S\": Food_Corn_Flks,_Kellogg'S,\n", 573 | " 'Couscous': Food_Couscous,\n", 574 | " 'Crm Mshrm Soup,W/Mlk': Food_Crm_Mshrm_Soup,W_Mlk,\n", 575 | " 'Frankfurter, Beef': Food_Frankfurter,_Beef,\n", 576 | " 'Frozen Broccoli': Food_Frozen_Broccoli,\n", 577 | " 'Frozen Corn': Food_Frozen_Corn,\n", 578 | " 'Grapes': Food_Grapes,\n", 579 | " 'Ham,Sliced,Extralean': Food_Ham,Sliced,Extralean,\n", 580 | " 'Hamburger W/Toppings': Food_Hamburger_W_Toppings,\n", 581 | " 'Hotdog, Plain': Food_Hotdog,_Plain,\n", 582 | " 'Kielbasa,Prk': Food_Kielbasa,Prk,\n", 583 | " 'Kiwifruit,Raw,Fresh': Food_Kiwifruit,Raw,Fresh,\n", 584 | " 'Lettuce,Iceberg,Raw': Food_Lettuce,Iceberg,Raw,\n", 585 | " 'Macaroni,Ckd': Food_Macaroni,Ckd,\n", 586 | " 'Malt-O-Meal,Choc': Food_Malt_O_Meal,Choc,\n", 587 | " 'New E Clamchwd,W/Mlk': Food_New_E_Clamchwd,W_Mlk,\n", 588 | " 'Neweng Clamchwd': Food_Neweng_Clamchwd,\n", 589 | " 'Oatmeal': Food_Oatmeal,\n", 590 | " 'Oatmeal Cookies': Food_Oatmeal_Cookies,\n", 591 | " 'Oranges': Food_Oranges,\n", 592 | " 'Peanut Butter': Food_Peanut_Butter,\n", 593 | " 'Peppers, Sweet, Raw': Food_Peppers,_Sweet,_Raw,\n", 594 | " 'Pizza W/Pepperoni': Food_Pizza_W_Pepperoni,\n", 595 | " 'Poached Eggs': Food_Poached_Eggs,\n", 596 | " 'Popcorn,Air-Popped': Food_Popcorn,Air_Popped,\n", 597 | " 'Pork': Food_Pork,\n", 598 | " 'Potato Chips,Bbqflvr': Food_Potato_Chips,Bbqflvr,\n", 599 | " 'Potatoes, Baked': Food_Potatoes,_Baked,\n", 600 | " 'Pretzels': Food_Pretzels,\n", 601 | " \"Raisin Brn, Kellg'S\": Food_Raisin_Brn,_Kellg'S,\n", 602 | " 'Rice Krispies': Food_Rice_Krispies,\n", 603 | " 'Roasted Chicken': Food_Roasted_Chicken,\n", 604 | " 'Sardines in Oil': Food_Sardines_in_Oil,\n", 605 | " 'Scrambled Eggs': Food_Scrambled_Eggs,\n", 606 | " 'Skim Milk': Food_Skim_Milk,\n", 607 | " 'Spaghetti W/ Sauce': Food_Spaghetti_W__Sauce,\n", 608 | " 'Special K': Food_Special_K,\n", 609 | " 'Splt Pea&Hamsoup': Food_Splt_Pea&Hamsoup,\n", 610 | " 'Taco': Food_Taco,\n", 611 | " 'Tofu': Food_Tofu,\n", 612 | " 'Tomato Soup': Food_Tomato_Soup,\n", 613 | " 'Tomato,Red,Ripe,Raw': Food_Tomato,Red,Ripe,Raw,\n", 614 | " 'Tortilla Chip': Food_Tortilla_Chip,\n", 615 | " 'Vegetbeef Soup': Food_Vegetbeef_Soup,\n", 616 | " 'Wheat Bread': Food_Wheat_Bread,\n", 617 | " 'White Bread': Food_White_Bread,\n", 618 | " 'White Rice': Food_White_Rice,\n", 619 | " 'White Tuna in Water': Food_White_Tuna_in_Water}" 620 | ] 621 | }, 622 | "execution_count": 21, 623 | "metadata": {}, 624 | "output_type": "execute_result" 625 | } 626 | ], 627 | "source": [ 628 | "food_vars" 629 | ] 630 | }, 631 | { 632 | "cell_type": "markdown", 633 | "metadata": {}, 634 | "source": [ 635 | "### Adding the objective function to the problem" 636 | ] 637 | }, 638 | { 639 | "cell_type": "code", 640 | "execution_count": 22, 641 | "metadata": {}, 642 | "outputs": [], 643 | "source": [ 644 | "# The objective function is added to 'prob' first\n", 645 | "prob += lpSum([costs[i]*food_vars[i] for i in food_items]), \"Total Cost of the balanced diet\"" 646 | ] 647 | }, 648 | { 649 | "cell_type": "markdown", 650 | "metadata": {}, 651 | "source": [ 652 | "### Adding the calorie constraints to the problem" 653 | ] 654 | }, 655 | { 656 | "cell_type": "code", 657 | "execution_count": 23, 658 | "metadata": {}, 659 | "outputs": [], 660 | "source": [ 661 | "prob += lpSum([calories[f] * food_vars[f] for f in food_items]) >= 1500.0, \"CalorieMinimum\"\n", 662 | "prob += lpSum([calories[f] * food_vars[f] for f in food_items]) <= 2500.0, \"CalorieMaximum\"" 663 | ] 664 | }, 665 | { 666 | "cell_type": "markdown", 667 | "metadata": {}, 668 | "source": [ 669 | "### Adding other nutrient constraints to the problem one by one..." 670 | ] 671 | }, 672 | { 673 | "cell_type": "code", 674 | "execution_count": 24, 675 | "metadata": {}, 676 | "outputs": [], 677 | "source": [ 678 | "# Cholesterol\n", 679 | "prob += lpSum([cholesterol[f] * food_vars[f] for f in food_items]) >= 30.0, \"CholesterolMinimum\"\n", 680 | "prob += lpSum([cholesterol[f] * food_vars[f] for f in food_items]) <= 240.0, \"CholesterolMaximum\"\n", 681 | "\n", 682 | "# Fat\n", 683 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) >= 20.0, \"FatMinimum\"\n", 684 | "prob += lpSum([fat[f] * food_vars[f] for f in food_items]) <= 70.0, \"FatMaximum\"\n", 685 | "\n", 686 | "# Sodium\n", 687 | "prob += lpSum([sodium[f] * food_vars[f] for f in food_items]) >= 800.0, \"SodiumMinimum\"\n", 688 | "prob += lpSum([sodium[f] * food_vars[f] for f in food_items]) <= 2000.0, \"SodiumMaximum\"\n", 689 | "\n", 690 | "# Carbs\n", 691 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) >= 130.0, \"CarbsMinimum\"\n", 692 | "prob += lpSum([carbs[f] * food_vars[f] for f in food_items]) <= 450.0, \"CarbsMaximum\"\n", 693 | "\n", 694 | "# Fiber\n", 695 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) >= 125.0, \"FiberMinimum\"\n", 696 | "prob += lpSum([fiber[f] * food_vars[f] for f in food_items]) <= 250.0, \"FiberMaximum\"\n", 697 | "\n", 698 | "# Protein\n", 699 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) >= 60.0, \"ProteinMinimum\"\n", 700 | "prob += lpSum([protein[f] * food_vars[f] for f in food_items]) <= 100.0, \"ProteinMaximum\"\n", 701 | "\n", 702 | "# Vitamin A\n", 703 | "prob += lpSum([vit_A[f] * food_vars[f] for f in food_items]) >= 1000.0, \"VitaminAMinimum\"\n", 704 | "prob += lpSum([vit_A[f] * food_vars[f] for f in food_items]) <= 10000.0, \"VitaminAMaximum\"\n", 705 | "\n", 706 | "# Vitamin C\n", 707 | "prob += lpSum([vit_C[f] * food_vars[f] for f in food_items]) >= 400.0, \"VitaminCMinimum\"\n", 708 | "prob += lpSum([vit_C[f] * food_vars[f] for f in food_items]) <= 5000.0, \"VitaminCMaximum\"\n", 709 | "\n", 710 | "# Calcium\n", 711 | "prob += lpSum([calcium[f] * food_vars[f] for f in food_items]) >= 700.0, \"CalciumMinimum\"\n", 712 | "prob += lpSum([calcium[f] * food_vars[f] for f in food_items]) <= 1500.0, \"CalciumMaximum\"\n", 713 | "\n", 714 | "# Iron\n", 715 | "prob += lpSum([iron[f] * food_vars[f] for f in food_items]) >= 10.0, \"IronMinimum\"\n", 716 | "prob += lpSum([iron[f] * food_vars[f] for f in food_items]) <= 40.0, \"IronMaximum\"" 717 | ] 718 | }, 719 | { 720 | "cell_type": "markdown", 721 | "metadata": {}, 722 | "source": [ 723 | "### Writing problem data to a `.lp` file" 724 | ] 725 | }, 726 | { 727 | "cell_type": "code", 728 | "execution_count": 25, 729 | "metadata": {}, 730 | "outputs": [], 731 | "source": [ 732 | "# The problem data is written to an .lp file\n", 733 | "prob.writeLP(\"SimpleDietProblem.lp\")" 734 | ] 735 | }, 736 | { 737 | "cell_type": "markdown", 738 | "metadata": {}, 739 | "source": [ 740 | "### Run the solver" 741 | ] 742 | }, 743 | { 744 | "cell_type": "code", 745 | "execution_count": 26, 746 | "metadata": {}, 747 | "outputs": [ 748 | { 749 | "data": { 750 | "text/plain": [ 751 | "1" 752 | ] 753 | }, 754 | "execution_count": 26, 755 | "metadata": {}, 756 | "output_type": "execute_result" 757 | } 758 | ], 759 | "source": [ 760 | "# The problem is solved using PuLP's choice of Solver\n", 761 | "prob.solve()" 762 | ] 763 | }, 764 | { 765 | "cell_type": "markdown", 766 | "metadata": {}, 767 | "source": [ 768 | "### Print the problem solution status `'optimal'`, `'infeasible'`, `'unbounded'` etc..." 769 | ] 770 | }, 771 | { 772 | "cell_type": "code", 773 | "execution_count": 27, 774 | "metadata": {}, 775 | "outputs": [ 776 | { 777 | "name": "stdout", 778 | "output_type": "stream", 779 | "text": [ 780 | "Status: Optimal\n" 781 | ] 782 | } 783 | ], 784 | "source": [ 785 | "# The status of the solution is printed to the screen\n", 786 | "print(\"Status:\", LpStatus[prob.status])" 787 | ] 788 | }, 789 | { 790 | "cell_type": "markdown", 791 | "metadata": {}, 792 | "source": [ 793 | "### Scan through the problem variables and print out only if the variable quanity is positive i.e. it is included in the optimal solution" 794 | ] 795 | }, 796 | { 797 | "cell_type": "code", 798 | "execution_count": 28, 799 | "metadata": {}, 800 | "outputs": [ 801 | { 802 | "name": "stdout", 803 | "output_type": "stream", 804 | "text": [ 805 | "Therefore, the optimal (least cost) balanced diet consists of\n", 806 | "--------------------------------------------------------------------------------------------------------------\n", 807 | "Food_Celery,_Raw = 52.64371\n", 808 | "Food_Frozen_Broccoli = 0.25960653\n", 809 | "Food_Lettuce,Iceberg,Raw = 63.988506\n", 810 | "Food_Oranges = 2.2929389\n", 811 | "Food_Poached_Eggs = 0.14184397\n", 812 | "Food_Popcorn,Air_Popped = 13.869322\n" 813 | ] 814 | } 815 | ], 816 | "source": [ 817 | "print(\"Therefore, the optimal (least cost) balanced diet consists of\\n\"+\"-\"*110)\n", 818 | "for v in prob.variables():\n", 819 | " if v.varValue>0:\n", 820 | " print(v.name, \"=\", v.varValue)" 821 | ] 822 | }, 823 | { 824 | "cell_type": "markdown", 825 | "metadata": {}, 826 | "source": [ 827 | "### Print the optimal diet cost" 828 | ] 829 | }, 830 | { 831 | "cell_type": "code", 832 | "execution_count": 29, 833 | "metadata": {}, 834 | "outputs": [ 835 | { 836 | "name": "stdout", 837 | "output_type": "stream", 838 | "text": [ 839 | "The total cost of this balanced diet is: $4.34\n" 840 | ] 841 | } 842 | ], 843 | "source": [ 844 | "print(\"The total cost of this balanced diet is: ${}\".format(round(value(prob.objective),2)))" 845 | ] 846 | } 847 | ], 848 | "metadata": { 849 | "kernelspec": { 850 | "display_name": "Python 3", 851 | "language": "python", 852 | "name": "python3" 853 | }, 854 | "language_info": { 855 | "codemirror_mode": { 856 | "name": "ipython", 857 | "version": 3 858 | }, 859 | "file_extension": ".py", 860 | "mimetype": "text/x-python", 861 | "name": "python", 862 | "nbconvert_exporter": "python", 863 | "pygments_lexer": "ipython3", 864 | "version": "3.6.2" 865 | }, 866 | "latex_envs": { 867 | "LaTeX_envs_menu_present": true, 868 | "autoclose": false, 869 | "autocomplete": true, 870 | "bibliofile": "biblio.bib", 871 | "cite_by": "apalike", 872 | "current_citInitial": 1, 873 | "eqLabelWithNumbers": true, 874 | "eqNumInitial": 1, 875 | "hotkeys": { 876 | "equation": "Ctrl-E", 877 | "itemize": "Ctrl-I" 878 | }, 879 | "labels_anchors": false, 880 | "latex_user_defs": false, 881 | "report_style_numbering": false, 882 | "user_envs_cfg": false 883 | } 884 | }, 885 | "nbformat": 4, 886 | "nbformat_minor": 2 887 | } 888 | -------------------------------------------------------------------------------- /Data/Readme.md: -------------------------------------------------------------------------------- 1 | ## Data files 2 | -------------------------------------------------------------------------------- /Data/diet - medium.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Optimization-Python/bbb2157e60682eca97b562cd0ba50e20003ec412/Data/diet - medium.xls -------------------------------------------------------------------------------- /Data/diet.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Optimization-Python/bbb2157e60682eca97b562cd0ba50e20003ec412/Data/diet.xls -------------------------------------------------------------------------------- /Data/monthly_prices.csv: -------------------------------------------------------------------------------- 1 | ,MSFT,V,WMT 2 | 1,44.259998,69.660004,64.839996 3 | 2,52.639999,77.580002,57.240002 4 | 3,54.349998,79.010002,58.84 5 | 4,55.48,77.550003,61.299999 6 | 5,55.09,74.489998,66.360001 7 | 6,50.880001,72.389999,66.339996 8 | 7,55.23,76.480003,68.489998 9 | 8,49.869999,77.239998,66.870003 10 | 9,53,78.940002,70.779999 11 | 10,51.169998,74.169998,73.019997 12 | 11,56.68,78.050003,72.970001 13 | 12,57.459999,80.900002,71.440002 14 | 13,57.599998,82.699997,72.120003 15 | 14,59.919998,82.510002,70.019997 16 | 15,60.259998,77.32,70.43 17 | 16,62.139999,78.019997,69.120003 18 | 17,64.650002,82.709999,66.739998 19 | 18,63.98,87.940002,70.93 20 | 19,65.860001,88.870003,72.080002 21 | 20,68.459999,91.220001,75.18 22 | 21,69.839996,95.230003,78.599998 23 | 22,68.93,93.779999,75.68 24 | 23,72.699997,99.559998,79.989998 25 | 24,74.769997,103.519997,78.07 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tirthajyoti Sarkar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PuLP_practice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from pulp import *" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Small cat food problem" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 4, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "# Create the 'prob' variable to contain the problem data\n", 26 | "prob = LpProblem(\"The Whiskas Problem\",LpMinimize)" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 5, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "x1=LpVariable(\"ChickenPercent\",0,100,LpInteger)\n", 36 | "x2=LpVariable(\"BeefPercent\",0,100)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 6, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "# The objective function is added to 'prob' first\n", 46 | "prob += 0.013*x1 + 0.008*x2, \"Total Cost of Ingredients per can\"" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 7, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "prob += x1 + x2 == 100, \"PercentagesSum\"\n", 56 | "prob += 0.100*x1 + 0.200*x2 >= 8.0, \"ProteinRequirement\"\n", 57 | "prob += 0.080*x1 + 0.100*x2 >= 6.0, \"FatRequirement\"\n", 58 | "prob += 0.001*x1 + 0.005*x2 <= 2.0, \"FibreRequirement\"\n", 59 | "prob += 0.002*x1 + 0.005*x2 <= 0.4, \"SaltRequirement\"" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 8, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "# The problem data is written to an .lp file\n", 69 | "prob.writeLP(\"WhiskasModel.lp\")" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 9, 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "data": { 79 | "text/plain": [ 80 | "1" 81 | ] 82 | }, 83 | "execution_count": 9, 84 | "metadata": {}, 85 | "output_type": "execute_result" 86 | } 87 | ], 88 | "source": [ 89 | "# The problem is solved using PuLP's choice of Solver\n", 90 | "prob.solve()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 10, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "name": "stdout", 100 | "output_type": "stream", 101 | "text": [ 102 | "Status: Optimal\n" 103 | ] 104 | } 105 | ], 106 | "source": [ 107 | "# The status of the solution is printed to the screen\n", 108 | "print(\"Status:\", LpStatus[prob.status])" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 11, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | "BeefPercent = 66.0\n", 121 | "ChickenPercent = 34.0\n" 122 | ] 123 | } 124 | ], 125 | "source": [ 126 | "# Each of the variables is printed with it's resolved optimum value\n", 127 | "for v in prob.variables():\n", 128 | " print(v.name, \"=\", v.varValue)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 12, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "Total Cost of Ingredients per can = 0.97\n" 141 | ] 142 | } 143 | ], 144 | "source": [ 145 | "# The optimised objective function value is printed to the screen\n", 146 | "print(\"Total Cost of Ingredients per can = \", value(prob.objective))" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "## Large cat food problem" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 13, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "# Creates a list of the Ingredients\n", 163 | "Ingredients = ['CHICKEN', 'BEEF', 'MUTTON', 'RICE', 'WHEAT', 'GEL']\n", 164 | "\n", 165 | "# A dictionary of the costs of each of the Ingredients is created\n", 166 | "costs = {'CHICKEN': 0.013, \n", 167 | " 'BEEF': 0.008, \n", 168 | " 'MUTTON': 0.010, \n", 169 | " 'RICE': 0.002, \n", 170 | " 'WHEAT': 0.005, \n", 171 | " 'GEL': 0.001}\n", 172 | "\n", 173 | "# A dictionary of the protein percent in each of the Ingredients is created\n", 174 | "proteinPercent = {'CHICKEN': 0.100, \n", 175 | " 'BEEF': 0.200, \n", 176 | " 'MUTTON': 0.150, \n", 177 | " 'RICE': 0.000, \n", 178 | " 'WHEAT': 0.040, \n", 179 | " 'GEL': 0.000}\n", 180 | "\n", 181 | "# A dictionary of the fat percent in each of the Ingredients is created\n", 182 | "fatPercent = {'CHICKEN': 0.080, \n", 183 | " 'BEEF': 0.100, \n", 184 | " 'MUTTON': 0.110, \n", 185 | " 'RICE': 0.010, \n", 186 | " 'WHEAT': 0.010, \n", 187 | " 'GEL': 0.000}\n", 188 | "\n", 189 | "# A dictionary of the fibre percent in each of the Ingredients is created\n", 190 | "fibrePercent = {'CHICKEN': 0.001, \n", 191 | " 'BEEF': 0.005, \n", 192 | " 'MUTTON': 0.003, \n", 193 | " 'RICE': 0.100, \n", 194 | " 'WHEAT': 0.150, \n", 195 | " 'GEL': 0.000}\n", 196 | "\n", 197 | "# A dictionary of the salt percent in each of the Ingredients is created\n", 198 | "saltPercent = {'CHICKEN': 0.002, \n", 199 | " 'BEEF': 0.005, \n", 200 | " 'MUTTON': 0.007, \n", 201 | " 'RICE': 0.002, \n", 202 | " 'WHEAT': 0.008, \n", 203 | " 'GEL': 0.000}" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": 14, 209 | "metadata": {}, 210 | "outputs": [], 211 | "source": [ 212 | "# Create the 'prob' variable to contain the problem data\n", 213 | "prob = LpProblem(\"The Whiskas Problem\", LpMinimize)" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 15, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "# A dictionary called 'ingredient_vars' is created to contain the referenced Variables\n", 223 | "ingredient_vars = LpVariable.dicts(\"Ingr\",Ingredients,0)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 16, 229 | "metadata": {}, 230 | "outputs": [ 231 | { 232 | "data": { 233 | "text/plain": [ 234 | "{'BEEF': Ingr_BEEF,\n", 235 | " 'CHICKEN': Ingr_CHICKEN,\n", 236 | " 'GEL': Ingr_GEL,\n", 237 | " 'MUTTON': Ingr_MUTTON,\n", 238 | " 'RICE': Ingr_RICE,\n", 239 | " 'WHEAT': Ingr_WHEAT}" 240 | ] 241 | }, 242 | "execution_count": 16, 243 | "metadata": {}, 244 | "output_type": "execute_result" 245 | } 246 | ], 247 | "source": [ 248 | "ingredient_vars" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": 17, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [ 257 | "# The objective function is added to 'prob' first\n", 258 | "prob += lpSum([costs[i]*ingredient_vars[i] for i in Ingredients]), \"Total Cost of Ingredients per can\"" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 18, 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [ 267 | "# The five constraints are added to 'prob'\n", 268 | "prob += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, \"PercentagesSum\"\n", 269 | "prob += lpSum([proteinPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 8.0, \"ProteinRequirement\"\n", 270 | "prob += lpSum([fatPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 6.0, \"FatRequirement\"\n", 271 | "prob += lpSum([fibrePercent[i] * ingredient_vars[i] for i in Ingredients]) <= 2.0, \"FibreRequirement\"\n", 272 | "prob += lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4, \"SaltRequirement\"" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 19, 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [ 281 | "# The problem data is written to an .lp file\n", 282 | "prob.writeLP(\"WhiskasModelBig.lp\")" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": 20, 288 | "metadata": {}, 289 | "outputs": [ 290 | { 291 | "data": { 292 | "text/plain": [ 293 | "1" 294 | ] 295 | }, 296 | "execution_count": 20, 297 | "metadata": {}, 298 | "output_type": "execute_result" 299 | } 300 | ], 301 | "source": [ 302 | "# The problem is solved using PuLP's choice of Solver\n", 303 | "prob.solve()" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 21, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "name": "stdout", 313 | "output_type": "stream", 314 | "text": [ 315 | "Ingr_BEEF = 60.0\n", 316 | "Ingr_CHICKEN = 0.0\n", 317 | "Ingr_GEL = 40.0\n", 318 | "Ingr_MUTTON = 0.0\n", 319 | "Ingr_RICE = 0.0\n", 320 | "Ingr_WHEAT = 0.0\n" 321 | ] 322 | } 323 | ], 324 | "source": [ 325 | "# Each of the variables is printed with it's resolved optimum value\n", 326 | "for v in prob.variables():\n", 327 | " print(v.name, \"=\", v.varValue)" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 22, 333 | "metadata": {}, 334 | "outputs": [ 335 | { 336 | "name": "stdout", 337 | "output_type": "stream", 338 | "text": [ 339 | "Total Cost of Ingredients per can = 0.52\n" 340 | ] 341 | } 342 | ], 343 | "source": [ 344 | "# The optimised objective function value is printed to the screen\n", 345 | "print(\"Total Cost of Ingredients per can = \", value(prob.objective))" 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [] 354 | } 355 | ], 356 | "metadata": { 357 | "kernelspec": { 358 | "display_name": "Python 3", 359 | "language": "python", 360 | "name": "python3" 361 | }, 362 | "language_info": { 363 | "codemirror_mode": { 364 | "name": "ipython", 365 | "version": 3 366 | }, 367 | "file_extension": ".py", 368 | "mimetype": "text/x-python", 369 | "name": "python", 370 | "nbconvert_exporter": "python", 371 | "pygments_lexer": "ipython3", 372 | "version": "3.6.2" 373 | }, 374 | "latex_envs": { 375 | "LaTeX_envs_menu_present": true, 376 | "autoclose": false, 377 | "autocomplete": true, 378 | "bibliofile": "biblio.bib", 379 | "cite_by": "apalike", 380 | "current_citInitial": 1, 381 | "eqLabelWithNumbers": true, 382 | "eqNumInitial": 1, 383 | "hotkeys": { 384 | "equation": "Ctrl-E", 385 | "itemize": "Ctrl-I" 386 | }, 387 | "labels_anchors": false, 388 | "latex_user_defs": false, 389 | "report_style_numbering": false, 390 | "user_envs_cfg": false 391 | } 392 | }, 393 | "nbformat": 4, 394 | "nbformat_minor": 2 395 | } 396 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optimization-Python 2 | 3 | ## General optimization (LP, MIP, QP etc.) examples using Python. 4 | 5 | ![lp1](https://people.richland.edu/james/lecture/m116/systems/linear.png) 6 | 7 | ## Fast optimization for complex simulations using Scipy interpolate 8 | ![simu-interpolate](https://raw.githubusercontent.com/tirthajyoti/Optimization-Python/master/images/Simu-interpolate-Header.png) 9 | 10 | ### Please feel free to [connect with me here on LinkedIn](https://www.linkedin.com/in/tirthajyoti-sarkar-2127aa7/) if you are interested in data science, machine learning. 11 | 12 | --- 13 | 14 | ## Requirements 15 | 16 | * Python 3+ 17 | * scipy (`pip install scipy`) 18 | * numpy (`pip install numpy`) 19 | * PuLP (`pip install pulp`) 20 | * CVXPY (`pip install cvxpy`) 21 | 22 | --- 23 | 24 | ## My Medium articles on optimization 25 | 26 | **[Read my article on Medium about optimization in machine learning algorithms](https://towardsdatascience.com/a-quick-overview-of-optimization-models-for-machine-learning-and-statistics-38e3a7d13138)** 27 | 28 | **[Read my article on Medium about optimization with SciPy](https://towardsdatascience.com/optimization-with-scipy-and-application-ideas-to-machine-learning-81d39c7938b8)** 29 | 30 | **[Read my article on Medium about stock market portfolio optimization with CVXPY](https://towardsdatascience.com/optimization-with-python-how-to-make-the-most-amount-of-money-with-the-least-amount-of-risk-1ebebf5b2f29)** 31 | 32 | **[Read my article on Medium about linear programming with PuLP](https://towardsdatascience.com/linear-programming-and-discrete-optimization-with-python-using-pulp-449f3c5f6e99)** 33 | 34 | -------------------------------------------------------------------------------- /SciPy_optimization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Notebook to demonstrate SciPY optimization methods\n", 8 | "\n", 9 | "Mathematical optimization is at the heart of solutions to major business problems in engineering, finance, healthcare, socioeconomic affairs. Pretty much all business problems boil down to minimization of some kind of resource cost or maximization of some kind of profit given other constraints.\n", 10 | "\n", 11 | "An optimization process is also the soul of operation research, which is intimately related to modern data-driven business analytics. In this manner, it is also closely related to the data science pipeline, employed in virtually all businesses today. \n", 12 | "\n", 13 | "Although much has been written about the data wrangling and predictive modeling aspects of a data science project, the final frontier often involves solving an optimization problem using the data-driven models which can improve the bottom-line of the business by reducing cost or enhancing productivity.\n", 14 | "\n", 15 | "Python has become the de-facto lingua franca of analytics, data science, and machine learning. Therefore, it makes sense to discuss optimization packages and frameworks within the Python ecosystem.\n", 16 | "\n", 17 | "We cover optimization algorithms available within the SciPy ecosystem. SciPy is the most widely used Python package for scientific and mathematical analysis and it is no wonder that it boasts of powerful yet easy-to-use optimization routines for solving complex problems.\n", 18 | "\n", 19 | "For more information see\n", 20 | "\n", 21 | "#### [SciPy optimization reference guide](https://docs.scipy.org/doc/scipy/reference/tutorial/optimize.html)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 1, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "import numpy as np\n", 31 | "from matplotlib import pyplot as plt\n", 32 | "from scipy import optimize" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### Minimize a simple scalar function: $\\text{sin}(x).\\text{exp}[(x-0.6)^2]$" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "def scalar1(x):\n", 49 | " return np.sin(x)*np.exp(-0.1*(x-0.6)**2)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 3, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "def plot_nice(x,y,title=None,xlabel='x',ylabel='y',show=True):\n", 59 | " #plt.figure(figsize=(8,5))\n", 60 | " if title!=None:\n", 61 | " plt.title(str(title)+'\\n',fontsize=18)\n", 62 | " plt.plot(x,y,color='k',lw=3)\n", 63 | " plt.grid(True)\n", 64 | " plt.xticks(fontsize=15)\n", 65 | " plt.yticks(fontsize=15)\n", 66 | " plt.xlabel(xlabel,fontsize=15)\n", 67 | " plt.ylabel(ylabel,fontsize=15)\n", 68 | " if show:\n", 69 | " plt.show()" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 4, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "x = np.arange(-10,10,0.05)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 5, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "y = scalar1(x)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 6, 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "data": { 97 | "image/png": "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\n", 98 | "text/plain": [ 99 | "
" 100 | ] 101 | }, 102 | "metadata": {}, 103 | "output_type": "display_data" 104 | } 105 | ], 106 | "source": [ 107 | "plot_nice(x,y,title=\"Objective function\",xlabel='x-values',ylabel='Function values')" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "### Use `optimize.minimize_scalar()` method" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 7, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "result = optimize.minimize_scalar(scalar1)" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 8, 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "data": { 133 | "text/plain": [ 134 | "True" 135 | ] 136 | }, 137 | "execution_count": 8, 138 | "metadata": {}, 139 | "output_type": "execute_result" 140 | } 141 | ], 142 | "source": [ 143 | "result['success']" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 9, 149 | "metadata": {}, 150 | "outputs": [ 151 | { 152 | "name": "stdout", 153 | "output_type": "stream", 154 | "text": [ 155 | "Minimum occurs at: -1.2214484245210282\n" 156 | ] 157 | } 158 | ], 159 | "source": [ 160 | "print(\"Minimum occurs at: \",result['x'])" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 10, 166 | "metadata": {}, 167 | "outputs": [ 168 | { 169 | "name": "stdout", 170 | "output_type": "stream", 171 | "text": [ 172 | " fun: -0.6743051024666711\n", 173 | " nfev: 15\n", 174 | " nit: 10\n", 175 | " success: True\n", 176 | " x: -1.2214484245210282\n" 177 | ] 178 | } 179 | ], 180 | "source": [ 181 | "print(result)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": {}, 187 | "source": [ 188 | "### Bounded search (bound on the independent variable)" 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": 11, 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [ 197 | "result = optimize.minimize_scalar(scalar1,bounds=(0,10),method='Bounded')" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 12, 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "name": "stdout", 207 | "output_type": "stream", 208 | "text": [ 209 | "When bounded between 0 and 10, minimum occurs at: 4.101466164987216\n" 210 | ] 211 | } 212 | ], 213 | "source": [ 214 | "print(\"When bounded between 0 and 10, minimum occurs at: \",result['x'])" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "### Other function-based constraints" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 13, 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [ 230 | "def constraint1(x):\n", 231 | " return 0.5-np.log10(x**2+2)" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 14, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "def constraint2(x):\n", 241 | " return np.log10(x**2+2) - 1.5" 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": 15, 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "def constraint3(x):\n", 251 | " return np.sin(x)+0.3*x**2-1" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 16, 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [ 260 | "cons = ({'type':'ineq','fun':constraint1},\n", 261 | " {'type':'ineq','fun':constraint2},\n", 262 | " {'type':'eq','fun':constraint3})" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": 17, 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "result = optimize.minimize(scalar1,x0=0,method='SLSQP',constraints=cons,options={'maxiter':100})" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 18, 277 | "metadata": {}, 278 | "outputs": [ 279 | { 280 | "name": "stdout", 281 | "output_type": "stream", 282 | "text": [ 283 | " fun: 0.7631695862891654\n", 284 | " jac: array([0.59193639])\n", 285 | " message: 'Iteration limit exceeded'\n", 286 | " nfev: 1254\n", 287 | " nit: 101\n", 288 | " njev: 101\n", 289 | " status: 9\n", 290 | " success: False\n", 291 | " x: array([0.8773752])\n" 292 | ] 293 | } 294 | ], 295 | "source": [ 296 | "print(result)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 19, 302 | "metadata": {}, 303 | "outputs": [], 304 | "source": [ 305 | "result = optimize.minimize(scalar1,x0=-20,method='SLSQP',constraints=cons,options={'maxiter':100})" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 20, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "name": "stdout", 315 | "output_type": "stream", 316 | "text": [ 317 | " fun: -0.28594944567686104\n", 318 | " jac: array([-0.46750661])\n", 319 | " message: 'Iteration limit exceeded'\n", 320 | " nfev: 1233\n", 321 | " nit: 101\n", 322 | " njev: 101\n", 323 | " status: 9\n", 324 | " success: False\n", 325 | " x: array([-2.37569791])\n" 326 | ] 327 | } 328 | ], 329 | "source": [ 330 | "print(result)" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 21, 336 | "metadata": {}, 337 | "outputs": [], 338 | "source": [ 339 | "result = optimize.minimize(scalar1,x0=-20,method='SLSQP',constraints=cons,options={'maxiter':3})" 340 | ] 341 | }, 342 | { 343 | "cell_type": "code", 344 | "execution_count": 22, 345 | "metadata": {}, 346 | "outputs": [ 347 | { 348 | "name": "stdout", 349 | "output_type": "stream", 350 | "text": [ 351 | " fun: -0.4155114388552631\n", 352 | " jac: array([-0.46860977])\n", 353 | " message: 'Iteration limit exceeded'\n", 354 | " nfev: 12\n", 355 | " nit: 4\n", 356 | " njev: 4\n", 357 | " status: 9\n", 358 | " success: False\n", 359 | " x: array([-2.10190632])\n" 360 | ] 361 | } 362 | ], 363 | "source": [ 364 | "print(result)" 365 | ] 366 | }, 367 | { 368 | "cell_type": "markdown", 369 | "metadata": {}, 370 | "source": [ 371 | "### Multi-variate case: Sum of Gaussians" 372 | ] 373 | }, 374 | { 375 | "cell_type": "code", 376 | "execution_count": 23, 377 | "metadata": {}, 378 | "outputs": [ 379 | { 380 | "data": { 381 | "image/png": "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\n", 382 | "text/plain": [ 383 | "
" 384 | ] 385 | }, 386 | "metadata": {}, 387 | "output_type": "display_data" 388 | } 389 | ], 390 | "source": [ 391 | "mu = [-1,0.3,2.1]\n", 392 | "sigma = [2.1,0.8,1.7]\n", 393 | "add=np.zeros(1000)\n", 394 | "plt.figure(figsize=(8,5))\n", 395 | "for m,s in zip(mu,sigma):\n", 396 | " x=np.arange(-5,5,0.01)\n", 397 | " y=np.exp(-(x-m)**2/(2*s**2))\n", 398 | " add+=y\n", 399 | " plot_nice(x,y,show=False)\n", 400 | " \n", 401 | "plt.show()" 402 | ] 403 | }, 404 | { 405 | "cell_type": "code", 406 | "execution_count": 24, 407 | "metadata": {}, 408 | "outputs": [ 409 | { 410 | "data": { 411 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZIAAAEVCAYAAADKN2OaAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzt3XmczWX/x/HXx4xtyFq2ctvSXtKIZGn83JWl204iyzC4SYiYlC1rhMgWoiHLaJhqUkS2kFSIuCt0Zy3hNpaRsV6/P874NjMNs55znTPn83w8zmNc1znfc95fw3zmu1zXJcYYlFJKqYzKYTuAUkop36aFRCmlVKZoIVFKKZUpWkiUUkplihYSpZRSmaKFRCmlVKZ4tJCISEsRiRGRoyISJyLbROS5NGxnUnh87YnMSimlbi7Qw5/XF/gVeAk4CTQAFonIrcaYKalsOwFYmqh9zj0RlVJKpYd4ckBiQsE4maxvEVDdGFPuJtsZ4EVjzFR3Z1RKKZU+Hj21lbyIJNgBFPNkDqWUUlnHo0ckKQYQ+RC4wxjz6E1eY4D/AYWA00AM8LIx5lRaPuPWW281ZcuWzYK0nnX+/Hny5ctnO4ZH6T77B3/bZ1/d323btp00xtyW2us8fY0kCRGpCzQGOqXy0nnAJ8AJoAowGKgkIlWNMVdv8N5dga4AxYsXZ/z48VmW21Pi4uLInz+/7RgepfvsH/xtn311f+vUqXMwLa+zdkQiImWBrcBXxpim6dy2PvAZ0NQY81Fqr69SpYr57rvvMhLTqvXr1xMSEmI7hkfpPvsHf9tnX91fEdlmjKmS2uusjCMRkSLACuAQ8HwG3mIlEAc8kpW5lFJKpZ/HC4mIBAHLgVxAQ2PM+fS+h/nrMErnwFdKKcs8eo1ERAKBKKAiUMMYczyD71MPyA9sy8J4SimlMsDTF9un4xqE2BsoIiKPJXpuhzHmooisATDG1AXnonkV4AtcgxgfAQYB3wCfejC7UkqpFHi6kDyV8HVyCs+VAw4AAcn6fwE6AM2BAsAxYD4w+EZ3bCmllPIcjxYSY0zZNLwmJFl7DbDGTZGU8nrGGA4ePMhvv/1Gzpw5qVChAkWKFLEdSymHzv6rlJf6448/ePXVVylTpgzlypWjRo0aVK1alaJFi1KtWjXmzp3LpUuXbMdUSguJUt7GGMO7777LXXfdxZgxYzh8+PDfXvPNN9/QuXNnKlWqxJYtWyykVOovWkiU8iKXL1+ma9eudOnShbNnzzr9BQoUoGrVqjz00EMEBPx1GfGnn36iVq1azJo1y0ZcpQAtJEp5jcuXL9OiRQveffddp69ixYpERUXxv//9j61bt7Jz505OnDjBmDFjnCk3rl69Srdu3Rg6dKit6MrPaSFRygtcu3aNzp07ExMT4/S1a9eOnTt30qJFCwID/7ovpnDhwrzyyivs3r2bypUrO/3Dhw9n3LhxHs2tFGghUcorjBkzhvfff99p9+/fn3nz5pE3b94bblOmTBk2btxIvXr1nL7w8HA++OADt2ZVKjktJEpZtmPHDoYMGeK0u3TpwtixYxGRVLfNly8f0dHRSSYE7NSpE7t373ZHVKVSpIVEKYtiY2MZOXIk165dA6B27dpMnz49TUXkurx58/Lhhx9SsWJFwLX2RdOmTYmLi3NLZqWS00KilEX9+vXj1CnX+mzFihVj8eLFSa6HpFWhQoX48MMPncWT9u/fz4ABA7I0q1I3ooVEKUtWr17Ne++957RnzpxJqVKlMvx+999/PzNmzHDaM2bMYNWqVZnKqFRaaCFRyoJLly7Ro0cPp92yZUuaNGmS6fd9/vnnadr0r3XiwsLCOH8+3Ss1KJUuWkiUsmD69Ons378fgPz58zNlypQseV8R4Z133uG221zLbB8+fJhRo0ZlyXsrdSNaSJTysFOnTjF8+HCn3a5dO4oXL55l71+sWLEk40nGjx/Pzz//nGXvr1RyWkiU8rCRI0cSGxsLQPny5bPklFZy7du35/HHHwdcI+Z79erFXwuLKpW1tJAo5UFHjx5l2rRpTnvs2LHkypUryz8nR44cTJs2jRw5XP/FV61axeeff57ln6MUaCFRyqPGjRvnTP1erVo1mjdv7rbPevjhh+nSpYvTDg8Pd8arKJWVtJAo5SHHjh1LMkvvkCFD0jXwMCOGDh1KUFAQALt27WLRokVu/Tzln7SQKOUhEyZMID4+HoDg4GDq16/v9s8sWbIkffv2ddqDBg3i4sWLbv9c5V+0kCjlASdOnGD69OlOe/DgwW4/Grmuf//+FC1aFICDBw/yzjvveORzlf/QQqKUB0ybNo0///wTgIceeohGjRp57LMLFCjA4MGDnfbYsWOdIyOlsoIWEqXcLD4+PsnUJQMHDvTY0ch13bp1o2TJkgD8/vvvzJkzx6Ofr7I3LSRKuVlkZCTHjx8H4I477nDrnVo3kidPHsLDw532G2+8oddKVJbRQqKUGxljmDRpktPu2bMnOXPmtJKlS5cuzgj6I0eOEBERYSWHyn60kCjlRhs2bGDnzp0ABAUFJRnX4WlBQUH079/faY8ePdoZ06JUZmghUcqNEh+NdOjQgSJFilhMA//+97+59dZbATh06FCS5X2VyigtJEq5yYEDB4iJiXHavXr1spjGJV++fLz88stOe9y4cTraXWWaFhKl3CQiIsKZKPHpp5/mnnvusZzIpXv37hQoUACAvXv3Jil2SmWEFhKl3ODatWtJVj8MCwuzmCapAgUK0L17d6c9duxYnRlYZYoWEqXcYO3atRw6dAiAokWL8q9//ctyoqR69+7tzDr89ddfs2nTJsuJlC/TQqKUG8ydO9f5c9u2bcmdO7fFNH9XsmRJ2rdv77QTL4SlVHppIVEqi8XGxhIdHe20O3XqZDHNjb388svOCPvly5ezZ88ey4mUr/JoIRGRliISIyJHRSRORLaJyHNp2C63iEwQkeMicl5EPhWRsu5PrFT6RUZGOqPGH3nkESpVqmQ5UcruvvtuGjdu7LTHjx9vMY3yZZ4+IukLxAEvAY2AdcAiEXkxle3eBjoCLwMtgFuB1SKSx31RlcqYxKe1vPVo5LoBAwY4f164cCFHjhyxmEb5Kk8Xkn8ZY9oYYz4wxqw1xrwMLMZVYFIkIncAnYGXjDHzjTErgGZAGeB5j6RWKo1++OEHvvvuOwBy5crFc8+lesBtVfXq1alZsybgWts98QBKpdLKo4XEGHMyhe4dQLGbbPZUwlfnpLMx5iiwCXD/ykBKpUPiW36bNm1qfSR7WiQ+Kpk1axanT5+2mEb5Im+42P448J+bPH8PcMQYE5es/8eE55TyCpcuXUoy5Yi3n9a6rmHDhtx7770AnDt3jpkzZ1pOpHyN1UIiInWBxsC0m7ysMJDSr0ixCc8p5RWWL1/OyZOug+7SpUtTt25dy4nSJkeOHEmmTZk8ebJOMa/SJdDWByfcdbUI+NgYE5HKy1Madis36L/+/l2BrgDFixdn/fr1GYlpVVxcnE/mzgxf3uc333zT+XNISAgbN25M03besM933HEHRYsW5X//+x+///47Q4YMceua8t6wz56U7ffXGOPxB1AE16mpb4F8qbx2HPBrCv3TgD1p+bzg4GDji9atW2c7gsf56j4fPXrU5MiRw+D65cbs378/zdt6yz6/8cYbTv57773XXL161W2f5S377Cm+ur/AdyYNP2M9fmpLRIKA5UAuoKEx5nwqm/wElBaRfMn670l4Tinr3n//fWcW3ZCQECpUqGA5Ufp169aN/PnzA/Djjz/y2WefWU6kfIWnByQGAlFARaC+MeZ4GjZblfC1aaL3KQXUAlZkeUil0skY41NjR26kUKFCdOvWzWnrtCkqrTx9RDIdaACMAIqIyGOJHrkBRGSNiKy5voEx5ggwB5gkIu1EpB6uW4EPAgs8nF+pv9myZQt79+4F4JZbbrGyJntW6d27N4GBrkunGzduZOvWrZYTKV/g6UJyfUzIZGBLskfJhOcCEh6J9QLmAxOBZcAp4CljTLy7AyuVmsRHI61btyYoKMhimswpXbp0kkGUiW8gUOpGPD0gsawxRm7wOJDwmhBjTEiy7S4aY/oaY24zxuQzxjQwxvzqyexKpSQuLo4lS5Y4bV89rZVY4nXdo6Oj2bdvn8U0yhd4w4BEpXzW0qVLiYtzjZW99957qVatmuVEmffggw9Sr149wHX9Z+LEiZYTKW+nhUSpTEg8JUpoaKgzLbuvS3xUEhERwfHjabkvRvkrLSRKZdC+ffv48ssvAQgICKBdu3aWE2WdOnXqEBwcDEB8fDxTp061nEh5My0kSmVQRESE8+eGDRtSokQJe2GymIgkOSqZNm0a58+nNuRL+SstJEplwNWrV5k3b57Tzg4X2ZNr3rw5ZcuWBeDUqVNJ7k5TKjEtJEplwOrVqzl69CgAxYoVo0GDBpYTZb3AwED69evntCdOnMiVK1csJlLeSguJUhmQ+Lfzdu3akTNnTotp3Cc0NNRZU+XAgQMsW7bMciLljbSQKJVOJ0+e5KOPPnLaoaGhFtO4V758+XjhhRec9rhx465PmqqUQwuJUum0aNEiLl++DEC1atW4//77LSdyr549e5InTx4Atm/fzpo1a1LZQvkbLSRKpVPisSPZ8SJ7csWKFaNjx45Oe8SIEfbCKK+khUSpdNixYwfff/89AHnz5uXZZ5+1nMgzwsPDnckcv/zySzZs2GA5kfImWkiUSofEF9mbN29OwYIFLabxnLJly9KhQwenPXz4cItplLfRQqJUGsXHx7Nw4UKn7Q+ntRIbOHAgAQGuibnXrl3Lpk2bLCdS3kILiVJpFBMTQ2xsLADlypXjiSeesJzIsypUqMDzzz/vtPWoRF2nhUSpNEp8Wqtjx47kyOF//31ee+01Z79Xr17Nli1bLCdS3sD//icolQGHDh1i1SrXqs8ikuR6gT+pWLEibdq0cdp6VKJAC4lSaRIREeEMxPvnP/9JmTJlLCeyZ9CgQc50+StXruTrr7+2nEjZpoVEqVRcu3YtyWmtsLAwi2nsu/vuu2ndurXTHjhwoI5293NaSJRKxZo1azh48CAARYsWpXHjxpYT2Tds2DDnDq7169ezevVqy4mUTVpIlErFu+++6/y5Xbt25M6d22Ia73DXXXclOTIbOHAg165ds5hI2aSFRKmbSD5BY+fOnS2m8S5DhgxJMgfX0qVLLSdStmghUeomFixYwKVLlwDXBI0PPPCA5UTeo1SpUvTu3dtpDxo0yJnMUvkXLSRK3YAxhjlz5jhtPRr5u/DwcAoVKgS41rDXVRT9kxYSpW7g22+/Zffu3YBrXY7Edyopl8KFCxMeHu60hwwZwpkzZywmUjZoIVHqBhJfZG/VqhW33HKLxTTeq1evXpQuXRqA48ePM3LkSMuJlKdpIVEqBWfPnmXx4sVO29/HjtxMUFAQY8eOddqTJ09m3759FhMpT9NColQKFixYQFxcHAD33Xcf1atXt5zIu7Vu3ZrHH38cgMuXL/Pyyy9bTqQ8SQuJUskYY5g+fbrT7tGjhzMliEqZiDBp0iSnHRMTwxdffGExkfIkLSRKJbNp0yb27NkDuC6yt2vXznIi3/Doo48mmcyyT58+ejuwn9BColQyiY9Gnn/+eQoUKGAxjW8ZPXo0+fLlA2DPnj289dZblhMpT9BColQix44dY9myZU67e/fuFtP4nlKlSjFs2DCnPWzYMH799Vd7gZRHaCFRKpE5c+Y4p2Nq1KhBpUqVLCfyPb1793b+3i5cuECPHj10duBszuOFRETuFJGZIrJTRK6KyPo0bFNWREwKj0gPRFZ+4sqVK8ycOdNp9+jRw2Ia35UzZ05mzpyZZM2SqKgoy6mUO9k4IrkfaADsTXikx8tA9USPQVkbTfmzmJgYDh8+DMBtt91G8+bNLSfyXdWqVUtSiHv37s3p06ctJlLuZKOQfGKMKW2MaQnsSee2Pxtjvk702O+OgMo/Jb4wHBYWptPFZ9KoUaMoWbIk4Lr29NJLL1lOpNzF44XEGKOLFiiv880337Bp0ybAdWqmZ8+elhP5voIFCzJ16lSnHRERwfLlyy0mUu7iaxfb30u4rvK7iEwUkby2A6nsIfHRSOvWrSlVqpTFNNlHs2bNkkx22aVLF06dOmUxkXIHsXk3hYgsBW41xoSk8rqSwGvAKuAsEAKEA6uMMSmueyoiXYGuAMWLFw+OjPS96/JxcXHkz5/fdgyPsrHPx48f57nnnnNW+Js1axYVK1b02Odn9+/zmTNnCA0NJTY2FoC6devSp0+fbL3Pyfnq97hOnTrbjDFVUn2hMcbaA1gKrM/gtt0BAzyc2muDg4ONL1q3bp3tCB5nY5/79+9vEv4tmZCQEI9/vj98nz/++GPn7xgww4YNsx3Jo3z1ewx8Z9Lw89jXTm0ldn1dz0esplA+LS4ujlmzZjntvn37WkyTfTVq1Ij27ds77QkTJjh3yCnf58uFxCT7qlS6zZo1y1mIqWLFijRs2NByouxr8uTJzrol586d4/nnn+fq1auWU6ms4MuFpEXC121WUyifFR8fz5tvvum0+/XrR44cvvxfwrsVKlSIhQsXOn/HX375JaNHj7acSmUFGyPbg0SkhYi0AG4HbrveFpGghNfsF5E5ibYZJiITRKSZiPxTRIYDbwHRxphdnt4HlT3MnTuXY8eOAa45ojp27Gg3kB+oVasWQ4cOddrDhg1j8+bNFhOprGDj169iQFTC4zHgvkTtYgmvCQQCEm3zE/AE8B7wGdAGeDPhq1Lpdvny5SSr+vXv318HIHrIa6+9xkMPPQTAtWvXaNOmjd4S7ONsDEg8YIyRGzwOJLymrDGmY6JtIo0xVYwxBY0xuYwxdxpjhhhjLno6v8oeFixYwKFDhwDXdChdunSxnMh/BAQE8Nprr1G4cGEADh06RLt27Zzbr5Xv0RPCyu9cvXqVMWPGOO2XXnrJWUNDeUaxYsV47733nPZnn33GqFGjLCZSmaGFRPmdyMhI9u3bB7guAL/wwguWE/mnxo0bM2DAAKc9dOhQPv/8c4uJVEZpIVF+5fLly0ku9vbq1UtXQLRo1KhR1KlTB3ANjm7Tpg0HDhywG0qlmxYS5Vfmzp3LL7/8AkDhwoV1RlrLAgMDWbx4MbfffjsAp06dokWLFsTHx1tOptJDC4nyGxcuXGD48OFO+5VXXqFQoUIWEymA4sWLExUVRc6cOQHYtm0b3bt311UVfYgWEuU3pk6dym+//QZAyZIldap4L1K9enUmTpzotCMiIpg8ebLFRCo9tJAovxAbG8sbb7zhtAcPHkxQUJDFRCq5F154Icmg0H79+rFq1Sp7gVSaaSFRfmHEiBHOoLdy5crRuXNny4lUciLCO++8Q/Xq1QHXYMVnn32WvXvTuyK38rQ0FxIReUZEtPAon/Pzzz8zZcoUpz127Fhy5cplMZG6kdy5cxMdHc0dd9wBwOnTp2nUqJGu9+7l0lMYPgaOishYEbnXXYGUymovv/wyV65cAVxzPbVo0SKVLZRNJUqU4KOPPiJPnjyA6xeB5557TmcK9mLpKSQVgFlAK2C3iGwRkS4iojfhK6+1atUqZ51wEWHSpEmIiOVUKjXBwcFJRr6vXLmS8PBwi4nUzaS5kCTMkTXUGFMOeBLYj2sG3t9F5H0RqeOukEplxMWLF+ndu7fTDg0N5ZFHdB00X9G6dWteffVVpz1hwgQiIiLsBVI3lKFrHsaYtcaYdsBduNYDaQt8ISK/ishLIhKYlSGVyohx48bx008/AZA/f35GjhxpOZFKrxEjRtC4cWOn3a1bN5123gtlqJCIyBMiEgH8DDwATAOewjUV/OvA/KwKqFRG7N27N8kkgKNHj6ZkyZIWE6mMyJEjB++//z4PPPAAAJcuXaJZs2YcPHjQcjKVWHru2iojIkNE5BdgLVAa6AqUNMa8aIxZY4wZAHQAGt/svZRyJ2MM3bt35+JF1yoDVapUoUePHpZTqYy65ZZbiImJ4dZbbwXg+PHjNG7cmLi4OMvJ1HXpOSL5L9AFWATcaYypa4xZnMKaIHuAb7IqoFLpNX/+fNauXQu4fqOdNWsWAQEBqWylvFm5cuVYtmyZM43Kzp07ad++va5h4iXSU0j+BZQxxgw2xvx6oxcZY/YaY/TCu7Li8OHD9OrVy2n36dOHypUrW0ykskrt2rWZMWOG0/7www+TzOSs7EnPXVufGWO0/Cuvde3aNUJDQzl79iwA5cuX5/XXX7ecSmWlzp0706dPH6c9cuRIFi9ebDGRAp0iRWUj06dPZ82aNYBrzMj8+fPJnz+/5VQqq7355ps8/fTTTrtTp058++23FhMpLSQqW9i7d2+S1fYGDBhAjRo1LCZS7hIYGEhkZCR33303APHx8TRu3JijR49aTua/tJAonxcfH0/r1q25cOECAA8++KCe0srmChUqxCeffELhwoUB+P3332nSpInzb0B5lhYS5fP69u3Ljh07AMiVKxfvv/8+uXPntpxKuVvFihWJiopy7sj77rvv6NSpky6IZYEWEuXTlixZkuROnokTJ1KpUiWLiZQn1a1bl7fffttpR0ZGMnr0aIuJ/JMWEuWz9u3bR5cuXZx2y5YtdeChH+rRowfdu3d32oMGDSI6OtpiIv+jhUT5pLNnz9KkSRPOnTsHQIUKFZg9e7bO7OunJk+eTJ06fw1fa9euHd9//73FRP5FC4nyOVevXqVt27b85z//AVyLIUVFRVGwYEHLyZQtOXPmJCoqigoVKgDw559/0qhRI/744w/LyfyDFhLlcwYNGuSsMQIwZ84cHb2uKFq0KJ988gkFCriWSDp8+DBNmzZ15lxT7qOFRPmUhQsX8sYbbzjt8PBw2rZtazGR8ib33nsvkZGR5Mjh+tG2ZcsWunbtqndyuZkWEuUz1q9fT6dOnZx2w4YNk0wVrxRA/fr1GT9+vNOeP38+EyZMsJgo+9NConzCDz/8QJMmTbh06RLg+s1z0aJFOquvSlGfPn2S/NIxYMAAPv30U4uJsjctJMrrHTp0iHr16nHmzBkASpQowWeffeacC1cqORFh+vTp1KxZE3CtUdO2bVv2799vOVn25PFCIiJ3ishMEdkpIldFZH0atysoIu+JSKyInBGRhSJS1M1xlWWnTp2iXr16/Pbbb4BrkaMVK1ZQtmxZu8GU18udOzfLli3jH//4BwBnzpyhadOmnD9/3nKy7MfGEcn9QANgb8IjrZYAIUAY0BF4FPgoi7MpL3L27Fnq16/Pjz/+CLhu8fzoo494+OGHLSdTvqJYsWIsW7bMmTJn9+7ddO7cWS++ZzEbheQTY0xpY0xLXKsppkpEqgNPAx2MMcuMMR8CzwM1ReSfbsyqLDl//jwNGzbkm2/+Wmxz/vz5/N///Z/FVMoXValSJck0OkuWLOGtt96ymCj78XghyeDiWPWBP4wxXyZ6n2+AXxOeU9nIhQsXaNSoEZs2bXL6pk+fTuvWrS2mUr4sNDSUf//73057wIABrFu3zmKi7MVXLrbfA/yUQv+PCc+pbOLSpUs0b97cWXMdXBMxJp5LSamMmDRpEo899hjgmh3h2Wef5fDhw5ZTZQ9i81yhiCwFbjXGhKTyutXAeWNMk2T9C4DyxpjHU9imK9AVoHjx4sGRkZFZlttT4uLi/GqFv0uXLjFkyBC2bt3q9IWFhWX7AYf+9n0Ge/t84sQJunXrRmxsLAD33HMPkydPJleuXG79XF/9HtepU2ebMaZKqi80xlh7AEuB9Wl43WrgwxT6FwKbU9s+ODjY+KJ169bZjuAxf/75p6lXr54BnMfgwYNtx/IIf/o+X2dznzds2GACAwOdf2ddunRx+2f66vcY+M6k4We5r5zaigUKpdBfCDjt4Swqi50/f55nnnmGlStXOn3h4eG6yqFyi9q1aycZ6T579mxmz55tMZHv85VC8hMpXwu50bUT5SPOnj1LvXr1klwTGTp0KGPGjNEp4ZXbvPjii0lOmfbs2TPJHYIqfXylkKwASohIzesdIlIFKJ/wnPJBsbGxPPnkk0nuzgoLC2PYsGFaRJRbiQizZs1yVtO8dOkSLVq04MSJE5aT+SYbI9uDRKSFiLQAbgduu94WkaCE1+wXkTnXtzHGbAE+B+aLSDMRaYLr+sgmY8wXnt4HlXknT56kbt26SX4LnDhxYra/sK68R1BQENHR0RQq5DprfvjwYZ577jmuXr1qOZnvsXFEUgyISng8BtyXqF0s4TWBQPLZ+FoDG4C5wHxgG9DUA3lVFjt69CghISHs2LHD6Zs+fTovvfSSxVTKH5UvX54FCxY47TVr1jB48GCLiXyTjQGJB4wxcoPHgYTXlDXGdEy23WljTKgxppAxpoAxpo0x5qSn86vM2bdvHzVq1GDPHtekBiLCnDlzdJyIsqZhw4YMGTLEaY8ZM4aPPtLZl9LDV66RqGxg+/bt1KhRg4MHDwIQGBjIggULkkz3rZQNQ4YMoV69ek67Q4cO7N2bnqkA/ZsWEuUR69atIyQkxLmYmTdvXmJiYmjTpo3lZEpBQEAACxcudGaVPnv2LM2aNdOZgtNIC4lyuw8//JB69epx7tw5AAoVKsQXX3xB/fo6TZryHkWKFEkyU/CePXvo0qWLzhScBlpIlFvNmTOHFi1aOCsblipVio0bN/L443+b1UYp6x555JEkMwUvXryYKVOmWEzkG7SQKLcwxjB27FjCwsK4ds014fOdd97J5s2beeCBByynU+rGQkND6dq1q9Pu169fkrFO6u+0kKgsd+3aNfr3788rr7zi9FWuXJnNmzfryobKJ7z99ts8+uijAFy5coWWLVvy+++/W07lvbSQqCx15coVOnXqlGQuo5CQENavX0+xYsVusqVS3iN37twsXbqUokVdq3kfO3aMZ599lsuXL1tO5p20kKgsc+HCBZo1a8a8efOcviZNmrBixQoKFChgMZlS6fePf/yDyMhIcuRw/ZjcuHEj4eHhllN5Jy0kKkucPn2ap59+mk8++cTp69y5M1FRUeTJk8diMqUy7p///CcjR4502m+99RZLliyxmMg7aSFRmXbs2DFCQkLYuHGj0xceHs7s2bMJDAy0mEypzAsPD6dx48b8FsamAAATMklEQVROu3Pnzs7MDMpFC4nKlP/+97/UrFmTnTt3On3jx4/njTfe0Bl8VbaQI0cO5s2bx5133gm41s9p1qwZZ8+etZzMe2ghURm2a9cuatSowS+//AK4RgdHRETQr18/y8mUyloFCxYkOjqaoKAgAPbu3UtoaKgOVkyghURlyIYNG6hduzbHjh0DIE+ePERHR9OhQwfLyZRyjwcffJB3333XaUdHRzN+/HiLibyHFhKVbsuWLePpp5/mzJkzABQoUIDPP/+cRo0aWU6mlHs999xz9OrVy2m/8sorSVb39FdaSFS6zJgxg5YtW3Lx4kUASpQowZdffknt2rUtJ1PKM958801q1KgBuAbftm7dmiNHjlhOZZcWEpUmxhgGDx5Mjx49nPPCd911F1u2bHGWK1XKH+TKlYsPPviA4sWLA3DixAlatGjh/HLlj7SQqFRduXKFrl27JrmfvmrVqmzatEmnPFF+qVSpUnzwwQcEBLgWct26dSt9+/a1nMoeLSTqpv7880+aN2+e5CJj/fr1Wbt2LbfddpvFZErZVbt2bcaNG+e0p0+fzvz58y0mskcLibqhU6dO8eSTTxITE+P0dejQgY8//ph8+fJZTKaUd3jppZdo2bKl0+7WrRvff/+9xUR2aCFRKTp8+DA1a9bkq6++cvrCw8N57733yJkzp8VkSnkPEWHOnDnce++9AMTHx9O8eXNiY2MtJ/MsLSTqb/bs2UP16tX58ccfnb5JkybpaHWlUnDLLbcQHR1N/vz5AddsD+3atXPW4fEHWkhUEps2baJmzZocPXoUgJw5c7J48WJ69+5tOZlS3uuee+4hIiLCaX/66aeMGjXKXiAP00KiHB9//DFPPvkkp0+fBly/aa1YsYLWrVtbTqaU92vevDn9+/d32kOHDmXlypUWE3mOFhIFwOzZs2nWrBnx8fEAFC9enA0bNlC3bl3LyZTyHaNHjyYkJARwjb1q06YNv/76q91QHqCFxM8ZYxgyZAhdu3ZNsrb6V199ReXKlS2nU8q3BAYGEhkZye233w5AbGysXwxW1ELixy5dukSHDh0YMWKE0xccHMzmzZspX768xWRK+a7ixYsTFRXl3N24fft2Jk2alK1nCtZC4qfOnDlDgwYNeP/9952+evXqsW7dOl1bXalMql69OpMmTXLaK1euTDKoN7vRQuKHro8RWbNmjdMXFhZGTEwMt9xyi8VkSmUf3bt3p127dk67Z8+ebN261WIi99FC4md27txJ9erV2b17t9M3cuRIZs2apQMNlcpCIsI777zDQw89BLhOJTdu3JhDhw5ZTpb1tJD4kdWrV1OrVi1njEhgYCDz58/ntdde04GGSrlBUFAQ0dHRFChQAIA//viDZ555Jtst06uFxE9ERETQoEEDzp07B7gWo1q5cmWSQ2+lVNarUKECr7/+unPE/8MPP9C6dWuuXLliOVnW8XghEZH7RGSNiPwpIr+JyHARCUhlm7IiYlJ4RHoqt68yxjBs2DBCQ0Odf7h33HEHmzdv1jEiSnnIww8/zOzZs532ihUrstW084Ge/DARKQx8AfwHaAxUACbgKmiD0vAWLwObE7VPZnXG7OTChQuEhoayZMkSp69SpUp8+umnzn3uSinP6NChA3v37mX06NEATJkyhbvuuouePXtaTpZ5Hi0kwL+BvEAzY8xZYLWIFACGici4hL6b+dkY87XbU2YDv//+O02aNOGbb75x+p566imioqKc87VKKc8aMWIE+/btIyoqCoDevXtToUIF6tevbzlZ5nj61FZ94PNkBSMSV3F5wsNZsq3vv/+eqlWrJikiL7zwAp9++qkWEaUsypEjB/PmzaNatWqAa833Vq1asW3bNsvJMsfTheQe4KfEHcaYQ8CfCc+l5j0RuSoiv4vIRBHJ646QviwmJoaaNWty5MgRAAICApg6dSpTp04lMNDTB6BKqeTy5s3Lxx9/TJkyZQCIi4ujQYMG/PLLL5aTZZx4cti+iFwG+htjJiXrPwLMN8a8eoPtSgKvAauAs0AIEA6sMsY0vsE2XYGuAMWLFw+OjPS96/JxcXHOGgepMcYQGRnJ7NmznakY8uXLx5AhQ6hatao7Y2ap9OxzdqH7nP2ltL8HDhygV69ezp2UpUqVYsqUKRQpUsRGxBTVqVNnmzGmSqovNMZ47AFcBnqn0H8UGJXO9+oOGODh1F4bHBxsfNG6devS9LoLFy6YDh06mIS/DwOY8uXLmz179rg3oBukdZ+zE93n7O9G+7t582aTJ08e5//tI488Ys6ePevZcDcBfGfS8PPY06e2YoFCKfQXBE6n872WJnx9JFOJfNzhw4epVasW8+bNc/pq1arF1q1bue+++ywmU0ql5vHHH2fJkiXkyOH6Ubx9+3aaNWvGpUuXLCdLH08Xkp9Idi1EREoD+Uh27SQNTLKvfmfDhg0EBwfz3XffOX2hoaGsXr2aW2+91WIypVRaNWrUiJkzZzrtL774gvbt23P16lWLqdLH04VkBfC0iCSeGfBZ4AKwIZ3v1SLhq2/f7pABxhjefvtt6taty4kTJwDXdCdTp05lzpw55M6d23JCpVR6hIWFJVnOYcmSJYSFhfnMuu+evo3nHaAXEC0iY4HywDBgokl0S7CI7Ac2GGM6J7SHAbfgGox4FqgN9AeijTG7PLkDtl24cIFu3bolmf69WLFiREVFUbt2bYvJlFKZ8dprr3H8+HGmTJkCuKY1yps3L9OmTfP6ufA8WkiMMbEiUheYCnyC67rIW7iKSfJciadN+QnXqPYwXGNODgFvAqPcHNmr7N+/n1atWrFjxw6n79FHHyU6Opo77rjDYjKlVGaJCJMmTeLChQvO2iUzZswgT548TJgwwauLiccHFhhj/gP8XyqvKZusHYlr4KLfioqKonPnzs6tguC6HjJ9+nTy5MljMZlSKqvkyJGDd955hwsXLrBw4UIA3nrrLYKCghg5cqTldDems/96uYsXL9KzZ09atWrlFJFcuXIxffp05syZo0VEqWwmICCAiIgImjdv7vSNGjWKwYMHe+1yvVpIvNhvv/1GjRo1mDZtmtNXrlw5vvrqK7p37+7Vh7pKqYwLDAxk0aJFPPPMM07fyJEj6d+/v1cWEy0kXioqKoquXbsmmYOnWbNmbN++neDgYIvJlFKekCtXLqKiomjQoIHTN2HCBF588UWvu5tLC4mXOXv2LB07dqRVq1acP38egJw5c/L222+zdOlSChVKaTynUio7ypMnD9HR0TRp0sTpmzZtGl27dvWqcSZaSLzI5s2bqVSpUpJR6mXLlmXz5s28+OKLeipLKT+UO3duPvjgA1q3bu30zZkzh7Zt23Lx4kWLyf6ihcQLXL58mcGDB1O7dm0OHDjg9D/55JN8//33PProo/bCKaWsy5kzJwsWLKBDhw5O35IlS6hfvz5nzpyxmMxF5xW3bM+ePXTs2DHJNCeFChVixowZlChRgoIFC1pMp5TyFgEBAcydO5f8+fM7N+CsW7eO2rVrs2LFCkqVKmUtmx6RWHL58mVGjBhB5cqVkxSRkJAQdu3aleQwVimlwDXOZMqUKc5yvQC7du2ievXq/Pjjj/ZyWftkP7Z9+3YeffRRhgwZwuXLlwHXHRrjxo1jzZo1lC5d2nJCpZS3EhEGDhxIREQEAQGuCUAOHTrE448/zqpVq6xk0kLiQRcuXODVV1+latWq7Ny50+mvVq0aO3bsoH///s500kopdTMdOnRg+fLl5MuXD4DTp09Tv359Jk+e7PGxJvpTy0OWL1/O/fffz5gxY5zb9vLmzcuECRPYvHmzrh2ilEq3evXqsWHDBm6//XbAtQZ8nz59CAsL8+gdXVpI3OzXX3+lcePG/Otf/+LXX391+p944gl27dpF3759ncNTpZRKr+DgYL799luqVavm9M2dO5c6depw5MgRj2TQQuIm8fHxjBo1ivvuu4+YmBinv0iRIsycOZO1a9dy5513WkyolMouSpYsyfr162nfvr3Tt2XLFipXrswXX3zh9s/XQpLFrl27xuLFi7nnnnsYNGgQ8fHxznOdO3fm559/pmvXrnotRCmVpfLkyUNERAQTJkxwznKcPHmSp556ihkzZrj1s/WnWRb68ssveeyxx2jTpg0HDx50+h9++GG++uor3n33XV0CVynlNiJC3759Wbt2LSVKlABcd4Q+9thjbv1cLSRZ4Mcff6Rp06Y88cQTfPvtt05/0aJFmTp1Kt9++y3Vq1e3mFAp5U9q167Njh07qF27NtOmTaNy5cpu/Twd2Z4JP/30E8OHDycyMjLJ7Xa5c+emT58+DBw4UEemK6WsKFGiBGvXrvXIzTxaSDJg7969jBgxgkWLFv1tOue2bdsyatQoypQpYymdUkq5eOqOUC0k6bBjxw7Gjx9PZGTk3wpIgwYNGD58uK4VopTyO1pIUmGM4fPPP2f8+PGsWbPmb8/Xq1ePYcOGJbmHWyml/IkWkhu4dOkSixcvZvz48ezevftvzz/11FMMGzZML6IrpfyeFpIbGDVqFMOHD0/SFxAQQKtWrejXr5+ewlJKqQR6++8NhIWFERjoqrP58uWjT58+7N+/n0WLFmkRUUqpRPSI5AZKly5Nr169uO222+jWrRuFCxe2HUkppbySFpKbmDBhgu0ISinl9fTUllJKqUzRQqKUUipTtJAopZTKFC0kSimlMkULiVJKqUzRQqKUUipTtJAopZTKFEm8jkZ2JSIngIOpvtD73AqctB3Cw3Sf/YO/7bOv7m8ZY8xtqb3ILwqJrxKR74wxVWzn8CTdZ//gb/uc3fdXT20ppZTKFC0kSimlMkULiXebZTuABbrP/sHf9jlb769eI1FKKZUpekSilFIqU7SQKKWUyhQtJD5GRPqIiBGRpbazuIuIFBCR10XkGxE5IyLHRORDEbnLdrasIiL3icgaEflTRH4TkeEiEmA7l7uISEsRiRGRoyISJyLbROQ527k8RURuT9hvIyL5befJalpIfIiIFAOGACdsZ3GzfwBdgM+BFkA3oCSwVURK2wyWFUSkMPAFYIDGwHCgH/C6zVxu1heIA14CGgHrgEUi8qLVVJ7zJq79z5b0YrsPEZE5QC6gNHDSGNPCciS3EJF8wDVjzIVEfUWAQ8Cbxhif/oErIgOBAbhGDZ9N6BsADANKXO/LTkTkVmPMyWR9i4DqxphylmJ5hIjUAj4GRuMqKLcYY7JVUdEjEh8hIo8CrYBXbGdxN2PM+cRFJKHvFK5pborZSZWl6gOfJysYkUBe4Ak7kdwreRFJsIPs8f28oYTTlVNwHXX64hQpaaKFxAeIiABTgXHGmKO289ggIrcBdwL/sZ0lC9wD/JS4wxhzCPgz4Tl/8TjZ4/t5M/8G8gDTbAdxp0DbAVSahAIlgPG2g1g0Adc55kjbQbJAYeB0Cv2xCc9leyJSF9f1oU62s7iLiBQFRgDPG2Muu34fzJ60kFggIgVxXTy+KWPMTwmvHQ30Sn66x5ekZ59T2LY78DzQ3BjzPzfEsyGli5Nyg/5sRUTKAouAj40xEVbDuNcoYKsx5jPbQdxNC4kdLYHZaXidAK8Ch4FVIlIooT8QyJnQPmeMueqemFkqPfv8V0OkEa5zzOHGmA/dEcyCWKBQCv0FSflIJdtIuGliBa4bJ563HMdtROR+XEdbtRP9vw1K+FpQRK768i+GyeldW15ORD7CdQrgRmoZYzZ5Ko8nicjjuG6TnWuM6Wk7T1YRkS+Bo8aY5xL1lcb1w7WRMeYTa+HcSESCcH0/i+O6W+u45UhuIyJNgJv94jPHGBPmqTzupkck3m8QMClZ3yTgDDAU+MHjiTwg4Te65cBKoJflOFltBdBfRG4xxpxL6HsWuABssBfLfUQkEIgCKgI1snMRSbAJqJOsrx4QDjQA/uvxRG6kRyQ+SETWk73HkRQDtuG6XtAeiE/09FljjE/f6ZMwIPE/wG5gLFAemAhMMsYMspnNXURkFq5Bpr2Bb5I9vcMYc9HzqTxLRDoC75ENx5HoEYnyRvcBdyT8eV2y5zYAIR5Nk8WMMbEJdy1NBT7BdV3kLVwDErOrpxK+Tk7huXLAAc9FUVlNj0iUUkplig5IVEoplSlaSJRSSmWKFhKllFKZooVEKaVUpmghUUoplSlaSJRSSmWKFhKllFKZooVEKaVUpmghUUoplSlaSJTyIBEpJCJHRGR+sv4YEdmbMEOuUj5FC4lSHmSMOQ10BtolTDWOiIQCDYGOxpg/beZTKiN0ri2lLBCRmUATXFOLrwNmGmPC7aZSKmO0kChlgYjkB3YBpYD9QLA/TKWusic9taWUBQnrUSwHcuNaLU+LiPJZekSilAUiUgXYgmuFyzLA/caYY3ZTKZUxWkiU8jARyQNsx7XcaitgJ/CjMaaR1WBKZZCe2lLK80YCJYAuCXdpdQAaJizFqpTP0SMSpTxIRGoAXwLtjDGLEvW/iWtN8weMMUds5VMqI7SQKKWUyhQ9taWUUipTtJAopZTKFC0kSimlMkULiVJKqUzRQqKUUipTtJAopZTKFC0kSimlMkULiVJKqUz5f0a5gIiVD2IoAAAAAElFTkSuQmCC\n", 412 | "text/plain": [ 413 | "
" 414 | ] 415 | }, 416 | "metadata": {}, 417 | "output_type": "display_data" 418 | } 419 | ], 420 | "source": [ 421 | "plot_nice(x=np.arange(-5,5,0.01),y=add)" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 25, 427 | "metadata": {}, 428 | "outputs": [], 429 | "source": [ 430 | "def gaussian(m,s):\n", 431 | " x = np.arange(-5,5,0.01)\n", 432 | " return np.exp(-(x-m)**2/(2*s**2))" 433 | ] 434 | }, 435 | { 436 | "cell_type": "code", 437 | "execution_count": 26, 438 | "metadata": {}, 439 | "outputs": [], 440 | "source": [ 441 | "def gaussian_mixture(x):\n", 442 | " \"\"\"\n", 443 | " Computes the resultant Gaussian mixture from an input vector and known mean, variance quantities\n", 444 | " \"\"\"\n", 445 | " return -(np.exp(-(x[0]+1)**2/(2.1**2))+np.exp(-(x[1]-0.3)**2/(0.8**2))+np.exp(-(x[2]-2.1)**2/(1.7**2)))" 446 | ] 447 | }, 448 | { 449 | "cell_type": "code", 450 | "execution_count": 27, 451 | "metadata": {}, 452 | "outputs": [ 453 | { 454 | "data": { 455 | "text/plain": [ 456 | "-1.0233691172715331" 457 | ] 458 | }, 459 | "execution_count": 27, 460 | "metadata": {}, 461 | "output_type": "execute_result" 462 | } 463 | ], 464 | "source": [ 465 | "gaussian_mixture(np.array([3,-2,2]))" 466 | ] 467 | }, 468 | { 469 | "cell_type": "code", 470 | "execution_count": 28, 471 | "metadata": {}, 472 | "outputs": [], 473 | "source": [ 474 | "x0=np.array([0]*3)\n", 475 | "result = optimize.minimize(gaussian_mixture,x0=x0,method='SLSQP',options={'maxiter':100})" 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": 29, 481 | "metadata": {}, 482 | "outputs": [ 483 | { 484 | "data": { 485 | "text/plain": [ 486 | " fun: -2.999999618263914\n", 487 | " jac: array([-8.10027122e-05, -2.40206718e-04, 7.11023808e-04])\n", 488 | " message: 'Optimization terminated successfully.'\n", 489 | " nfev: 42\n", 490 | " nit: 8\n", 491 | " njev: 8\n", 492 | " status: 0\n", 493 | " success: True\n", 494 | " x: array([-1.00017856, 0.29992313, 2.10102744])" 495 | ] 496 | }, 497 | "execution_count": 29, 498 | "metadata": {}, 499 | "output_type": "execute_result" 500 | } 501 | ], 502 | "source": [ 503 | "result" 504 | ] 505 | }, 506 | { 507 | "cell_type": "markdown", 508 | "metadata": {}, 509 | "source": [ 510 | "### Bounds with multiple variables" 511 | ] 512 | }, 513 | { 514 | "cell_type": "code", 515 | "execution_count": 30, 516 | "metadata": {}, 517 | "outputs": [], 518 | "source": [ 519 | "x0=np.array([0]*3)\n", 520 | "x1_bound = (-2,2)\n", 521 | "x2_bound = (0,5)\n", 522 | "x3_bound = (-3,0)\n", 523 | "result = optimize.minimize(gaussian_mixture,x0=x0,method='SLSQP',options={'maxiter':100},\n", 524 | " bounds=(x1_bound,x2_bound,x3_bound))" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": 31, 530 | "metadata": {}, 531 | "outputs": [ 532 | { 533 | "data": { 534 | "text/plain": [ 535 | " fun: -2.217414055755018\n", 536 | " jac: array([-2.89082527e-06, 3.60012054e-04, -3.15965086e-01])\n", 537 | " message: 'Optimization terminated successfully.'\n", 538 | " nfev: 31\n", 539 | " nit: 6\n", 540 | " njev: 6\n", 541 | " status: 0\n", 542 | " success: True\n", 543 | " x: array([-1.00000644e+00, 3.00115191e-01, -8.03574200e-17])" 544 | ] 545 | }, 546 | "execution_count": 31, 547 | "metadata": {}, 548 | "output_type": "execute_result" 549 | } 550 | ], 551 | "source": [ 552 | "result" 553 | ] 554 | } 555 | ], 556 | "metadata": { 557 | "kernelspec": { 558 | "display_name": "Python 3", 559 | "language": "python", 560 | "name": "python3" 561 | }, 562 | "language_info": { 563 | "codemirror_mode": { 564 | "name": "ipython", 565 | "version": 3 566 | }, 567 | "file_extension": ".py", 568 | "mimetype": "text/x-python", 569 | "name": "python", 570 | "nbconvert_exporter": "python", 571 | "pygments_lexer": "ipython3", 572 | "version": "3.6.2" 573 | }, 574 | "latex_envs": { 575 | "LaTeX_envs_menu_present": true, 576 | "autoclose": false, 577 | "autocomplete": true, 578 | "bibliofile": "biblio.bib", 579 | "cite_by": "apalike", 580 | "current_citInitial": 1, 581 | "eqLabelWithNumbers": true, 582 | "eqNumInitial": 1, 583 | "hotkeys": { 584 | "equation": "Ctrl-E", 585 | "itemize": "Ctrl-I" 586 | }, 587 | "labels_anchors": false, 588 | "latex_user_defs": false, 589 | "report_style_numbering": false, 590 | "user_envs_cfg": false 591 | } 592 | }, 593 | "nbformat": 4, 594 | "nbformat_minor": 2 595 | } 596 | -------------------------------------------------------------------------------- /Scipy-Linear-Programming.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Linear programming with Scipy\n", 8 | "## Dr. Tirthajyoti Sarkar\n", 9 | "\n", 10 | "Simple, straight-forward linear programming (LP) problems can also be addressed by Scipy. Prior to 2014, it did not have a LP solver built-in, but it has changed since then. Let’s take a practical factory production problem (borrowed from [this example](https://realpython.com/linear-programming-python/) and slightly changed)" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "---\n", 18 | "## The problem\n", 19 | "\n", 20 | "A factory produces four different products, and that the daily produced amount of the first product is $x_1$, the amount produced of the second product is $x_2$, and so on. The goal is to determine the profit-maximizing daily production amount for each product, with the following constraints,\n", 21 | "\n", 22 | "- The profit per unit of product is 20, 12, 30, and 15 for the first, second, third, and fourth product, respectively.\n", 23 | "\n", 24 | "- Due to manpower constraints, the total number of units produced per day can’t exceed fifty (50).\n", 25 | "\n", 26 | "- For each unit of the first product, three units of the raw material A are consumed. Each unit of the second product requires two units of the raw material A and one unit of the raw material B. Each unit of the third product needs two unit of A and five units of B. Finally, each unit of the fourth product requires three units of B.\n", 27 | "\n", 28 | "- Due to the transportation and storage constraints, the factory can consume up to one hundred units of the raw material A and ninety units of B per day.\n", 29 | "\n", 30 | "The linear programming (LP) problem is,\n", 31 | "\n", 32 | "$$\\text{maximize: }\\ 20x_1+12x_2+30x_3+15x_4$$\n", 33 | "\n", 34 | "$$\\text{s.t.: } x_1+x_2+x_3+x_4 \\leq 50 \\text { (manpower constraint)}$$\n", 35 | "\n", 36 | "$$3x_1+2x_2+2x_3 \\leq 100 \\text { (material A constraint)}$$\n", 37 | "\n", 38 | "$$x_2+5x_3+3x_4 \\leq 90 \\text { (material B constraint)}$$\n", 39 | "\n", 40 | "$$x_1, x_2, x_3, x_4 \\geq 0$$\n", 41 | "\n", 42 | "---" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "### Negative coefficients for the objective function because it is a maximization" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 7, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "obj = [-20, -12, -30, -15] " 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "### Inequality matrices" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 13, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "# LHS matrix of inequality equations\n", 75 | "lhs = [[1, 1, 1, 1],\n", 76 | " [3, 2, 2, 0],\n", 77 | " [0, 1, 5, 3]]" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 14, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "# RHS matrix of inequality equations\n", 87 | "rhs = [50,\n", 88 | " 100,\n", 89 | " 90]" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "### Setup and solve in Scipy" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 15, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "from scipy.optimize import linprog" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 16, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "lp_opt = linprog(c=obj,\n", 115 | " A_ub=lhs,\n", 116 | " b_ub=rhs,\n", 117 | " method = 'interior-point')" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 17, 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "data": { 127 | "text/plain": [ 128 | " con: array([], dtype=float64)\n", 129 | " fun: -1033.3333113034805\n", 130 | " message: 'Optimization terminated successfully.'\n", 131 | " nit: 4\n", 132 | " slack: array([1.06529068e-06, 2.14344466e-06, 1.86209118e-06])\n", 133 | " status: 0\n", 134 | " success: True\n", 135 | " x: array([2.66666661e+01, 1.84050438e-08, 9.99999980e+00, 1.33333330e+01])" 136 | ] 137 | }, 138 | "execution_count": 17, 139 | "metadata": {}, 140 | "output_type": "execute_result" 141 | } 142 | ], 143 | "source": [ 144 | "lp_opt" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "## Note\n", 152 | "\n", 153 | "So, the solution says that,\n", 154 | "\n", 155 | "- The factory should produce 26.66 units of $x_1$, 10 units of $x_3$, and 13.33 units of $x_4$ every day. The extremely small number corresponding to $x_2$ essentially indicates that no amount of $x_2$ should be produced.\n", 156 | "\n", 157 | "- The maximum profit obtainable is $1033.33 under this arrangement.\n", 158 | "\n", 159 | "A noteworthy point is that the solution indicates a fractional choice, which may not be feasible in a practical situation. This is the limitation of Scipy solver that it cannot solve the so-called integer programming problems. **Other Python packages like `PuLP`** could be an option for such problems. **[See my article here](https://towardsdatascience.com/linear-programming-and-discrete-optimization-with-python-using-pulp-449f3c5f6e99)**." 160 | ] 161 | } 162 | ], 163 | "metadata": { 164 | "kernelspec": { 165 | "display_name": "Python 3", 166 | "language": "python", 167 | "name": "python3" 168 | }, 169 | "language_info": { 170 | "codemirror_mode": { 171 | "name": "ipython", 172 | "version": 3 173 | }, 174 | "file_extension": ".py", 175 | "mimetype": "text/x-python", 176 | "name": "python", 177 | "nbconvert_exporter": "python", 178 | "pygments_lexer": "ipython3", 179 | "version": "3.7.0" 180 | } 181 | }, 182 | "nbformat": 4, 183 | "nbformat_minor": 4 184 | } 185 | -------------------------------------------------------------------------------- /images/Markowitz_quote.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Optimization-Python/bbb2157e60682eca97b562cd0ba50e20003ec412/images/Markowitz_quote.jpeg -------------------------------------------------------------------------------- /images/Readme.md: -------------------------------------------------------------------------------- 1 | ## Images 2 | -------------------------------------------------------------------------------- /images/Simu-interpolate-Header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Optimization-Python/bbb2157e60682eca97b562cd0ba50e20003ec412/images/Simu-interpolate-Header.png --------------------------------------------------------------------------------