Introduction à la Recherche Opérationelle


M2 Statistique Science des Données (SSD)
Anatoli Juditsky
Franck Iutzeler

1- Récupération des données

In [1]:
import cvxpy as cp
import pandas as pd
import numpy as np
In [2]:
foods = pd.read_csv("data/food.dat",sep='\t')
n,p = foods.shape 

Le tableau foods contient tous les aliments présent à la carte d'un restaurant avec leurs informations nutritionelles.

In [3]:
foods
Out[3]:
Food Cal CalFat Fat SatFat Chol Sodium Carbo Protein VitA VitC Calcium Iron
0 1%_Low_Fat_Milk_Jug 1_carton_(236_ml) ... 100 20 2 1 10 125 12 8 10 4 30 0
1 Apple_Slices 1.2_oz_(34_g) ... 15 0 0 0 0 0 4 0 0 160 2 0
2 BBQ_Ranch_Burger 4.1_oz_(116_g) ... 350 140 16 6 45 680 37 16 4 0 20 15
3 Bacon,_Egg_&_Cheese_Bagel 7_oz_(199_g) ... 630 290 32 11 275 1490 57 30 20 15 20 20
4 Bacon,_Egg_&_Cheese_Bagel_with_Egg_Whites 7.2_... 580 230 26 9 60 1490 55 30 10 15 20 15
... ... ... ... ... ... ... ... ... ... ... ... ... ...
300 Vanilla_McCafe_Shake_(12_fl_oz_cup) 12_fl_oz ... 530 140 15 10 60 160 86 11 20 0 40 0
301 Vanilla_McCafe_Shake_(16_fl_oz_cup) 16_fl_oz ... 660 170 19 12 75 200 109 14 25 0 50 0
302 Vanilla_McCafe_Shake_(22_fl_oz_cup) 22_fl_oz ... 820 210 23 15 90 260 135 18 30 0 60 0
303 Vanilla_Reduced_Fat_Ice_Cream_Cone 3.7_oz_(105... 170 40 4 3 15 70 27 5 6 0 15 2
304 Whipped_Margarine_(1_pat) 1_pkg_(6_g) ... 40 40 4 1 0 55 0 0 4 0 0 0

305 rows × 13 columns

In [4]:
n
Out[4]:
305

Alimentation idéale en termes de nutriments

In [5]:
nutr = pd.read_csv("data/nutr_ideal.dat",sep=' ')

La tableau nutr contient les valeurs pour une alimentation optimale en terme de nutriments.

In [6]:
nutr
Out[6]:
CalFat Fat SatFat Chol Sodium Carbo Protein VitA VitC Calcium Iron
0 600 65 20 300 2400 300 50 100 100 100 100

2- Trouver un régime optimal

Combinaisons pour une alimentation pauvre en calories

Pour commencer, nous allons essayer de trouver une combinaison $x\in\mathbb{R}^n$ des aliments ($x_i$ correspondant à la quantité d'aliment $i$ prise) qui minimise le total de calories sous contrainte de rester idéal vis à vis des nutriments (CalFat, Fat, VitA, etc.)

Question: Formaliser et résoudre le problème décrit ci-dessus. Qu'observez-vous sur la solution?

  • Fonction objective linéaire à minimiser: $c^\mathrm{T}x$ où $c$ contient les calories par aliment
In [7]:
x = cp.Variable(n) 

cal = np.array(foods["Cal"]) # calories

diet1 = cp.Minimize(cal @ x)
  • Contrainte à vérifier: $x$ est positif (par de quantités négatives d'aliments) et $Ax=b$ où $A$ contient les valeurs des nutriments par aliment et $b$ contient les valeurs idéales
In [8]:
# Constraints matrix
A = np.array(foods.drop(columns=["Food","Cal"])).T
b = nutr.values.T.reshape((-1,))
In [9]:
A
Out[9]:
array([[ 20,   0, 140, ..., 210,  40,  40],
       [  2,   0,  16, ...,  23,   4,   4],
       [  1,   0,   6, ...,  15,   3,   1],
       ...,
       [  4, 160,   0, ...,   0,   0,   0],
       [ 30,   2,  20, ...,  60,  15,   0],
       [  0,   0,  15, ...,   0,   2,   0]])
In [10]:
b
Out[10]:
array([ 600,   65,   20,  300, 2400,  300,   50,  100,  100,  100,  100])
In [11]:
contrainte1 = x>= 0.0
contrainte2 = A @ x - b == 0.0
  • Resolution par CVXPY

Attention, utilisez le Solver "ECOS"

    prob.solve(verbose=True,solver="ECOS")

le solveur par défaut peut donner de fausses informations...

In [12]:
prob =  cp.Problem(diet1 , constraints = [contrainte1, contrainte2])
prob.solve(verbose=True)
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +2.013e+03  +2.013e+03  +3e+03  7e-01  8e-02  1e+00  1e+01    ---    ---    1  1  - |  -  - 
 1  +1.933e+03  +1.938e+03  +2e+03  1e+00  1e-01  7e+00  8e+00  0.4808  4e-01   1  1  1 |  0  0
 2  +1.177e+03  +1.192e+03  +4e+02  6e-01  8e-02  2e+01  1e+00  0.8448  3e-02   1  1  1 |  0  0
 3  +1.190e+03  +1.195e+03  +5e+01  2e-01  1e-02  6e+00  2e-01  0.8924  1e-02   1  1  1 |  0  0
 4  +1.607e+03  +1.621e+03  +2e+01  5e-01  1e-02  1e+01  7e-02  0.7035  2e-01   1  1  1 |  0  0
 5  +1.983e+03  +2.054e+03  +3e+00  5e-01  1e-02  7e+01  1e-02  0.8444  6e-03   1  1  1 |  0  0
 6  +1.941e+03  +7.645e+03  +4e-02  5e-01  1e-02  6e+03  1e-04  0.9890  2e-03   1  1  1 |  0  0
 7  +1.941e+03  +5.161e+05  +5e-04  5e-01  1e-02  5e+05  2e-06  0.9890  1e-04   1  1  1 |  0  0
 8  +1.941e+03  +4.636e+07  +5e-06  5e-01  1e-02  5e+07  2e-08  0.9890  1e-04   3  2  2 |  0  0
 9  +1.941e+03  +4.175e+09  +6e-08  5e-01  1e-02  4e+09  2e-10  0.9890  1e-04   3  2  2 |  0  0

PRIMAL INFEASIBLE (within feastol=5.1e-10).
Runtime: 0.004142 seconds.

Out[12]:
inf
In [13]:
prob.status
Out[13]:
'infeasible'

Relaxation des contraintes

Le problème précedent n'avait pas de point faisable avec les contraintes imposées. Afin d'obtenir des points faisable, nous allons remplacer la contrainte $$ Ax = b$$ par les deux contraintes de type boite $$ Ax \geq 0.8 b ~~ \text{ et } ~~ Ax \leq b $$ ainsi les contraintes de nutriments sont satisfaites entre 80 et 100% et le problème devient faisable.

Question: Définir les deux contraintes de boite et resoudre le problème. Vérifier qu'il est faisable.

In [14]:
contrainte1 = x>= 0.0
contrainte2a = A @ x >= 0.8*b
contrainte2b = A @ x <= b
In [15]:
prob =  cp.Problem(diet1 , constraints = [contrainte1, contrainte2a, contrainte2b])
prob.solve(verbose=True)
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +1.808e+03  +8.185e+02  +2e+04  2e-01  3e-01  1e+00  5e+01    ---    ---    1  1  - |  -  - 
 1  +1.736e+03  +1.219e+03  +8e+03  1e-01  1e-01  7e+00  3e+01  0.5884  2e-01   1  1  1 |  0  0
 2  +1.596e+03  +1.437e+03  +3e+03  3e-02  4e-02  6e+00  1e+01  0.8010  2e-01   1  1  1 |  0  0
 3  +1.293e+03  +1.005e+03  +3e+03  6e-02  8e-02  2e+01  9e+00  0.2964  6e-01   1  1  1 |  0  0
 4  +1.243e+03  +1.100e+03  +1e+03  3e-02  4e-02  1e+01  4e+00  0.5997  1e-01   1  1  1 |  0  0
 5  +1.329e+03  +1.216e+03  +1e+03  2e-02  3e-02  8e+00  4e+00  0.4499  7e-01   1  1  1 |  0  0
 6  +1.373e+03  +1.332e+03  +4e+02  9e-03  1e-02  3e+00  1e+00  0.6741  4e-03   1  1  1 |  0  0
 7  +1.398e+03  +1.378e+03  +2e+02  5e-03  5e-03  2e+00  6e-01  0.7476  3e-01   1  1  1 |  0  0
 8  +1.397e+03  +1.390e+03  +7e+01  2e-03  2e-03  5e-01  2e-01  0.6799  9e-02   1  1  1 |  0  0
 9  +1.400e+03  +1.397e+03  +4e+01  8e-04  9e-04  2e-01  1e-01  0.5746  2e-01   1  1  1 |  0  0
10  +1.400e+03  +1.397e+03  +4e+01  7e-04  9e-04  2e-01  1e-01  0.1143  6e-01   1  1  1 |  0  0
11  +1.398e+03  +1.396e+03  +2e+01  3e-04  4e-04  1e-01  5e-02  0.8303  3e-01   1  1  1 |  0  0
12  +1.398e+03  +1.397e+03  +5e+00  1e-04  1e-04  4e-02  2e-02  0.8814  3e-01   1  1  1 |  0  0
13  +1.397e+03  +1.397e+03  +1e-01  3e-06  3e-06  1e-03  4e-04  0.9774  4e-03   1  1  1 |  0  0
14  +1.397e+03  +1.397e+03  +2e-03  3e-08  4e-08  1e-05  5e-06  0.9890  1e-04   1  1  1 |  0  0
15  +1.397e+03  +1.397e+03  +2e-05  3e-10  4e-10  1e-07  5e-08  0.9890  1e-04   1  1  1 |  0  0
16  +1.397e+03  +1.397e+03  +2e-07  4e-12  5e-12  1e-09  6e-10  0.9890  1e-04   1  0  0 |  0  0

OPTIMAL (within feastol=4.6e-12, reltol=1.4e-10, abstol=2.0e-07).
Runtime: 0.008529 seconds.

Out[15]:
1397.048714782058
In [ ]:
 

Question: Affichez les valeur des nutriments pour la solution obtenue et la comparer à 80% et 100% des valeurs prescrites.

In [16]:
base = pd.DataFrame()
base["0.8*b"] = 0.8*b
base["solution"] = np.dot(A,x.value)
base["1.0*b"] = b
base
Out[16]:
0.8*b solution 1.0*b
0 480.0 480.000000 600
1 52.0 54.081051 65
2 16.0 20.000000 20
3 240.0 240.000000 300
4 1920.0 2200.662407 2400
5 240.0 244.665407 300
6 40.0 50.000000 50
7 80.0 100.000000 100
8 80.0 80.000000 100
9 80.0 80.000000 100
10 80.0 80.000000 100
In [17]:
prob.constraints[1].dual_value
Out[17]:
array([9.04095886e-01, 6.68613696e-10, 3.65769105e-10, 1.10803566e-01,
       4.13185892e-12, 3.42617062e-10, 1.37945790e-10, 6.87426914e-11,
       7.08292083e-02, 1.83366334e+00, 1.50989970e+01])
In [18]:
prob.constraints[2].dual_value
Out[18]:
array([1.15537285e-11, 1.25417123e-10, 6.25291078e+00, 2.31583678e-11,
       8.49475716e-12, 2.40337293e-11, 4.12069564e+00, 9.26963316e-01,
       8.05833164e-11, 7.02228555e-11, 7.05187857e-11])
In [ ]:
 

Question: Donner le nombre de calories totales et les aliments pour lesquelles la quantité est supérieure à 0.3.

In [19]:
xopt = x.value
xopt
Out[19]:
array([ 6.16117835e-12,  2.36008897e-01,  1.23342457e-11,  2.61398891e-12,
        1.19737825e-12,  8.57558587e-12,  5.07316519e-12,  3.34377193e-12,
        2.22466099e-12,  5.39549307e-12,  2.36453541e-12,  6.80724193e-12,
        4.09753443e-12,  6.24063695e-12,  3.21615499e-12,  9.23998509e-12,
        5.61732483e-12,  9.78024854e-13,  1.13548103e-12,  2.84695127e-12,
        3.58490779e-12,  7.66824827e-13,  9.44620335e-13,  1.23443315e-11,
       -3.08042389e-12, -2.44788750e-12, -1.97079930e-12,  1.68292411e-11,
       -2.78972604e-12, -3.28601293e-12, -2.76110869e-12, -2.28511002e-12,
       -2.39066486e-12, -2.32575653e-12,  2.23397345e-11,  8.52785166e-13,
       -3.73604532e-13, -5.60964779e-13,  4.42255054e-12,  3.07328759e-12,
       -3.17290131e-13,  5.12209769e-12,  7.67096918e-12,  3.69308368e+00,
       -2.16115051e-12, -2.26698427e-12, -2.09346160e-12,  2.86086303e-12,
        9.87991186e-13, -2.88509065e-12, -2.27403922e-12, -1.01942491e-12,
        3.20976010e+00,  3.20976010e+00,  3.20976010e+00,  1.26023122e-10,
        2.38440959e-11,  1.21931928e-11,  3.20976010e+00,  1.58348534e+00,
        1.24456183e+00,  1.46213427e+00,  1.76702209e+00,  2.25730698e-10,
        5.18716071e-11,  7.04098515e-11,  1.08489533e-10,  9.94889879e-12,
        3.78273758e-12,  1.09302162e-12, -2.47455494e-12, -1.64376542e-12,
       -7.62561910e-13,  3.09360337e+01,  4.99223167e-01, -3.34094703e-12,
       -3.14299743e-12,  5.48535115e-12,  1.24818967e+00,  3.40958808e-12,
       -3.17172334e-12, -3.31470759e-12, -3.12688394e-12, -2.94223588e-12,
       -2.98109997e-12, -2.96570338e-12, -2.84392583e-12, -3.09340061e-12,
       -3.05530910e-12, -3.01865877e-12, -2.60468220e-12, -2.63870288e-12,
       -2.54099954e-12,  7.38492949e-12,  1.21026940e-11,  7.47423636e-12,
        1.86086153e-11,  2.29388284e-11,  1.53428972e+00,  1.30787774e-11,
       -2.78972604e-12, -3.15509579e-12, -2.76110869e-12,  7.41277272e-12,
        1.05149443e-12,  1.98434102e-12,  4.06932763e-12,  8.10709550e-12,
        2.95547397e-11,  5.25857944e-12,  7.94005764e-12, -2.82541360e-12,
       -1.74873638e-12, -1.87461969e-12, -7.29080740e-13, -5.63334845e-13,
       -4.08347625e-13,  7.73905524e-13,  2.24530815e-13,  1.92720445e-10,
       -2.44202256e-12,  1.81956590e-11,  3.13811898e-12, -2.33762682e-12,
       -2.39026386e-12, -2.10153601e-12, -3.38853641e-12, -2.23590447e-12,
       -1.41946166e-12, -2.90592186e-12, -1.88748789e-12, -7.43514500e-13,
       -3.06107405e-12, -2.23590447e-12, -1.57499497e-12, -3.32608096e-12,
       -2.53799856e-12, -2.12037826e-12,  8.83549136e-13,  2.82058530e-12,
        6.30941146e-12, -1.92553651e-12, -1.34864021e-12, -1.45837806e-12,
       -7.37995679e-13, -2.20842075e-13,  2.90531128e-14, -1.54822103e-12,
       -1.57165299e-12, -9.46721480e-13,  2.12050661e+00,  1.58348534e+00,
        1.76702209e+00,  1.76702209e+00,  9.92161034e-11,  1.29875274e-11,
        2.32083248e-11,  6.78767546e-14, -4.18483233e-13, -5.88745995e-13,
        7.20017122e-13, -1.04299350e-12, -1.54405442e-12, -5.78534774e-13,
        2.06753045e-11, -3.89417737e-12, -3.85225611e-12, -3.34320365e-12,
       -1.13768534e-12, -1.06851280e-12, -5.31535858e-13,  1.41326251e-11,
        1.69482812e-11, -2.82347663e-12, -2.88761613e-12, -1.50879776e-12,
       -1.91006319e-12, -2.50087001e-12,  6.50124348e-12,  1.24956777e-12,
        1.69843264e-13,  2.91451351e-12,  1.00511804e-12,  1.02165927e-11,
       -9.51237385e-14,  1.10500939e-12,  2.38454210e-12, -1.92897465e-12,
       -1.84634750e-12, -1.29453868e-12, -8.24384410e-13, -8.13343499e-13,
        7.84354883e-13,  3.22429737e-11,  6.99316902e-11,  2.94864327e-11,
        1.22184729e-11,  1.10328081e-11, -1.75235878e-12, -2.10385878e-12,
       -1.87878921e-12, -1.48876565e-12, -1.74554876e-12, -6.78961562e-13,
       -1.59274281e-12, -9.85634693e-13, -1.48304624e-12, -1.75235878e-12,
       -2.10385878e-12, -1.88019971e-12,  2.35143568e-12,  2.06577120e-12,
        3.51179569e-12,  6.88341483e-13,  5.82842842e-13,  1.13465807e-12,
        3.40291599e-11,  1.03253085e-11,  1.00318316e-12,  2.45662114e-12,
        6.49558385e-12,  2.25302609e-11,  3.49419749e-12, -1.05482881e-13,
        3.45718954e-13,  5.98598064e-12,  1.56962099e-12,  2.49045459e-12,
        1.36320732e-11,  2.65808422e-12,  4.00041858e-12,  2.07352117e-12,
        3.67904256e-12,  3.19977572e-12,  5.93200467e-12,  4.80332374e-12,
        1.12937293e-11,  1.91002620e-12,  3.39589900e-12,  4.91230156e-12,
        3.11674870e-13,  1.00510185e-12,  7.36607766e-12,  7.26815553e-12,
        1.98941909e-09,  9.34357613e-12,  5.19198443e-12,  8.14805549e-12,
        3.09360337e+01,  5.78820702e-13,  7.15052312e-01,  3.50352435e-12,
        1.55302326e-12,  5.57352762e-12,  7.38486065e-12,  6.75602255e-12,
        1.08970082e-11,  2.85565957e-12,  4.25809692e-12,  2.44551606e-01,
        3.50684183e-12,  5.80119532e-12,  9.65076713e-12,  3.26151998e-12,
        1.54723575e+00,  4.53966281e-12, -3.60870982e-12, -2.98190737e-12,
       -4.49559903e-13, -2.08472297e-12, -3.01937752e-12,  4.01627085e-12,
        5.63402070e-12,  5.84311467e-12,  6.01089751e-11, -2.88743722e-13,
        1.86829924e-12,  7.55297756e-13, -2.98009706e-12, -2.35819135e-12,
       -1.29348076e-12,  3.61104409e-12,  1.42229007e-11,  3.11873769e-11,
       -2.42912814e-12, -1.23332863e-12, -1.71345695e-12, -3.61728175e-12,
       -3.42359694e-12, -3.20495855e-12,  1.87401983e-11, -3.41293125e-12,
        6.59934855e-11,  1.78138940e-11, -4.79356311e-13, -3.13963560e-12,
       -2.70408247e-12, -2.46894525e-12,  1.95902667e-11,  5.49513434e-02,
       -3.59583615e-12, -3.43476539e-12, -3.23316142e-12, -1.79923746e-13,
        4.64769620e-11])
In [20]:
for j in range(n):
    if xopt[j]>0.3:
        print("{} \t ({} cal) \t : \t {}".format(foods.iloc[j,0],foods.iloc[j,1],xopt[j]))
Chocolate_Chip_Cookie 1_cookie_(33_g)                      	 (160 cal) 	 : 	 3.6930836797788635
Coffee_(Large) 16_fl_oz_cup                                	 (0 cal) 	 : 	 3.2097600966773765
Coffee_(Medium) 16_fl_oz_cup                               	 (0 cal) 	 : 	 3.2097600966773765
Coffee_(Small) 12_fl_oz_cup                                	 (0 cal) 	 : 	 3.2097600966773765
Dasani_Water 16.9_fl_oz                                    	 (0 cal) 	 : 	 3.2097600966773765
Diet_Coke_(Child) 12_fl_oz_cup                             	 (0 cal) 	 : 	 1.583485336944826
Diet_Coke_(Large) 30_fl_oz_cup                             	 (0 cal) 	 : 	 1.2445618254039157
Diet_Coke_(Medium) 21_fl_oz_cup                            	 (0 cal) 	 : 	 1.4621342658272247
Diet_Coke_(Small) 16_fl_oz_cup                             	 (0 cal) 	 : 	 1.7670220854987486
EQUAL_0_Calorie_Sweetener 1_pkg_(1.0_g)                    	 (0 cal) 	 : 	 30.93603366965125
Egg_McMuffin 4.8_oz_(135_g)                                	 (290 cal) 	 : 	 0.49922316684071805
Fat_Free_Chocolate_Milk_Jug 1_carton_(236_ml)              	 (130 cal) 	 : 	 1.2481896702955813
Hamburger 3.5_oz_(100_g)                                   	 (250 cal) 	 : 	 1.5342897229643404
Iced_Tea_(Child) 12_fl_oz_cup                              	 (0 cal) 	 : 	 2.1205066117077562
Iced_Tea_(Large) 30_fl_oz_cup                              	 (0 cal) 	 : 	 1.583485336944826
Iced_Tea_(Medium) 21_fl_oz                                 	 (0 cal) 	 : 	 1.7670220854987486
Iced_Tea_(Small) 16_fl_oz_cup                              	 (0 cal) 	 : 	 1.7670220854987486
SPLENDA_No_Calorie_Sweetener 1_pkg_(1.0_g)                 	 (0 cal) 	 : 	 30.93603366965125
Salt_Packet 1_pkg_(0.7_g)                                  	 (0 cal) 	 : 	 0.7150523116395081
Side_Salad 3.1_oz_(87_g)                                   	 (20 cal) 	 : 	 1.5472357496715032

On remarque que la solution n'est pas pratique!

D'autres contraintes

On va imposer:

  • pas de EQUAL_0_Calorie_Sweetener 1_pkg_(1.0_g) (item $73$) ni de SPLENDA_No_Calorie_Sweetener 1_pkg_(1.0_g) : 30.9360336697206 (item $248$) car c'est pas très bon
  • pas plus de 10 unités dans le menu, pas plus de 2 unités par item
  • au moins un petit déjeuner (items 3 à 22)

Relacher au besoin les contraintes de nutrition...

Question : Implémenter ces contraintes une par une et regarder la valeur optimale de la fonction (le nombre de calories). Cette valeur augmente-t-elle ou diminue-t-elle ? Pourquoi ?

In [21]:
contrainte3a = x[73] == 0
contrainte3b = x[248] == 0
In [22]:
prob2= cp.Problem(diet1 , constraints = [contrainte1, contrainte2a,contrainte2b, contrainte3a, contrainte3b])
prob2.solve(verbose=True,solver="ECOS")
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +1.808e+03  +8.182e+02  +2e+04  2e-01  3e-01  1e+00  5e+01    ---    ---    1  1  - |  -  - 
 1  +1.746e+03  +1.231e+03  +8e+03  1e-01  1e-01  7e+00  3e+01  0.5836  2e-01   1  1  1 |  0  0
 2  +1.620e+03  +1.463e+03  +3e+03  3e-02  4e-02  5e+00  1e+01  0.7975  3e-01   1  1  1 |  0  0
 3  +1.605e+03  +1.557e+03  +1e+03  1e-02  1e-02  3e+00  4e+00  0.8698  3e-01   1  1  1 |  0  0
 4  +1.595e+03  +1.578e+03  +4e+02  3e-03  4e-03  8e-01  1e+00  0.8209  2e-01   1  1  1 |  0  0
 5  +1.589e+03  +1.583e+03  +2e+02  1e-03  2e-03  3e-01  5e-01  0.6595  8e-02   1  1  1 |  0  0
 6  +1.586e+03  +1.584e+03  +7e+01  5e-04  7e-04  1e-01  2e-01  0.6479  1e-01   1  1  1 |  0  0
 7  +1.585e+03  +1.585e+03  +3e+01  2e-04  3e-04  4e-02  8e-02  0.7791  2e-01   1  1  1 |  0  0
 8  +1.585e+03  +1.585e+03  +2e+00  2e-05  2e-05  3e-03  7e-03  0.9606  5e-02   1  1  1 |  0  0
 9  +1.585e+03  +1.585e+03  +7e-01  5e-06  7e-06  9e-04  2e-03  0.7543  1e-01   1  1  1 |  0  0
10  +1.585e+03  +1.585e+03  +1e-02  7e-08  1e-07  1e-05  3e-05  0.9890  3e-03   1  1  1 |  0  0
11  +1.585e+03  +1.585e+03  +1e-04  8e-10  1e-09  1e-07  4e-07  0.9890  1e-04   1  1  1 |  0  0
12  +1.585e+03  +1.585e+03  +1e-06  9e-12  1e-11  1e-09  4e-09  0.9890  1e-04   1  0  0 |  0  0

OPTIMAL (within feastol=1.2e-11, reltol=8.2e-10, abstol=1.3e-06).
Runtime: 0.006630 seconds.

Out[22]:
1584.5315377055053
In [23]:
print("Sans les contraintes en plus:", prob.value, "Avec les contraintes en plus:", prob2.value)
Sans les contraintes en plus: 1397.048714782058 Avec les contraintes en plus: 1584.5315377055053
In [24]:
x.value[73]
Out[24]:
1.065534174500681e-13
In [25]:
prob2.constraints[3].dual_value
Out[25]:
16.63390287684338

Contrainte suivante: pas plus de 10 unités dans le menu, pas plus de 2 unités par item

In [26]:
contrainte4a = cp.sum(x) <= 10
contrainte4b = x <= 2.0
In [27]:
prob3= cp.Problem(diet1 , constraints = [contrainte1, contrainte2a,contrainte2b, contrainte3a, contrainte3b, contrainte4a, contrainte4b])
prob3.solve(verbose=True,solver="ECOS")
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +1.832e+03  -8.810e+04  +1e+05  2e-01  7e-02  1e+00  2e+02    ---    ---    1  1  - |  -  - 
 1  +1.647e+03  -1.282e+04  +3e+04  3e-02  1e-02  2e+01  4e+01  0.8943  1e-01   1  1  1 |  0  0
 2  +1.648e+03  -4.603e+03  +1e+04  1e-02  5e-03  9e+00  2e+01  0.6298  1e-01   0  0  0 |  0  0
 3  +1.639e+03  -2.981e+02  +4e+03  3e-03  2e-03  4e+00  6e+00  0.8822  2e-01   0  0  0 |  0  0
 4  +1.630e+03  +9.231e+02  +1e+03  1e-03  6e-04  1e+00  2e+00  0.6739  8e-02   0  0  0 |  0  0
 5  +1.626e+03  +1.364e+03  +6e+02  4e-04  2e-04  5e-01  9e-01  0.6477  7e-02   1  0  1 |  0  0
 6  +1.623e+03  +1.560e+03  +2e+02  1e-04  7e-05  1e-01  2e-01  0.9890  3e-01   1  1  1 |  0  0
 7  +1.614e+03  +1.551e+03  +1e+02  1e-04  6e-05  1e-01  2e-01  0.2259  8e-01   1  1  1 |  0  0
 8  +1.611e+03  +1.571e+03  +9e+01  6e-05  4e-05  8e-02  1e-01  0.3810  9e-02   1  1  1 |  0  0
 9  +1.612e+03  +1.586e+03  +6e+01  4e-05  3e-05  5e-02  1e-01  0.9890  7e-01   1  1  1 |  0  0
10  +1.608e+03  +1.597e+03  +3e+01  2e-05  1e-05  2e-02  4e-02  0.6728  2e-01   1  1  1 |  0  0
11  +1.608e+03  +1.598e+03  +2e+01  1e-05  9e-06  2e-02  3e-02  0.3824  5e-01   1  1  1 |  0  0
12  +1.605e+03  +1.604e+03  +4e+00  2e-06  2e-06  3e-03  6e-03  0.9076  8e-02   1  1  1 |  0  0
13  +1.605e+03  +1.605e+03  +7e-02  5e-08  3e-08  5e-05  1e-04  0.9864  6e-03   1  1  1 |  0  0
14  +1.605e+03  +1.605e+03  +8e-04  5e-10  3e-10  6e-07  1e-06  0.9890  1e-04   1  0  0 |  0  0
15  +1.605e+03  +1.605e+03  +9e-06  6e-12  4e-12  7e-09  1e-08  0.9890  1e-04   1  0  0 |  0  0

OPTIMAL (within feastol=5.9e-12, reltol=5.6e-09, abstol=9.0e-06).
Runtime: 0.008516 seconds.

Out[27]:
1604.7522340116125
In [28]:
print("Valeur optimales successives:", prob.value, prob2.value, prob3.value)
Valeur optimales successives: 1397.048714782058 1584.5315377055053 1604.7522340116125

Contrainte suivante: au moins un petit déjeuner (items 3 à 22)

In [29]:
dej = np.zeros(n)
dej[3:23] = 1.0
In [30]:
contrainte5 = dej@x >=1.0
In [31]:
prob4= cp.Problem(diet1 , constraints = [contrainte1, contrainte2a,contrainte2b, contrainte3a, contrainte3b, contrainte4a, contrainte4b, contrainte5])
prob4.solve(verbose=True)
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +1.832e+03  -8.800e+04  +1e+05  2e-01  7e-02  1e+00  2e+02    ---    ---    1  1  - |  -  - 
 1  +1.649e+03  -1.372e+04  +3e+04  3e-02  1e-02  2e+01  5e+01  0.8864  1e-01   1  1  0 |  0  0
 2  +1.651e+03  -4.821e+03  +1e+04  1e-02  6e-03  9e+00  2e+01  0.6348  1e-01   0  0  0 |  0  0
 3  +1.642e+03  -5.917e+02  +4e+03  4e-03  2e-03  4e+00  7e+00  0.8023  2e-01   0  0  0 |  0  0
 4  +1.637e+03  +8.524e+02  +2e+03  1e-03  6e-04  2e+00  2e+00  0.7060  8e-02   0  0  0 |  0  0
 5  +1.634e+03  +1.331e+03  +6e+02  6e-04  2e-04  7e-01  9e-01  0.7119  1e-01   1  0  0 |  0  0
 6  +1.633e+03  +1.509e+03  +2e+02  2e-04  8e-05  3e-01  3e-01  0.6770  9e-02   1  0  0 |  0  0
 7  +1.633e+03  +1.580e+03  +8e+01  1e-04  3e-05  2e-01  1e-01  0.6623  5e-02   1  1  1 |  0  0
 8  +1.635e+03  +1.590e+03  +6e+01  1e-04  2e-05  1e-01  9e-02  0.3271  1e-01   1  1  1 |  0  0
 9  +1.647e+03  +1.613e+03  +5e+01  7e-05  2e-05  9e-02  8e-02  0.5772  8e-01   1  1  1 |  0  0
10  +1.650e+03  +1.640e+03  +1e+01  2e-05  5e-06  3e-02  2e-02  0.7609  1e-02   1  1  1 |  0  0
11  +1.651e+03  +1.651e+03  +1e+00  2e-06  5e-07  3e-03  2e-03  0.9255  3e-02   1  1  1 |  0  0
12  +1.652e+03  +1.652e+03  +1e-02  2e-08  5e-09  3e-05  2e-05  0.9890  1e-04   1  1  1 |  0  0
13  +1.652e+03  +1.652e+03  +2e-04  3e-10  6e-11  4e-07  2e-07  0.9890  1e-04   1  1  1 |  0  0
14  +1.652e+03  +1.652e+03  +2e-06  3e-12  7e-13  4e-09  3e-09  0.9890  1e-04   1  0  0 |  0  0

OPTIMAL (within feastol=3.0e-12, reltol=1.0e-09, abstol=1.7e-06).
Runtime: 0.008105 seconds.

Out[31]:
1651.6421331297968
In [32]:
print("Valeur optimales successives:", prob.value, prob2.value, prob3.value,prob4.value)
Valeur optimales successives: 1397.048714782058 1584.5315377055053 1604.7522340116125 1651.6421331297968
In [33]:
xoptNew = x.value

for j in range(n):
    if xoptNew[j]>0.3:
        print("[{:3d}]{} \t ({} cal) \t : \t {}".format(j,foods.iloc[j,0],foods.iloc[j,1],xoptNew[j]))
[ 14]Baked_Hot_Apple_Pie 2.7_oz_(77_g)                          	 (250 cal) 	 : 	 0.9999999996684428
[ 43]Chocolate_Chip_Cookie 1_cookie_(33_g)                      	 (160 cal) 	 : 	 1.0043893797538552
[ 52]Coffee_(Large) 16_fl_oz_cup                                	 (0 cal) 	 : 	 0.36902748167124416
[ 53]Coffee_(Medium) 16_fl_oz_cup                               	 (0 cal) 	 : 	 0.36902748167124416
[ 54]Coffee_(Small) 12_fl_oz_cup                                	 (0 cal) 	 : 	 0.36902748167124416
[ 58]Dasani_Water 16.9_fl_oz                                    	 (0 cal) 	 : 	 0.36902748167124416
[ 74]Egg_McMuffin 4.8_oz_(135_g)                                	 (290 cal) 	 : 	 0.6330076555098998
[ 78]Fat_Free_Chocolate_Milk_Jug 1_carton_(236_ml)              	 (130 cal) 	 : 	 0.6634884734115589
[ 98]Hamburger 3.5_oz_(100_g)                                   	 (250 cal) 	 : 	 1.0065487922780203
[121]Hotcakes 5.3_oz_(151_g)                                    	 (350 cal) 	 : 	 1.788221746628979
[264]Side_Salad 3.1_oz_(87_g)                                   	 (20 cal) 	 : 	 1.745816776486434

3- D'autres problèmes

Beaucoup d'autres problèmes de ce genre existent. Ci-dessous vous trouverez un nouveau problème mais vous êtes également invités à inventer vos propres problèmes et chercher si ils sont solvables par CVXPY !

Question : Quels sont les aliments à choisir pour obtenir au moins les nutriments recommendés en un minimum d'unités ?

In [34]:
y  = cp.Variable(n) 

efficace = cp.Minimize( cp.sum(y))
In [35]:
contrainte = A@y >= b
In [36]:
problemEff = cp.Problem(efficace , constraints = [contrainte, y>=0])
problemEff.solve(verbose=True)
ECOS 2.0.7 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +5.441e+00  +2.937e+02  +1e+03  3e-01  3e+00  1e+00  4e+00    ---    ---    1  1  - |  -  - 
 1  +1.631e+01  +1.518e+02  +6e+02  2e-01  2e+00  3e+00  2e+00  0.6946  2e-01   1  1  1 |  0  0
 2  +1.260e+01  +1.164e+02  +5e+02  1e-01  1e+00  2e+00  2e+00  0.2432  6e-01   1  1  1 |  0  0
 3  +1.007e+01  +6.130e+01  +3e+02  5e-02  7e-01  1e+00  1e+00  0.4805  2e-01   1  1  1 |  0  0
 4  +7.483e+00  +2.498e+01  +2e+02  1e-02  3e-01  4e-01  5e-01  0.7136  3e-01   1  1  1 |  0  0
 5  +6.284e+00  +1.795e+01  +1e+02  7e-03  2e-01  3e-01  4e-01  0.4344  6e-01   1  1  1 |  0  0
 6  +5.663e+00  +1.067e+01  +7e+01  2e-03  8e-02  1e-01  2e-01  0.5264  1e-01   1  1  1 |  0  0
 7  +5.148e+00  +9.870e+00  +6e+01  2e-03  8e-02  8e-02  2e-01  0.4406  7e-01   1  1  1 |  0  0
 8  +4.206e+00  +5.930e+00  +2e+01  7e-04  3e-02  3e-02  7e-02  0.7410  1e-01   1  1  1 |  0  0
 9  +4.117e+00  +5.236e+00  +1e+01  4e-04  2e-02  9e-03  4e-02  0.8922  6e-01   1  1  1 |  0  0
10  +3.825e+00  +4.540e+00  +9e+00  3e-04  1e-02  5e-03  3e-02  0.4825  2e-01   1  1  1 |  0  0
11  +3.799e+00  +4.490e+00  +9e+00  3e-04  1e-02  5e-03  3e-02  0.0238  9e-01   1  1  1 |  0  0
12  +3.821e+00  +4.542e+00  +9e+00  2e-04  1e-02  5e-03  3e-02  0.0295  1e+00   1  1  1 |  0  0
13  +3.396e+00  +3.635e+00  +3e+00  7e-05  5e-03  1e-03  9e-03  0.9083  3e-01   1  1  1 |  0  0
14  +3.310e+00  +3.430e+00  +1e+00  4e-05  2e-03  5e-04  5e-03  0.5919  2e-01   1  1  1 |  0  0
15  +3.302e+00  +3.408e+00  +1e+00  3e-05  2e-03  5e-04  4e-03  0.4358  7e-01   1  1  1 |  0  0
16  +3.279e+00  +3.355e+00  +9e-01  2e-05  1e-03  3e-04  3e-03  0.4267  3e-01   1  1  1 |  0  0
17  +3.276e+00  +3.353e+00  +9e-01  2e-05  2e-03  4e-04  3e-03  0.0757  9e-01   1  1  1 |  0  0
18  +3.209e+00  +3.211e+00  +2e-02  6e-07  4e-05  9e-06  7e-05  0.9766  1e-03   1  1  1 |  0  0
19  +3.207e+00  +3.207e+00  +2e-04  6e-09  4e-07  1e-07  8e-07  0.9890  1e-04   1  0  0 |  0  0
20  +3.207e+00  +3.207e+00  +3e-06  7e-11  5e-09  1e-09  9e-09  0.9890  1e-04   1  0  0 |  0  0
21  +3.207e+00  +3.207e+00  +3e-08  8e-13  5e-11  1e-11  1e-10  0.9890  1e-04   1  0  0 |  0  0

OPTIMAL (within feastol=5.1e-11, reltol=9.5e-09, abstol=3.1e-08).
Runtime: 0.005879 seconds.

Out[36]:
3.2072772481674483
In [37]:
xoptEff = y.value

for j in range(n):
    if xoptEff[j]>0.3:
        print("[{:3d}]{} \t ({} cal) \t : \t {}".format(j,foods.iloc[j,0],foods.iloc[j,1],xoptEff[j]))
[ 19]Big_Breakfast_with_Hotcakes_(Large_Size_Biscuit) 15.3_oz_(434_g) 	 (1150 cal) 	 : 	 2.273024165714703
[174]McFlurry_with_M&M'S_Candies_(16_fl_oz_cup) 16.2_oz_(460_g) 	 (930 cal) 	 : 	 0.37883735982826483
[241]Premium_Southwest_Salad_with_Grilled_Chicken 11.8_oz_(335_g) 	 (290 cal) 	 : 	 0.35271064428563503

Question : Quels nutriments sont les plus dur à obtenir (on pourra regarder la valeur de la variable duale) ?

In [38]:
for i in range(len(b)):
    print("{} \t for {} \t {}".format(A.dot(y.value)[i],b[i],nutr.columns[i]))
1361.9856303910965 	 for 600 	 CalFat
151.7047681326478 	 for 65 	 Fat
53.74265185722111 	 for 20 	 SatFat
1360.0914430177845 	 for 300 	 Chol
5464.794252713973 	 for 2400 	 Sodium
343.2323206211901 	 for 300 	 Carbo
98.92880472092085 	 for 50 	 Protein
100.00000000302231 	 for 100 	 VitA
100.0000000047785 	 for 100 	 VitC
100.00000000542161 	 for 100 	 Calcium
100.00000000069555 	 for 100 	 Iron
In [39]:
problemEff.constraints[0].dual_value
Out[39]:
array([2.41411413e-14, 2.09469785e-13, 4.06955292e-13, 3.10180270e-14,
       5.42025090e-15, 3.13134343e-12, 2.30460188e-13, 3.24226155e-03,
       2.43902439e-03, 1.09171433e-02, 1.54743433e-02])

4- Programmation entière [*]

Les solutions obtenues précédemment sont des solutions continues, alors que l'on commande généralement un nombre entier d'items. Les problèmes d'optimisation avec variables entières appellés (Mixed) Integer Programs, se résolvent avec des méthodes et des solveurs différents. CVXPY appellant différents solveurs nativement, cette opération est transparente ici.

Les différences pour la programmation en nombre entier

  • le type de la variable qui doit être précisé lors de la déclaration à CVXPY

      x = cp.Variable(n,integer=True) 
  • le solver (qui doit etre capable de faire des MIP) (mais c'est transparent pour nous ici)

Question: Reprendre le problème relaxé plus haut (minimisation des calories entre 80% et 100% des contraintes de nutrition) en ajoutant que la variable $x$ est maintenant à valeur entières. Le problème est-il toujours faisable?

In [40]:
x = cp.Variable(n,integer=True) 

cal = np.array(foods["Cal"]) # calories

diet_int = cp.Minimize(cal@x)
In [41]:
# Constraints matrix
A = np.array(foods.drop(columns=["Food","Cal"])).T
b = nutr.values.T.reshape((-1,))
In [42]:
contrainte1 = x>= 0.0
contrainte2a = A @ x >= 0.8*b
contrainte2b = A @ x <= b
In [43]:
prob =  cp.Problem(diet_int , constraints = [contrainte1, contrainte2a, contrainte2b])
prob.solve(verbose=True)
      0: obj =   0.000000000e+00 inf =   2.012e+01 (11)
     28: obj =   1.970065220e+03 inf =   0.000e+00 (0)
*    39: obj =   1.433096050e+03 inf =   0.000e+00 (0)
Long-step dual simplex will be used
+    39: mip =     not found yet >=              -inf        (1; 0)
+   275: mip =     not found yet >=     tree is empty        (0; 47)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_6490/2037298910.py in <module>
      1 prob =  cp.Problem(diet_int , constraints = [contrainte1, contrainte2a, contrainte2b])
----> 2 prob.solve(verbose=True)

~/.local/lib/python3.8/site-packages/cvxpy/problems/problem.py in solve(self, *args, **kwargs)
    394         else:
    395             solve_func = Problem._solve
--> 396         return solve_func(self, *args, **kwargs)
    397 
    398     @classmethod

~/.local/lib/python3.8/site-packages/cvxpy/problems/problem.py in _solve(self, solver, warm_start, verbose, gp, qcp, requires_grad, enforce_dpp, **kwargs)
    744         data, solving_chain, inverse_data = self.get_problem_data(
    745             solver, gp, enforce_dpp)
--> 746         solution = solving_chain.solve_via_data(
    747             self, data, warm_start, verbose, kwargs)
    748         self.unpack_results(solution, solving_chain, inverse_data)

~/.local/lib/python3.8/site-packages/cvxpy/reductions/solvers/solving_chain.py in solve_via_data(self, problem, data, warm_start, verbose, solver_opts)
    323             a Solution object.
    324         """
--> 325         return self.solver.solve_via_data(data, warm_start, verbose,
    326                                           solver_opts, problem._solver_cache)

~/.local/lib/python3.8/site-packages/cvxpy/reductions/solvers/conic_solvers/glpk_mi_conif.py in solve_via_data(self, data, warm_start, verbose, solver_opts, solver_cache)
     87         # Convert CVXOPT results to solution format.
     88         solution = {}
---> 89         status = self.STATUS_MAP[results_dict['status']]
     90         solution[s.STATUS] = status
     91         if solution[s.STATUS] in s.SOLUTION_PRESENT:

KeyError: 'infeasible problem'
In [ ]:
 

Relaxation des contraintes

Les problemes MIP (Mixed Integer Programs) sont en général beaucoup plus dur car trouver des points admissibles est difficile.

Question : Relaxez des contraintes jusqu'a obtenir un problème faisable et observez la solution.

In [44]:
contrainte1a = x>=0
contrainte1b = x<=2
contrainte2a = A @ x >= 0.8*b
contrainte2b = A @ x <= 1.5*b
In [45]:
prob2 = cp.Problem(diet_int , constraints = [contrainte1a, contrainte1b, contrainte2a, contrainte2b])
In [46]:
prob2.solve(verbose=True)
      0: obj =   0.000000000e+00 inf =   1.575e+01 (11)
Out[46]:
1620.0
     16: obj =   2.301634440e+03 inf =   0.000e+00 (0)
*    36: obj =   1.588749307e+03 inf =   0.000e+00 (0)
Long-step dual simplex will be used
+    36: mip =     not found yet >=              -inf        (1; 0)
Solution found by heuristic: 1780
Solution found by heuristic: 1750
Solution found by heuristic: 1720
Solution found by heuristic: 1700
Solution found by heuristic: 1690
+   596: >>>>>   1.680000000e+03 >=   1.610000000e+03   4.2% (219; 151)
Solution found by heuristic: 1675
+  1174: >>>>>   1.635000000e+03 >=   1.620000000e+03   0.9% (426; 226)
+  1344: >>>>>   1.620000000e+03 >=   1.620000000e+03 < 0.1% (143; 805)
+  1344: mip =   1.620000000e+03 >=     tree is empty   0.0% (0; 1309)
In [47]:
prob2.status
Out[47]:
'optimal'
In [48]:
x.value
Out[48]:
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 2., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 2., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
In [49]:
xoptInt = x.value

for j in range(n):
    if xoptInt[j]>0.0001:
        print("[{:3d}]{} \t ({} cal) \t : \t {}".format(j,foods.iloc[j,0],foods.iloc[j,1],xoptInt[j]))
[ 43]Chocolate_Chip_Cookie 1_cookie_(33_g)                      	 (160 cal) 	 : 	 1.0
[ 55]Coffee_Cream 0.4_fl_oz_(11_ml)                             	 (20 cal) 	 : 	 2.0
[ 73]EQUAL_0_Calorie_Sweetener 1_pkg_(1.0_g)                    	 (0 cal) 	 : 	 2.0
[ 74]Egg_McMuffin 4.8_oz_(135_g)                                	 (290 cal) 	 : 	 1.0
[ 95]Fruit_'n_Yogurt_Parfait 5.2_oz_(149_g)                     	 (150 cal) 	 : 	 1.0
[ 98]Hamburger 3.5_oz_(100_g)                                   	 (250 cal) 	 : 	 2.0
[121]Hotcakes 5.3_oz_(151_g)                                    	 (350 cal) 	 : 	 1.0
[154]Ketchup_Packet 1_pkg_(10_g)                                	 (10 cal) 	 : 	 2.0
[248]SPLENDA_No_Calorie_Sweetener 1_pkg_(1.0_g)                 	 (0 cal) 	 : 	 2.0
[264]Side_Salad 3.1_oz_(87_g)                                   	 (20 cal) 	 : 	 2.0
[290]Strawberry_Preserves 0.5_oz_(14_g)                         	 (35 cal) 	 : 	 2.0