{ "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": [ "
9×6 DataFrame
Rownamecostcaloriesproteinfatsodium
AnyAnyAnyAnyAnyAny
1hamburger2.494102426730
2chicken2.8942032101190
3hot dog1.556020321800
4fries1.89380419270
5macaroni2.093201210930
6pizza1.993201512820
7salad2.4932031121230
8milk0.8910082.5125
9ice cream1.59330810180
" ], "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": [ "
4×3 DataFrame
Rownameminmax
AnyAnyAny
1calories18002200
2protein91Inf
3fat065
4sodium01779
" ], "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 }