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)

Gallery generated by Sphinx-Gallery