Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions diet_optimization/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ The diet optimization notebook solves a linear programming problem where:
- The diet is a mix of different foods.
- The foods have different prices and nutritional values.

The notebook also demonstrates **sensitivity analysis** on the solved LP: reading constraint
**shadow prices** (`DualValue`) and variable **reduced costs** (`ReducedCost`) to see which
nutritional requirements drive the cost and how far each unused food is from entering the diet,
then adding a constraint and reading *its* shadow price (the marginal cost of the cap).


### 2. Diet Optimization (MILP)

Expand Down
69 changes: 61 additions & 8 deletions diet_optimization/diet_optimization_lp.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"We need to select quantities of different foods to:\n",
"- Meet minimum and maximum nutritional requirements\n",
"- Minimize total cost\n",
"- Satisfy additional constraints (like limiting dairy servings)\n",
"- Satisfy additional constraints (like limiting red-meat servings)\n",
"\n",
"The nutrition guidelines are based on USDA Dietary Guidelines for Americans, 2005.\n"
]
Expand Down Expand Up @@ -380,13 +380,44 @@
"print_solution()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sensitivity Analysis: Duals and Reduced Costs\n",
"\n",
"Because every variable here is continuous, this is a linear program, and cuOpt returns dual information at the optimum — the economic \"why\" behind the plan:\n",
"\n",
"- **Constraint dual** — the sensitivity of the optimal cost to a constraint's bound: how much the minimum cost changes per unit change in that bound (traditionally called the *shadow price*). A *binding* constraint has a nonzero dual; a slack one is approximately zero.\n",
"- **Reduced cost (variable dual)** — for a food left out of the diet (amount 0), how much its per-serving price would have to fall before buying it could lower total cost."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Sensitivity analysis — read the LP duals at the optimum\n",
"if problem.Status.name == \"Optimal\":\n",
" print(\"Constraint duals — change in cost per unit change in the bound:\")\n",
" for c in problem.getConstraints():\n",
" print(f\" {c.ConstraintName:14s} dual={c.DualValue:+.4f} slack={c.Slack:.4f}\")\n",
"\n",
" print(\"\\nReduced costs (variable duals) — for foods at 0, price drop needed to enter the diet:\")\n",
" for v in problem.getVariables():\n",
" print(f\" {v.VariableName:12s} amount={v.getValue():7.3f} reduced_cost={v.ReducedCost:+.4f}\")\n",
"else:\n",
" print(f\"No duals available — solver status is {problem.Status.name}.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding Additional Constraints\n",
"\n",
"Now let's demonstrate how to add additional constraints to the existing model. We'll add a constraint to limit dairy servings to at most 6.\n"
"Now let's add a constraint to the existing model and read its **dual** — the sensitivity of cost to that constraint. We'll cap **hamburger + hot dog at 0.4 servings** combined — say, a red-meat preference — and re-solve."
]
},
{
Expand All @@ -395,9 +426,9 @@
"metadata": {},
"outputs": [],
"source": [
"# Create LinearExpression for dairy constraint\n",
"dairy_expr = buy_vars[\"milk\"] + buy_vars[\"ice cream\"]\n",
"dairy_constraint = problem.addConstraint(dairy_expr <= 6, name=\"limit_dairy\")"
"# Limit combined hamburger + hot dog servings (a red-meat preference)\n",
"meat_expr = buy_vars[\"hamburger\"] + buy_vars[\"hot dog\"]\n",
"meat_constraint = problem.addConstraint(meat_expr <= 0.4, name=\"limit_meat\")"
]
},
{
Expand All @@ -407,7 +438,7 @@
"outputs": [],
"source": [
"# Solve the problem again with the new constraint\n",
"print(\"\\nSolving with dairy constraint...\")\n",
"print(\"\\nSolving with the meat constraint...\")\n",
"print(f\"Problem now has {problem.NumVariables} variables and {problem.NumConstraints} constraints\")\n",
"\n",
"start_time = time.time()\n",
Expand All @@ -419,13 +450,35 @@
"print(f\"Objective value: ${problem.ObjValue:.2f}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The Dual of the Meat Cap\n",
"\n",
"Capping hamburger and hot dog tightens the model and nudges the cost up. The cap's **dual** gives the marginal cost of that limit directly — how much total cost changes per additional combined serving you allow — without re-solving at every level."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Marginal cost of the meat cap, read straight off its dual\n",
"if problem.Status.name == \"Optimal\":\n",
" print(f\"limit_meat dual: {meat_constraint.DualValue:+.4f} \"\n",
" f\"(cost change per +1 combined serving of hamburger/hot dog allowed)\")\n",
" print(f\"slack on the cap: {meat_constraint.Slack:.4f} (0 means the cap is binding)\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solution Comparison\n",
"\n",
"Let's compare the solutions before and after adding the dairy constraint to see the impact.\n"
"Let's compare the solutions before and after adding the meat cap to see the impact."
]
},
{
Expand Down Expand Up @@ -468,7 +521,7 @@
"metadata": {},
"source": [
"\n",
"SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n",
"SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n",
"\n",
"SPDX-License-Identifier: Apache-2.0\n",
"\n",
Expand Down