Note
Go to the end to download the full example code.
Diet problem#
Please refer to chapter 2 in the AMPL Book for a detailed description of the problem. We will illustrate how to implement this model with DOcplex using the docplex-extensions library.
Implementation reference: ampl/colab.ampl.com — Copyright (c) 2022-2022, AMPL Optimization inc. (licensed under the MIT License)
Ensure DOcplex and its required dependecies are installed correctly#
from docplex.mp.check_list import run_docplex_check_list
run_docplex_check_list()
* system is: Linux 64bit
* Python version 3.10.17, located at: /home/docs/checkouts/readthedocs.org/user_builds/docplex-extensions/envs/stable/bin/python
* docplex is present, version is 2.29.245
* CPLEX library is present, version is 22.1.2.0, located at: /home/docs/checkouts/readthedocs.org/user_builds/docplex-extensions/envs/stable/lib/python3.10/site-packages
* pandas is present, version is 2.2.3
* Your cplex version 22.1.2.0 is the latest available
! Cplex promotional version, limited to 1000 variables, 1000 constraints
* diagnostics: 2
-- Module cloudpickle is missing, run: pip install cloudpickle
-- Your local CPLEX edition is limited. Consider purchasing a full license.
Import libraries#
from docplex.mp.model import Model
import docplex_extensions as dex
Set up problem data#
Index-sets#
\[\begin{split}\begin{align*}
i &\in NUTR: && \text{Nutrients to consider} \\
j &\in FOOD: && \text{Food items to consider} \\
\end{align*}\end{split}\]
NUTR = dex.IndexSet1D(['A', 'B1', 'B2', 'C'], name='NUTRIENT')
FOOD = dex.IndexSet1D(['BEEF', 'CHK', 'FISH', 'HAM', 'MCH', 'MTL', 'SPG', 'TUR'], name='FOOD')
Parameters#
\[\begin{split}\begin{align*}
cost_{j} &\in \mathbb{R}^{+}: && \text{Cost of food item $j$}, \; \forall \; j \in FOOD \\
f\_min_{j} &\in \mathbb{R}_{0}^{+}: && \text{Minimum purchase quantity required for food item
$j$}, \; \forall \; j \in FOOD \\
f\_max_{j} &\in \mathbb{R}_{0}^{+}: && \text{Maximum purchase quantity allowed for food item
$j$}, \; \forall \; j \in FOOD \\
n\_min_{i} &\in \mathbb{R}_{0}^{+}: && \text{Minimum amount required of nutrient $i$}, \;
\forall \; i \in NUTR \\
n\_max_{i} &\in \mathbb{R}_{0}^{+}: && \text{Maximum amount allowed of nutrient $i$}, \;
\forall \; i \in NUTR \\
amt_{i, j} &\in \mathbb{R}_{0}^{+}: && \text{Amount of nutrient $i$ in food item $j$}, \;
\forall \; i \in NUTR \; \text{and} \; j \in FOOD \\
\end{align*}\end{split}\]
cost = dex.ParamDict1D(
{
'BEEF': 3.19,
'CHK': 2.59,
'FISH': 2.29,
'HAM': 2.89,
'MCH': 1.89,
'MTL': 1.99,
'SPG': 1.99,
'TUR': 2.49,
},
key_name='FOOD',
value_name='COST',
)
f_min = dex.ParamDict1D(
{
'BEEF': 0,
'CHK': 0,
'FISH': 0,
'HAM': 0,
'MCH': 0,
'MTL': 0,
'SPG': 0,
'TUR': 0,
},
key_name='FOOD',
value_name='MIN-QTY',
)
f_max = dex.ParamDict1D(
{
'BEEF': 100,
'CHK': 100,
'FISH': 100,
'HAM': 100,
'MCH': 100,
'MTL': 100,
'SPG': 100,
'TUR': 100,
},
key_name='FOOD',
value_name='MAX-QTY',
)
n_min = dex.ParamDict1D(
{'A': 700, 'C': 700, 'B1': 700, 'B2': 700},
key_name='NUTRIENT',
value_name='MIN-AMT',
)
n_max = dex.ParamDict1D(
{'A': 10000, 'C': 10000, 'B1': 10000, 'B2': 10000},
key_name='NUTRIENT',
value_name='MAX-AMT',
)
amt = dex.ParamDictND(
{
('A', 'BEEF'): 60,
('C', 'BEEF'): 20,
('B1', 'BEEF'): 10,
('B2', 'BEEF'): 15,
('A', 'CHK'): 8,
('C', 'CHK'): 0,
('B1', 'CHK'): 20,
('B2', 'CHK'): 20,
('A', 'FISH'): 8,
('C', 'FISH'): 10,
('B1', 'FISH'): 15,
('B2', 'FISH'): 10,
('A', 'HAM'): 40,
('C', 'HAM'): 40,
('B1', 'HAM'): 35,
('B2', 'HAM'): 10,
('A', 'MCH'): 15,
('C', 'MCH'): 35,
('B1', 'MCH'): 15,
('B2', 'MCH'): 15,
('A', 'MTL'): 70,
('C', 'MTL'): 30,
('B1', 'MTL'): 15,
('B2', 'MTL'): 15,
('A', 'SPG'): 25,
('C', 'SPG'): 50,
('B1', 'SPG'): 25,
('B2', 'SPG'): 15,
('A', 'TUR'): 60,
('C', 'TUR'): 20,
('B1', 'TUR'): 15,
('B2', 'TUR'): 10,
},
key_names=('FOOD', 'NUTR'),
value_name='AMT',
)
Set up model#
Instantiate#
model: Model = Model(name='diet')
Add decision variables#
\[\begin{split}\begin{align*}
buy_{j} &\in \mathbb{R}_{0}^{+}: && \text{Quantity of food item $j$ to be purchased}, \;
\forall \; j \in FOOD \\
&\geq f\_min_{j} \\
&\leq f\_max_{j} \\
\end{align*}\end{split}\]
buy = dex.add_variables(
model,
indexset=FOOD,
var_type='continuous',
lb=f_min,
ub=f_max,
name='BUY-QTY',
)
Set objective#
\[\begin{split}\begin{align*}
&\text{Minimize the total cost of the diet:} \\
&\text{min.} \; \sum_{j \in FOOD} cost_{j} \times buy_{j} \\
\end{align*}\end{split}\]
# fmt: off
model.minimize(
model.sum(cost[j] * buy[j] for j in FOOD)
)
# fmt: on
Add constraints#
\[\begin{split}\begin{align*}
&\text{Ensure that the nutritional limits are satisfied by the diet:} \\
& \sum_{j \in FOOD} amt_{i, j} \times buy_{j} \geq n\_min_{i}, \; \forall \; i \in NUTR \\
& \sum_{j \in FOOD} amt_{i, j} \times buy_{j} \leq n\_max_{i}, \; \forall \; i \in NUTR \\
\end{align*}\end{split}\]
# fmt: off
min_nutr_reqd = model.add_constraints(
(model.sum(amt[i, j] * buy[j] for j in FOOD) >= n_min[i], f'min-nutr-reqd_{i}')
for i in NUTR
)
max_nutr_allwd = model.add_constraints(
(model.sum(amt[i, j] * buy[j] for j in FOOD) <= n_max[i], f'max-nutr-allwd_{i}')
for i in NUTR
)
# fmt: on
Solve#
sol = dex.solve(model, log_output=True)
------------------------------ LP problem statistics ------------------------------
Problem name : diet
Objective sense : Minimize
Variables : 8 [Box: 8]
Objective nonzeros : 8
Linear constraints : 8 [Less: 4, Greater: 4]
Nonzeros : 62
RHS nonzeros : 8
Variables : Min LB: 0.000000 Max UB: 100.0000
Objective nonzeros : Min : 1.890000 Max : 3.190000
Linear constraints :
Nonzeros : Min : 8.000000 Max : 70.00000
RHS nonzeros : Min : 700.0000 Max : 10000.00
------------------------------- CPLEX optimizer log -------------------------------
Version identifier: 22.1.2.0 | 2024-12-10 | f4cec290b
CPXPARAM_Read_DataCheck 1
Tried aggregator 1 time.
No LP presolve or aggregator reductions.
Presolve time = 0.00 sec. (0.01 ticks)
Iteration log . . .
Iteration: 1 Dual objective = 88.200000
--------------------------- Solution quality statistics ---------------------------
There are no bound infeasibilities.
There are no reduced-cost infeasibilities.
Max. unscaled (scaled) Ax-b resid. = 6.89226e-13 (1.07692e-14)
Max. unscaled (scaled) c-B'pi resid. = 2.22045e-16 (2.22045e-16)
Max. unscaled (scaled) |x| = 46.6667 (46.6667)
Max. unscaled (scaled) |slack| = 9300 (581.25)
Max. unscaled (scaled) |pi| = 0.126 (2.016)
Max. unscaled (scaled) |red-cost| = 1.63 (2.06)
Condition number of scaled basis = 2.3e+00
-------------------------------------------------------------------------------------
Solution#
sol.display(print_zeros=False)
solution for: diet
objective: 88.200
status: OPTIMAL_SOLUTION(2)
BUY-QTY_MCH = 46.667
Total running time of the script: (0 minutes 0.055 seconds)