├── .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 | " Foods | \n",
64 | " Price/ Serving | \n",
65 | " Serving Size | \n",
66 | " Calories | \n",
67 | " Cholesterol mg | \n",
68 | " Total_Fat g | \n",
69 | " Sodium mg | \n",
70 | " Carbohydrates g | \n",
71 | " Dietary_Fiber g | \n",
72 | " Protein g | \n",
73 | " Vit_A IU | \n",
74 | " Vit_C IU | \n",
75 | " Calcium mg | \n",
76 | " Iron mg | \n",
77 | "
\n",
78 | " \n",
79 | " \n",
80 | " \n",
81 | " 0 | \n",
82 | " Frozen Broccoli | \n",
83 | " 0.16 | \n",
84 | " 10 Oz Pkg | \n",
85 | " 73.8 | \n",
86 | " 0.0 | \n",
87 | " 0.8 | \n",
88 | " 68.2 | \n",
89 | " 13.6 | \n",
90 | " 8.5 | \n",
91 | " 8.0 | \n",
92 | " 5867.4 | \n",
93 | " 160.2 | \n",
94 | " 159.0 | \n",
95 | " 2.3 | \n",
96 | "
\n",
97 | " \n",
98 | " 1 | \n",
99 | " Carrots,Raw | \n",
100 | " 0.07 | \n",
101 | " 1/2 Cup Shredded | \n",
102 | " 23.7 | \n",
103 | " 0.0 | \n",
104 | " 0.1 | \n",
105 | " 19.2 | \n",
106 | " 5.6 | \n",
107 | " 1.6 | \n",
108 | " 0.6 | \n",
109 | " 15471.0 | \n",
110 | " 5.1 | \n",
111 | " 14.9 | \n",
112 | " 0.3 | \n",
113 | "
\n",
114 | " \n",
115 | " 2 | \n",
116 | " Celery, Raw | \n",
117 | " 0.04 | \n",
118 | " 1 Stalk | \n",
119 | " 6.4 | \n",
120 | " 0.0 | \n",
121 | " 0.1 | \n",
122 | " 34.8 | \n",
123 | " 1.5 | \n",
124 | " 0.7 | \n",
125 | " 0.3 | \n",
126 | " 53.6 | \n",
127 | " 2.8 | \n",
128 | " 16.0 | \n",
129 | " 0.2 | \n",
130 | "
\n",
131 | " \n",
132 | " 3 | \n",
133 | " Frozen Corn | \n",
134 | " 0.18 | \n",
135 | " 1/2 Cup | \n",
136 | " 72.2 | \n",
137 | " 0.0 | \n",
138 | " 0.6 | \n",
139 | " 2.5 | \n",
140 | " 17.1 | \n",
141 | " 2.0 | \n",
142 | " 2.5 | \n",
143 | " 106.6 | \n",
144 | " 5.2 | \n",
145 | " 3.3 | \n",
146 | " 0.3 | \n",
147 | "
\n",
148 | " \n",
149 | " 4 | \n",
150 | " Lettuce,Iceberg,Raw | \n",
151 | " 0.02 | \n",
152 | " 1 Leaf | \n",
153 | " 2.6 | \n",
154 | " 0.0 | \n",
155 | " 0.0 | \n",
156 | " 1.8 | \n",
157 | " 0.4 | \n",
158 | " 0.3 | \n",
159 | " 0.2 | \n",
160 | " 66.0 | \n",
161 | " 0.8 | \n",
162 | " 3.8 | \n",
163 | " 0.1 | \n",
164 | "
\n",
165 | " \n",
166 | "
\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 | " Foods | \n",
64 | " Price/Serving | \n",
65 | " Serving Size | \n",
66 | " Calories | \n",
67 | " Cholesterol (mg) | \n",
68 | " Total_Fat (g) | \n",
69 | " Sodium (mg) | \n",
70 | " Carbohydrates (g) | \n",
71 | " Dietary_Fiber (g) | \n",
72 | " Protein (g) | \n",
73 | " Vit_A (IU) | \n",
74 | " Vit_C (IU) | \n",
75 | " Calcium (mg) | \n",
76 | " Iron (mg) | \n",
77 | "
\n",
78 | " \n",
79 | " \n",
80 | " \n",
81 | " 0 | \n",
82 | " Frozen Broccoli | \n",
83 | " 0.48 | \n",
84 | " 10 Oz Pkg | \n",
85 | " 73.8 | \n",
86 | " 0.0 | \n",
87 | " 0.8 | \n",
88 | " 68.2 | \n",
89 | " 13.6 | \n",
90 | " 8.5 | \n",
91 | " 8.0 | \n",
92 | " 5867.4 | \n",
93 | " 160.2 | \n",
94 | " 159.0 | \n",
95 | " 2.3 | \n",
96 | "
\n",
97 | " \n",
98 | " 1 | \n",
99 | " Frozen Corn | \n",
100 | " 0.54 | \n",
101 | " 1/2 Cup | \n",
102 | " 72.2 | \n",
103 | " 0.0 | \n",
104 | " 0.6 | \n",
105 | " 2.5 | \n",
106 | " 17.1 | \n",
107 | " 2.0 | \n",
108 | " 2.5 | \n",
109 | " 106.6 | \n",
110 | " 5.2 | \n",
111 | " 3.3 | \n",
112 | " 0.3 | \n",
113 | "
\n",
114 | " \n",
115 | " 2 | \n",
116 | " Raw Lettuce Iceberg | \n",
117 | " 0.06 | \n",
118 | " 1 Leaf | \n",
119 | " 2.6 | \n",
120 | " 0.0 | \n",
121 | " 0.0 | \n",
122 | " 1.8 | \n",
123 | " 0.4 | \n",
124 | " 0.3 | \n",
125 | " 0.2 | \n",
126 | " 66.0 | \n",
127 | " 0.8 | \n",
128 | " 3.8 | \n",
129 | " 0.1 | \n",
130 | "
\n",
131 | " \n",
132 | " 3 | \n",
133 | " Baked Potatoes | \n",
134 | " 0.18 | \n",
135 | " 1/2 Cup | \n",
136 | " 171.5 | \n",
137 | " 0.0 | \n",
138 | " 0.2 | \n",
139 | " 15.2 | \n",
140 | " 39.9 | \n",
141 | " 3.2 | \n",
142 | " 3.7 | \n",
143 | " 0.0 | \n",
144 | " 15.6 | \n",
145 | " 22.7 | \n",
146 | " 4.3 | \n",
147 | "
\n",
148 | " \n",
149 | " 4 | \n",
150 | " Tofu | \n",
151 | " 0.93 | \n",
152 | " 1/4 block | \n",
153 | " 88.2 | \n",
154 | " 0.0 | \n",
155 | " 5.5 | \n",
156 | " 8.1 | \n",
157 | " 2.2 | \n",
158 | " 1.4 | \n",
159 | " 9.4 | \n",
160 | " 98.6 | \n",
161 | " 0.1 | \n",
162 | " 121.8 | \n",
163 | " 6.2 | \n",
164 | "
\n",
165 | " \n",
166 | " 5 | \n",
167 | " Roasted Chicken | \n",
168 | " 2.52 | \n",
169 | " 1 lb chicken | \n",
170 | " 277.4 | \n",
171 | " 129.9 | \n",
172 | " 10.8 | \n",
173 | " 125.6 | \n",
174 | " 0.0 | \n",
175 | " 0.0 | \n",
176 | " 42.2 | \n",
177 | " 77.4 | \n",
178 | " 0.0 | \n",
179 | " 21.9 | \n",
180 | " 1.8 | \n",
181 | "
\n",
182 | " \n",
183 | " 6 | \n",
184 | " Spaghetti W/ Sauce | \n",
185 | " 2.34 | \n",
186 | " 1 1/2 Cup | \n",
187 | " 358.2 | \n",
188 | " 0.0 | \n",
189 | " 12.3 | \n",
190 | " 1237.1 | \n",
191 | " 58.3 | \n",
192 | " 11.6 | \n",
193 | " 8.2 | \n",
194 | " 3055.2 | \n",
195 | " 27.9 | \n",
196 | " 80.2 | \n",
197 | " 2.3 | \n",
198 | "
\n",
199 | " \n",
200 | " 7 | \n",
201 | " Raw Apple | \n",
202 | " 0.72 | \n",
203 | " 1 Fruit,3/Lb,Wo/Rf | \n",
204 | " 81.4 | \n",
205 | " 0.0 | \n",
206 | " 0.5 | \n",
207 | " 0.0 | \n",
208 | " 21.0 | \n",
209 | " 3.7 | \n",
210 | " 0.3 | \n",
211 | " 73.1 | \n",
212 | " 7.9 | \n",
213 | " 9.7 | \n",
214 | " 0.2 | \n",
215 | "
\n",
216 | " \n",
217 | " 8 | \n",
218 | " Banana | \n",
219 | " 0.45 | \n",
220 | " 1 Fruit,Wo/Skn&Seeds | \n",
221 | " 104.9 | \n",
222 | " 0.0 | \n",
223 | " 0.5 | \n",
224 | " 1.1 | \n",
225 | " 26.7 | \n",
226 | " 2.7 | \n",
227 | " 1.2 | \n",
228 | " 92.3 | \n",
229 | " 10.4 | \n",
230 | " 6.8 | \n",
231 | " 0.4 | \n",
232 | "
\n",
233 | " \n",
234 | " 9 | \n",
235 | " Wheat Bread | \n",
236 | " 0.15 | \n",
237 | " 1 Sl | \n",
238 | " 65.0 | \n",
239 | " 0.0 | \n",
240 | " 1.0 | \n",
241 | " 134.5 | \n",
242 | " 12.4 | \n",
243 | " 1.3 | \n",
244 | " 2.2 | \n",
245 | " 0.0 | \n",
246 | " 0.0 | \n",
247 | " 10.8 | \n",
248 | " 0.7 | \n",
249 | "
\n",
250 | " \n",
251 | " 10 | \n",
252 | " White Bread | \n",
253 | " 0.18 | \n",
254 | " 1 Sl | \n",
255 | " 65.0 | \n",
256 | " 0.0 | \n",
257 | " 1.0 | \n",
258 | " 132.5 | \n",
259 | " 11.8 | \n",
260 | " 1.1 | \n",
261 | " 2.3 | \n",
262 | " 0.0 | \n",
263 | " 0.0 | \n",
264 | " 26.2 | \n",
265 | " 0.8 | \n",
266 | "
\n",
267 | " \n",
268 | " 11 | \n",
269 | " Oatmeal Cookies | \n",
270 | " 0.27 | \n",
271 | " 1 Cookie | \n",
272 | " 81.0 | \n",
273 | " 0.0 | \n",
274 | " 3.3 | \n",
275 | " 68.9 | \n",
276 | " 12.4 | \n",
277 | " 0.6 | \n",
278 | " 1.1 | \n",
279 | " 2.9 | \n",
280 | " 0.1 | \n",
281 | " 6.7 | \n",
282 | " 0.5 | \n",
283 | "
\n",
284 | " \n",
285 | " 12 | \n",
286 | " Apple Pie | \n",
287 | " 0.48 | \n",
288 | " 1 Oz | \n",
289 | " 67.2 | \n",
290 | " 0.0 | \n",
291 | " 3.1 | \n",
292 | " 75.4 | \n",
293 | " 9.6 | \n",
294 | " 0.5 | \n",
295 | " 0.5 | \n",
296 | " 35.2 | \n",
297 | " 0.9 | \n",
298 | " 3.1 | \n",
299 | " 0.1 | \n",
300 | "
\n",
301 | " \n",
302 | " 13 | \n",
303 | " Scrambled Eggs | \n",
304 | " 0.33 | \n",
305 | " 1 Egg | \n",
306 | " 99.6 | \n",
307 | " 211.2 | \n",
308 | " 7.3 | \n",
309 | " 168.0 | \n",
310 | " 1.3 | \n",
311 | " 0.0 | \n",
312 | " 6.7 | \n",
313 | " 409.2 | \n",
314 | " 0.1 | \n",
315 | " 42.6 | \n",
316 | " 0.7 | \n",
317 | "
\n",
318 | " \n",
319 | " 14 | \n",
320 | " Turkey Bologna | \n",
321 | " 0.45 | \n",
322 | " 1 Oz | \n",
323 | " 56.4 | \n",
324 | " 28.1 | \n",
325 | " 4.3 | \n",
326 | " 248.9 | \n",
327 | " 0.3 | \n",
328 | " 0.0 | \n",
329 | " 3.9 | \n",
330 | " 0.0 | \n",
331 | " 0.0 | \n",
332 | " 23.8 | \n",
333 | " 0.4 | \n",
334 | "
\n",
335 | " \n",
336 | " 15 | \n",
337 | " Beef Frankfurter | \n",
338 | " 0.81 | \n",
339 | " 1 Frankfurter | \n",
340 | " 141.8 | \n",
341 | " 27.4 | \n",
342 | " 12.8 | \n",
343 | " 461.7 | \n",
344 | " 0.8 | \n",
345 | " 0.0 | \n",
346 | " 5.4 | \n",
347 | " 0.0 | \n",
348 | " 10.8 | \n",
349 | " 9.0 | \n",
350 | " 0.6 | \n",
351 | "
\n",
352 | " \n",
353 | " 16 | \n",
354 | " Chocolate Chip Cookies | \n",
355 | " 0.09 | \n",
356 | " 1 Cookie | \n",
357 | " 78.1 | \n",
358 | " 5.1 | \n",
359 | " 4.5 | \n",
360 | " 57.8 | \n",
361 | " 9.3 | \n",
362 | " 0.0 | \n",
363 | " 0.9 | \n",
364 | " 101.8 | \n",
365 | " 0.0 | \n",
366 | " 6.2 | \n",
367 | " 0.4 | \n",
368 | "
\n",
369 | " \n",
370 | "
\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 | " Foods | \n",
64 | " Price/ Serving | \n",
65 | " Serving Size | \n",
66 | " Calories | \n",
67 | " Cholesterol mg | \n",
68 | " Total_Fat g | \n",
69 | " Sodium mg | \n",
70 | " Carbohydrates g | \n",
71 | " Dietary_Fiber g | \n",
72 | " Protein g | \n",
73 | " Vit_A IU | \n",
74 | " Vit_C IU | \n",
75 | " Calcium mg | \n",
76 | " Iron mg | \n",
77 | "
\n",
78 | " \n",
79 | " \n",
80 | " \n",
81 | " 0 | \n",
82 | " Frozen Broccoli | \n",
83 | " 0.16 | \n",
84 | " 10 Oz Pkg | \n",
85 | " 73.8 | \n",
86 | " 0.0 | \n",
87 | " 0.8 | \n",
88 | " 68.2 | \n",
89 | " 13.6 | \n",
90 | " 8.5 | \n",
91 | " 8.0 | \n",
92 | " 5867.4 | \n",
93 | " 160.2 | \n",
94 | " 159.0 | \n",
95 | " 2.3 | \n",
96 | "
\n",
97 | " \n",
98 | " 1 | \n",
99 | " Carrots,Raw | \n",
100 | " 0.07 | \n",
101 | " 1/2 Cup Shredded | \n",
102 | " 23.7 | \n",
103 | " 0.0 | \n",
104 | " 0.1 | \n",
105 | " 19.2 | \n",
106 | " 5.6 | \n",
107 | " 1.6 | \n",
108 | " 0.6 | \n",
109 | " 15471.0 | \n",
110 | " 5.1 | \n",
111 | " 14.9 | \n",
112 | " 0.3 | \n",
113 | "
\n",
114 | " \n",
115 | " 2 | \n",
116 | " Celery, Raw | \n",
117 | " 0.04 | \n",
118 | " 1 Stalk | \n",
119 | " 6.4 | \n",
120 | " 0.0 | \n",
121 | " 0.1 | \n",
122 | " 34.8 | \n",
123 | " 1.5 | \n",
124 | " 0.7 | \n",
125 | " 0.3 | \n",
126 | " 53.6 | \n",
127 | " 2.8 | \n",
128 | " 16.0 | \n",
129 | " 0.2 | \n",
130 | "
\n",
131 | " \n",
132 | " 3 | \n",
133 | " Frozen Corn | \n",
134 | " 0.18 | \n",
135 | " 1/2 Cup | \n",
136 | " 72.2 | \n",
137 | " 0.0 | \n",
138 | " 0.6 | \n",
139 | " 2.5 | \n",
140 | " 17.1 | \n",
141 | " 2.0 | \n",
142 | " 2.5 | \n",
143 | " 106.6 | \n",
144 | " 5.2 | \n",
145 | " 3.3 | \n",
146 | " 0.3 | \n",
147 | "
\n",
148 | " \n",
149 | " 4 | \n",
150 | " Lettuce,Iceberg,Raw | \n",
151 | " 0.02 | \n",
152 | " 1 Leaf | \n",
153 | " 2.6 | \n",
154 | " 0.0 | \n",
155 | " 0.0 | \n",
156 | " 1.8 | \n",
157 | " 0.4 | \n",
158 | " 0.3 | \n",
159 | " 0.2 | \n",
160 | " 66.0 | \n",
161 | " 0.8 | \n",
162 | " 3.8 | \n",
163 | " 0.1 | \n",
164 | "
\n",
165 | " \n",
166 | "
\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 | 
6 |
7 | ## Fast optimization for complex simulations using Scipy interpolate
8 | 
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": "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\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
--------------------------------------------------------------------------------