{
"cells": [
{
"cell_type": "markdown",
"id": "7309eff3",
"metadata": {},
"source": [
"# The Diet Problem"
]
},
{
"cell_type": "markdown",
"id": "74095735",
"metadata": {},
"source": [
"Following a tutorial of JuMP, the modeling language for mathematical optimization in Julia, we consider the diet problem. "
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "429d1a04",
"metadata": {},
"outputs": [],
"source": [
"using JuMP\n",
"import DataFrames\n",
"import GLPK"
]
},
{
"cell_type": "markdown",
"id": "db7a92a6",
"metadata": {},
"source": [
"We encountered ``DataFrames`` in lecture 4. The ``JuMP`` and ``GLPK`` will need to be installed if they are used for the first time."
]
},
{
"cell_type": "markdown",
"id": "e2e191de",
"metadata": {},
"source": [
"## 1. Problem Formulation"
]
},
{
"cell_type": "markdown",
"id": "6634f4cb",
"metadata": {},
"source": [
"Suppose we want to choose the quantity of each food to eat from a set of $n$ foods."
]
},
{
"cell_type": "markdown",
"id": "4a98cc52",
"metadata": {},
"source": [
"For the $i$th food, we have\n",
"\n",
"1. a cost $c_i$ coefficient, and\n",
"2. a nutrient profile $a_{j, i}$, for each nutrient, for $j \\in \\{1,2, \\ldots, m \\}$."
]
},
{
"cell_type": "markdown",
"id": "776f8c4c",
"metadata": {},
"source": [
"In a well balanced meal, for the $j$th nutrient, we have \n",
"\n",
"1. a lower bound $\\ell_j$, and\n",
"2. an upper bound $u_j$."
]
},
{
"cell_type": "markdown",
"id": "2e1ce90d",
"metadata": {},
"source": [
"We want to determine the quantities $x_1$, $x_2$, $\\ldots$, $x_n$ of each food so that\n",
"\n",
"1. we satisfy all lower and upper bounds on the nutritional requirements, and\n",
"2. we minimize the cost."
]
},
{
"cell_type": "markdown",
"id": "fa95d01b",
"metadata": {},
"source": [
"The cost of the meal is obtained by summing up the quantities multiplied by the cost coefficients:\n",
"\n",
"$$\n",
" \\sum_{i=1}^n c_i x_i.\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "1eca1efb",
"metadata": {},
"source": [
"For the $j$th nutrient, we multiply the quantities with the nutrient profile and obtain the linear inequality:\n",
"\n",
"$$\n",
" \\ell_j \\leq \\sum_{j=1}^m a_{j,i} x_i \\leq u_j, \\quad j=1,2,\\ldots,m.\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "299c1cfa",
"metadata": {},
"source": [
"The optimization problem can then be formulated as\n",
"\n",
"$$\n",
" \\min \\sum_{i=1}^n c_i x_i\n",
"$$\n",
"\n",
"subject to\n",
"\n",
"$$\n",
" \\ell_j \\leq \\sum_{j=1}^m a_{j,i} x_i \\leq u_j, \\quad j=1,2,\\ldots,m,\n",
"$$\n",
"\n",
"and\n",
"\n",
"$$\n",
" x_i \\geq 0, \\quad i=1,2,\\ldots,n.\n",
"$$"
]
},
{
"cell_type": "markdown",
"id": "135752e5",
"metadata": {},
"source": [
"The objective function and all constraints are linear, so this is a *linear programming problem*."
]
},
{
"cell_type": "markdown",
"id": "7714e7da",
"metadata": {},
"source": [
"## 2. Data for the Problem"
]
},
{
"cell_type": "markdown",
"id": "f4d5c96a",
"metadata": {},
"source": [
"The data below is copied from the JuMP tutorial."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "d2141a22",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
| Row | name | cost | calories | protein | fat | sodium |
|---|
| Any | Any | Any | Any | Any | Any |
|---|
| 1 | hamburger | 2.49 | 410 | 24 | 26 | 730 |
| 2 | chicken | 2.89 | 420 | 32 | 10 | 1190 |
| 3 | hot dog | 1.5 | 560 | 20 | 32 | 1800 |
| 4 | fries | 1.89 | 380 | 4 | 19 | 270 |
| 5 | macaroni | 2.09 | 320 | 12 | 10 | 930 |
| 6 | pizza | 1.99 | 320 | 15 | 12 | 820 |
| 7 | salad | 2.49 | 320 | 31 | 12 | 1230 |
| 8 | milk | 0.89 | 100 | 8 | 2.5 | 125 |
| 9 | ice cream | 1.59 | 330 | 8 | 10 | 180 |
"
],
"text/latex": [
"\\begin{tabular}{r|cccccc}\n",
"\t& name & cost & calories & protein & fat & sodium\\\\\n",
"\t\\hline\n",
"\t& Any & Any & Any & Any & Any & Any\\\\\n",
"\t\\hline\n",
"\t1 & hamburger & 2.49 & 410 & 24 & 26 & 730 \\\\\n",
"\t2 & chicken & 2.89 & 420 & 32 & 10 & 1190 \\\\\n",
"\t3 & hot dog & 1.5 & 560 & 20 & 32 & 1800 \\\\\n",
"\t4 & fries & 1.89 & 380 & 4 & 19 & 270 \\\\\n",
"\t5 & macaroni & 2.09 & 320 & 12 & 10 & 930 \\\\\n",
"\t6 & pizza & 1.99 & 320 & 15 & 12 & 820 \\\\\n",
"\t7 & salad & 2.49 & 320 & 31 & 12 & 1230 \\\\\n",
"\t8 & milk & 0.89 & 100 & 8 & 2.5 & 125 \\\\\n",
"\t9 & ice cream & 1.59 & 330 & 8 & 10 & 180 \\\\\n",
"\\end{tabular}\n"
],
"text/plain": [
"\u001b[1m9×6 DataFrame\u001b[0m\n",
"\u001b[1m Row \u001b[0m│\u001b[1m name \u001b[0m\u001b[1m cost \u001b[0m\u001b[1m calories \u001b[0m\u001b[1m protein \u001b[0m\u001b[1m fat \u001b[0m\u001b[1m sodium \u001b[0m\n",
"\u001b[1m \u001b[0m│\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\n",
"─────┼─────────────────────────────────────────────────\n",
" 1 │ hamburger 2.49 410 24 26 730\n",
" 2 │ chicken 2.89 420 32 10 1190\n",
" 3 │ hot dog 1.5 560 20 32 1800\n",
" 4 │ fries 1.89 380 4 19 270\n",
" 5 │ macaroni 2.09 320 12 10 930\n",
" 6 │ pizza 1.99 320 15 12 820\n",
" 7 │ salad 2.49 320 31 12 1230\n",
" 8 │ milk 0.89 100 8 2.5 125\n",
" 9 │ ice cream 1.59 330 8 10 180"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foods = DataFrames.DataFrame(\n",
" [\n",
" \"hamburger\" 2.49 410 24 26 730\n",
" \"chicken\" 2.89 420 32 10 1190\n",
" \"hot dog\" 1.50 560 20 32 1800\n",
" \"fries\" 1.89 380 4 19 270\n",
" \"macaroni\" 2.09 320 12 10 930\n",
" \"pizza\" 1.99 320 15 12 820\n",
" \"salad\" 2.49 320 31 12 1230\n",
" \"milk\" 0.89 100 8 2.5 125\n",
" \"ice cream\" 1.59 330 8 10 180\n",
" ],\n",
" [\"name\", \"cost\", \"calories\", \"protein\", \"fat\", \"sodium\"],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3e3b7c82",
"metadata": {},
"source": [
"We have 9 foods in the rows of the table, $n=9$, and 4 nutrients, $m = 4$, in the last four columns of the table. "
]
},
{
"cell_type": "markdown",
"id": "1b379dcd",
"metadata": {},
"source": [
"Another data provides the lower and the upper bounds for the nutritional requirements."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "0133226c",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"| Row | name | min | max |
|---|
| Any | Any | Any |
|---|
| 1 | calories | 1800 | 2200 |
| 2 | protein | 91 | Inf |
| 3 | fat | 0 | 65 |
| 4 | sodium | 0 | 1779 |
"
],
"text/latex": [
"\\begin{tabular}{r|ccc}\n",
"\t& name & min & max\\\\\n",
"\t\\hline\n",
"\t& Any & Any & Any\\\\\n",
"\t\\hline\n",
"\t1 & calories & 1800 & 2200 \\\\\n",
"\t2 & protein & 91 & Inf \\\\\n",
"\t3 & fat & 0 & 65 \\\\\n",
"\t4 & sodium & 0 & 1779 \\\\\n",
"\\end{tabular}\n"
],
"text/plain": [
"\u001b[1m4×3 DataFrame\u001b[0m\n",
"\u001b[1m Row \u001b[0m│\u001b[1m name \u001b[0m\u001b[1m min \u001b[0m\u001b[1m max \u001b[0m\n",
"\u001b[1m \u001b[0m│\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\u001b[90m Any \u001b[0m\n",
"─────┼──────────────────────\n",
" 1 │ calories 1800 2200\n",
" 2 │ protein 91 Inf\n",
" 3 │ fat 0 65\n",
" 4 │ sodium 0 1779"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"limits = DataFrames.DataFrame(\n",
" [\n",
" \"calories\" 1800 2200\n",
" \"protein\" 91 Inf\n",
" \"fat\" 0 65\n",
" \"sodium\" 0 1779\n",
" ],\n",
" [\"name\", \"min\", \"max\"],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "57e975e1",
"metadata": {},
"source": [
"## 3. JuMP formulation"
]
},
{
"cell_type": "markdown",
"id": "0c24e30a",
"metadata": {},
"source": [
"For a linear program, we use ``GLPK``."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2cf5059b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"A JuMP Model\n",
"├ solver: GLPK\n",
"├ objective_sense: FEASIBILITY_SENSE\n",
"├ num_variables: 0\n",
"├ num_constraints: 0\n",
"└ Names registered in the model: none"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = Model(GLPK.Optimizer)"
]
},
{
"cell_type": "markdown",
"id": "6a7c5ca5",
"metadata": {},
"source": [
"We define the variables, from the data in ``foods``."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "9695a4f8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:\n",
" Dimension 1, Any[\"hamburger\", \"chicken\", \"hot dog\", \"fries\", \"macaroni\", \"pizza\", \"salad\", \"milk\", \"ice cream\"]\n",
"And data, a 9-element Vector{VariableRef}:\n",
" x[hamburger]\n",
" x[chicken]\n",
" x[hot dog]\n",
" x[fries]\n",
" x[macaroni]\n",
" x[pizza]\n",
" x[salad]\n",
" x[milk]\n",
" x[ice cream]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@variable(model, x[foods.name] >= 0)"
]
},
{
"cell_type": "markdown",
"id": "56d9a71e",
"metadata": {},
"source": [
"The objective is to minimize the cost."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e4226ef5",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$ 2.49 x_{hamburger} + 2.89 x_{chicken} + 1.5 x_{hot dog} + 1.89 x_{fries} + 2.09 x_{macaroni} + 1.99 x_{pizza} + 2.49 x_{salad} + 0.89 x_{milk} + 1.59 x_{ice cream} $"
],
"text/plain": [
"2.49 x[hamburger] + 2.89 x[chicken] + 1.5 x[hot dog] + 1.89 x[fries] + 2.09 x[macaroni] + 1.99 x[pizza] + 2.49 x[salad] + 0.89 x[milk] + 1.59 x[ice cream]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@objective(\n",
" model,\n",
" Min,\n",
" sum(food[\"cost\"] * x[food[\"name\"]] for food in eachrow(foods)),\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f77dbad3",
"metadata": {},
"source": [
"The formulation of the constraints uses the ``foods`` and the ``limits``. "
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "1a12ee90",
"metadata": {},
"outputs": [],
"source": [
"for limit in eachrow(limits)\n",
" intake = @expression(\n",
" model,\n",
" sum(food[limit[\"name\"]] * x[food[\"name\"]] for food in eachrow(foods)),\n",
" )\n",
" @constraint(model, limit.min <= intake <= limit.max)\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "719a2c35",
"metadata": {},
"source": [
"Let us look at the model."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8c640b3e",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$$ \\begin{aligned}\n",
"\\min\\quad & 2.49 x_{hamburger} + 2.89 x_{chicken} + 1.5 x_{hot dog} + 1.89 x_{fries} + 2.09 x_{macaroni} + 1.99 x_{pizza} + 2.49 x_{salad} + 0.89 x_{milk} + 1.59 x_{ice cream}\\\\\n",
"\\text{Subject to} \\quad & 410 x_{hamburger} + 420 x_{chicken} + 560 x_{hot dog} + 380 x_{fries} + 320 x_{macaroni} + 320 x_{pizza} + 320 x_{salad} + 100 x_{milk} + 330 x_{ice cream} \\in [1800, 2200]\\\\\n",
" & 24 x_{hamburger} + 32 x_{chicken} + 20 x_{hot dog} + 4 x_{fries} + 12 x_{macaroni} + 15 x_{pizza} + 31 x_{salad} + 8 x_{milk} + 8 x_{ice cream} \\in [91, Inf]\\\\\n",
" & 26 x_{hamburger} + 10 x_{chicken} + 32 x_{hot dog} + 19 x_{fries} + 10 x_{macaroni} + 12 x_{pizza} + 12 x_{salad} + 2.5 x_{milk} + 10 x_{ice cream} \\in [0, 65]\\\\\n",
" & 730 x_{hamburger} + 1190 x_{chicken} + 1800 x_{hot dog} + 270 x_{fries} + 930 x_{macaroni} + 820 x_{pizza} + 1230 x_{salad} + 125 x_{milk} + 180 x_{ice cream} \\in [0, 1779]\\\\\n",
" & x_{hamburger} \\geq 0\\\\\n",
" & x_{chicken} \\geq 0\\\\\n",
" & x_{hot dog} \\geq 0\\\\\n",
" & x_{fries} \\geq 0\\\\\n",
" & x_{macaroni} \\geq 0\\\\\n",
" & x_{pizza} \\geq 0\\\\\n",
" & x_{salad} \\geq 0\\\\\n",
" & x_{milk} \\geq 0\\\\\n",
" & x_{ice cream} \\geq 0\\\\\n",
"\\end{aligned} $$"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"print(model)"
]
},
{
"cell_type": "markdown",
"id": "544ea41a",
"metadata": {},
"source": [
"## 4. Solving the Linear Programming Problem"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "39838cf0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"solution_summary(; result = 1, verbose = false)\n",
"├ solver_name : GLPK\n",
"├ Termination\n",
"│ ├ termination_status : OPTIMAL\n",
"│ ├ result_count : 1\n",
"│ ├ raw_status : Solution is optimal\n",
"│ └ objective_bound : -Inf\n",
"├ Solution (result = 1)\n",
"│ ├ primal_status : FEASIBLE_POINT\n",
"│ ├ dual_status : FEASIBLE_POINT\n",
"│ ├ objective_value : 1.18289e+01\n",
"│ └ dual_objective_value : 1.18289e+01\n",
"└ Work counters\n",
" └ solve_time (sec) : 0.00000e+00"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"optimize!(model)\n",
"solution_summary(model)"
]
},
{
"cell_type": "markdown",
"id": "23cd847f",
"metadata": {},
"source": [
"Ok, the problem is solved. Let us now see what the solution looks like."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "07d30777",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hamburger = 0.6045138888888888\n",
"chicken = 0.0\n",
"hot dog = 0.0\n",
"fries = 0.0\n",
"macaroni = 0.0\n",
"pizza = 0.0\n",
"salad = 0.0\n",
"milk = 6.9701388888888935\n",
"ice cream = 2.591319444444441\n"
]
}
],
"source": [
"for food in foods.name\n",
" println(food, \" = \", value(x[food]))\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "1f040d74",
"metadata": {},
"source": [
"Interesting solution, we get to drink a lot of milk and eat ice cream."
]
},
{
"cell_type": "markdown",
"id": "3e4a0c4f",
"metadata": {},
"source": [
"## 5. Modification of the Problem"
]
},
{
"cell_type": "markdown",
"id": "31955f8d",
"metadata": {},
"source": [
"Suppose we want to eat one unit of salad, so we add this constraint."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "b61f79bb",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$$ x_{salad} \\geq 1 $$"
],
"text/plain": [
"x[salad] >= 1"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@constraint(model, x[\"salad\"] >= 1)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "0496f747",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"solution_summary(; result = 1, verbose = false)\n",
"├ solver_name : GLPK\n",
"├ Termination\n",
"│ ├ termination_status : INFEASIBLE\n",
"│ ├ result_count : 1\n",
"│ ├ raw_status : No feasible primal-dual solution exists.\n",
"│ └ objective_bound : -Inf\n",
"├ Solution (result = 1)\n",
"│ ├ primal_status : NO_SOLUTION\n",
"│ ├ dual_status : INFEASIBILITY_CERTIFICATE\n",
"│ ├ objective_value : 1.22686e+01\n",
"│ └ dual_objective_value : 1.71303e+00\n",
"└ Work counters\n",
" └ solve_time (sec) : 0.00000e+00"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"optimize!(model)\n",
"solution_summary(model)"
]
},
{
"cell_type": "markdown",
"id": "3f4a71b2",
"metadata": {},
"source": [
"Unfortunately, this requirement is not feasible!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Julia 1.12",
"language": "julia",
"name": "julia-1.12"
},
"language_info": {
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
"version": "1.12.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}