Planeación de menús - Solución#
Tags: Mezcla, Recurso
Show code cell source Hide code cell source
import os
# Por precaución, cambiamos el directorio activo de Python a aquel que contenga este notebook
if "optimizacion" in os.listdir():
os.chdir(r"optimizacion/Formularios/2. Planeacion de menus/")
Enunciado#
El personal técnico de un hospital quiere desarrollar un sistema computarizado de planificación de menús. Para comenzar, el hospital llevará a cabo la planeación de un menú de prueba para el almuerzo. Este menú debe incluir 100 gramos (g) de cada uno de los siguientes tres grupos alimenticios (\(T\)): frutas, verduras, y carnes. La siguiente tabla presenta el costo por cada 100 g de algunos alimentos sugeridos (\(A\)), así como el porcentaje de macronutrientes (carbohidratos, proteínas y grasas) y la cantidad, en miligramos (mg), de vitaminas que contienen 100 g de dichos alimentos.
Por cada 100g |
Vitaminas (mg) |
Carbohidratos (%) |
Proteína (%) |
Grasas (%) |
Costo ($) |
|
---|---|---|---|---|---|---|
Frutas |
||||||
Naranja |
50.00 |
8.90 |
0.80 |
0.00 |
570.00 |
|
Manzana |
5.00 |
10.50 |
0.30 |
0.00 |
650.00 |
|
Banano |
35.00 |
20.80 |
1.20 |
0.30 |
200.00 |
|
Pera |
12.00 |
11.70 |
0.40 |
0.10 |
550.00 |
|
Verduras |
||||||
Brócoli |
116.00 |
4.90 |
3.20 |
0.20 |
450.00 |
|
Espinaca |
52.00 |
4.10 |
2.50 |
0.30 |
600.00 |
|
Guisantes |
23.00 |
18.20 |
7.20 |
0.40 |
800.00 |
|
Pepino |
9.00 |
0.70 |
0.15 |
2.70 |
500.00 |
|
Calabacín |
21.00 |
7.30 |
4.20 |
0.10 |
450.00 |
|
Carnes |
||||||
Pollo |
61.00 |
1.00 |
23.00 |
2.00 |
1,420.00 |
|
Res |
31.00 |
2.00 |
18.00 |
20.00 |
4,800.00 |
|
Cerdo |
2.00 |
1.50 |
16.00 |
27.00 |
2,900.00 |
El menú del almuerzo debe contener una cantidad mínima de cada uno de los cuatro nutrientes (carbohidratos, proteínas, grasas y vitaminas) mostradas en la tabla. Estas cantidades mínimas son: 100 mg de vitaminas, 25 g de carbohidratos, 17 g de proteínas y 5 g de grasas. El equipo técnico del hospital desea incluir un modelo de optimización en el sistema para planear el menú del almuerzo al menor costo posible, ellos quieren saber cuantas porciones de 100g deberían incluir de cada alimento en el menú. Pueden haber porciones fraccionales.
Formulación#
a. Formula matemáticamente un modelo de optimización de forma general que represente la situación anterior. Defina y explique clara y rigurosamente:
Conjuntos
Parámetros
Variables de decisión
Restricciones
Naturaleza de las variables
Función objetivo
Conjuntos#
\(T\): conjunto de tipos de alimentos
\(A\): conjunto de alimentos
\(N\): conjunto de nutrientes
Parámetros#
\(l_n\): contenido mínimo del nutriente \(n\in N\) que debe tener el menú
\(k_{an}\): cantidad del nutriente \(n\in N\) que contiene 100 g del alimento \(a\in A\)
\(c_a\): costo por porción de 100g del alimento \(a\in A\)
\(p_{at}: \begin{cases}1\text{,}&\text{si el alimento }a\in A\text{ pertenece al tipo de alimento }t\in T\text{;} \\ 0\text{,}& \text{d.l.c.} \end{cases}\)
Variables de decisión#
\(x_a\): cantidad de porciones de 100g del alimento \(a\in A\) incluidas en el menú
Restricciones#
Debe incluirse en total exactamente una porción de 100g, de entre los alimentos que pertenecen a cada tipo de alimento \(t \in T\):
En el menú, la cantidad de cada nutriente \(n\in N\) incluida debe ser mayor al límite inferior \(l_n\):
Naturaleza de las Variables#
Solo pueden incluirse cantidades positivas de cada alimento:
Función objetivo#
Debe minimizarse el costo total del menú:
Implementación#
b. Resuelve el modelo planteado utilizando la librería de PuLP en Python. ¿Cuál es la solución óptima del problema?
Librerías#
Importa la librería pulp
para crear y resolver el modelo.
import pulp as lp
Conjuntos#
Define los conjuntos T
, A
y N
que representan respectivamente los tipos de alimento, los alimentos y los nutrientes.
Recuerda que por conveniencia de preservar el orden de los elementos de los conjuntos, no siempre deberás definirlos con el tipo set
.
# Tipos de Alimentos
T = ["Frutas", "Verduras", "Carnes"]
# Alimentos
A = [
"Naranja",
"Manzana",
"Banano",
"Pera",
"Brócoli",
"Espinaca",
"Guisantes",
"Pepino",
"Calabacín",
"Pollo",
"Res",
"Cerdo",
]
# Nutrientes
N = ["Vitaminas", "Carbohidratos", "Proteína", "Grasas"]
Parámetros#
Define o importa los parámetros del modelo.
# Contenido minimo del nutriente n en N que debe tener el menú
l = {"Vitaminas": 100 / 1000, "Carbohidratos": 25, "Proteína": 17, "Grasas": 5}
# Cantidad del nutriente n en N que tiene el alimento a en A
k = {
("Naranja", "Vitaminas"): 50,
("Naranja", "Carbohidratos"): 8.9,
("Naranja", "Proteína"): 0.8,
("Naranja", "Grasas"): 0,
("Manzana", "Vitaminas"): 5,
("Manzana", "Carbohidratos"): 10.5,
("Manzana", "Proteína"): 0.3,
("Manzana", "Grasas"): 0,
("Banano", "Vitaminas"): 35,
("Banano", "Carbohidratos"): 20.8,
("Banano", "Proteína"): 1.2,
("Banano", "Grasas"): 0.3,
("Pera", "Vitaminas"): 12,
("Pera", "Carbohidratos"): 11.7,
("Pera", "Proteína"): 0.4,
("Pera", "Grasas"): 0.1,
("Brócoli", "Vitaminas"): 116,
("Brócoli", "Carbohidratos"): 4.9,
("Brócoli", "Proteína"): 3.2,
("Brócoli", "Grasas"): 0.2,
("Espinaca", "Vitaminas"): 52,
("Espinaca", "Carbohidratos"): 4.1,
("Espinaca", "Proteína"): 2.5,
("Espinaca", "Grasas"): 0.3,
("Guisantes", "Vitaminas"): 23,
("Guisantes", "Carbohidratos"): 18.2,
("Guisantes", "Proteína"): 7.2,
("Guisantes", "Grasas"): 0.4,
("Pepino", "Vitaminas"): 9,
("Pepino", "Carbohidratos"): 0.7,
("Pepino", "Proteína"): 0.15,
("Pepino", "Grasas"): 2.7,
("Calabacín", "Vitaminas"): 21,
("Calabacín", "Carbohidratos"): 7.3,
("Calabacín", "Proteína"): 4.2,
("Calabacín", "Grasas"): 0.1,
("Pollo", "Vitaminas"): 61,
("Pollo", "Carbohidratos"): 1,
("Pollo", "Proteína"): 23,
("Pollo", "Grasas"): 2,
("Res", "Vitaminas"): 31,
("Res", "Carbohidratos"): 2,
("Res", "Proteína"): 18,
("Res", "Grasas"): 20,
("Cerdo", "Vitaminas"): 2,
("Cerdo", "Carbohidratos"): 1.5,
("Cerdo", "Proteína"): 16,
("Cerdo", "Grasas"): 27,
}
# Costo por porción (100g) de alimento a en A
c = {
"Naranja": 570,
"Manzana": 650,
"Banano": 200,
"Pera": 550,
"Brócoli": 450,
"Espinaca": 600,
"Guisantes": 800,
"Pepino": 500,
"Calabacín": 450,
"Pollo": 1420,
"Res": 4800,
"Cerdo": 2900,
}
# Si el alimento a pertenece al tipo de alimento t en T
p = {
("Naranja", "Frutas"): 1,
("Naranja", "Verduras"): 0,
("Naranja", "Carnes"): 0,
("Manzana", "Frutas"): 1,
("Manzana", "Verduras"): 0,
("Manzana", "Carnes"): 0,
("Banano", "Frutas"): 1,
("Banano", "Verduras"): 0,
("Banano", "Carnes"): 0,
("Pera", "Frutas"): 1,
("Pera", "Verduras"): 0,
("Pera", "Carnes"): 0,
("Brócoli", "Frutas"): 0,
("Brócoli", "Verduras"): 1,
("Brócoli", "Carnes"): 0,
("Espinaca", "Frutas"): 0,
("Espinaca", "Verduras"): 1,
("Espinaca", "Carnes"): 0,
("Guisantes", "Frutas"): 0,
("Guisantes", "Verduras"): 1,
("Guisantes", "Carnes"): 0,
("Pepino", "Frutas"): 0,
("Pepino", "Verduras"): 1,
("Pepino", "Carnes"): 0,
("Calabacín", "Frutas"): 0,
("Calabacín", "Verduras"): 1,
("Calabacín", "Carnes"): 0,
("Pollo", "Frutas"): 0,
("Pollo", "Verduras"): 0,
("Pollo", "Carnes"): 1,
("Res", "Frutas"): 0,
("Res", "Verduras"): 0,
("Res", "Carnes"): 1,
("Cerdo", "Frutas"): 0,
("Cerdo", "Verduras"): 0,
("Cerdo", "Carnes"): 1,
}
Para obtener resultados consistentes en unidades, adaptamos los valores del parámetro k
para las vitaminas contenidas en 100 g de alimento.
for a in A:
k[a, "Vitaminas"] /= 1000
Objeto del modelo#
Construye un problema al que luego agregarás las restricciones y la función objetivo.
problema = lp.LpProblem(name="Planeacion_menus", sense=lp.LpMinimize)
Variables de decisión#
Define las variables del problema de manera que estén contenidas en diccionarios indexados en los conjuntos de sus variables respectivas.
# Porción del alimento a en A
x = {
a: lp.LpVariable(
name=f"porcion_alimento_{a}", lowBound=0, upBound=None, cat=lp.LpContinuous
)
for a in A
}
Función objetivo#
Agrega al problema la función objetivo. Recuerda que al definir el problema, ya definiste si este es de maximización o minimización.
problema += sum(c[a] * x[a] for a in A), "costo_alimentos"
Restricciones#
Agrega al problema las restricciones del modelo.
# Se garantizan 100 gramos por cada tipo de alimento
for t in T:
problema += (
sum(x[a] for a in A if p[a, t] == 1) == 1,
f"100_gramos_tipo_alimento_{t}",
)
# Se garantiza el requerimiento mínimo de cada nutriente
for n in N:
problema += (
sum(k[a, n] * x[a] for a in A) >= l[n],
f"minimo_nutriente_{n}",
)
Resolver el problema#
Invoca el optimizador. Este paso le asigna un valor a las variables incluidas en las restricciones o función objetivo del modelo.
problema.solve()
Welcome to the CBC MILP Solver
Version: 2.10.8
Build Date: May 6 2022
command line - cbc /tmp/bbb05a8f4dc0417cab35dd897531895f-pulp.mps timeMode elapsed branch printingOptions all solution /tmp/bbb05a8f4dc0417cab35dd897531895f-pulp.sol (default strategy 1)
At line 2 NAME MODEL
At line 3 ROWS
At line 12 COLUMNS
At line 83 RHS
At line 91 BOUNDS
At line 92 ENDATA
Problem MODEL has 7 rows, 12 columns and 58 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Presolve 7 (0) rows, 12 (0) columns and 58 (0) elements
0 Obj 0 Primal inf 27.497304 (7)
8 Obj 2159.0559
Optimal - objective value 2159.0559
Optimal objective 2159.055923 - 8 iterations time 0.002
Option for printingOptions changed from normal to all
Total time (CPU seconds): 0.00 (Wallclock seconds): 0.00
1
Imprimir resultados#
Antes de estudiar el óptimo del modelo, identifica en el estado del optimizador si pudo resolver el problema.
f"Estado del optimizador: {lp.LpStatus[problema.status]}"
'Estado del optimizador: Optimal'
Identifica también el valor de la función objetivo.
f"Costo total de alimentos: {lp.value(problema.objective)}"
'Costo total de alimentos: 2159.0559213'
Por último, imprime de manera estructurada el valor de las variables de decisión y otras expresiones de interés.
for t in T:
print(t)
for a in A:
if p[a, t] == 1:
print("\t", a, ":", round(x[a].value() * 100, 1), "gramos")
Frutas
Naranja : 0.0 gramos
Manzana : 0.0 gramos
Banano : 100.0 gramos
Pera : 0.0 gramos
Verduras
Brócoli : 0.0 gramos
Espinaca : 0.0 gramos
Guisantes : 0.0 gramos
Pepino : 62.4 gramos
Calabacín : 37.6 gramos
Carnes
Pollo : 96.1 gramos
Res : 0.0 gramos
Cerdo : 3.9 gramos
c. Varios aspectos prácticos no fueron tenidos en cuenta en el modelo planteado anteriormente. Algunos de estos aspectos son: la inclusión de alimentos de los otros cuatro grupos alimenticios, la planeación de menús para desayunos, almuerzos y cenas; la planeación de menús para que los pacientes reciban menús variados de comida a lo largo de la semana; y menús especiales para pacientes con ciertas restricciones, entre otros. Discute en detalle cómo podría tener en cuenta estos aspectos dentro de un modelo de optimización en el sistema de planeación del hospital.
Créditos#
Equipo Principios de Optimización
Autores: Camilo Aguilar
Desarrollo: Camilo Aguilar, Alejandro Mantilla
Última fecha de modificación: 07/04/2023