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
**dual values** (`DualValue`) and variable **reduced costs** (`ReducedCost`) to see which
nutritional requirements drive the cost at the margin and roughly how far each unused food is
from entering the diet (local rates — see the notebook's degeneracy and units caveats).


### 2. Diet Optimization (MILP)

Expand Down
62 changes: 18 additions & 44 deletions diet_optimization/diet_optimization_lp.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -384,48 +384,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding Additional Constraints\n",
"## Sensitivity Analysis: Dual Values and Reduced Costs\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"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create LinearExpression for dairy constraint\n",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have financial interests in the dairy industry? 😆
Why are we changing the formulation of the problem?

@cafzal cafzal Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Reverted: the diet formulation stays canonical, and the sensitivity section now reads the duals and reduced costs from the existing solve rather than adding a constraint. Also trimmed the duals write-up.

"dairy_expr = buy_vars[\"milk\"] + buy_vars[\"ice cream\"]\n",
"dairy_constraint = problem.addConstraint(dairy_expr <= 6, name=\"limit_dairy\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Solve the problem again with the new constraint\n",
"print(\"\\nSolving with dairy constraint...\")\n",
"print(f\"Problem now has {problem.NumVariables} variables and {problem.NumConstraints} constraints\")\n",
"Every variable here is continuous, so cuOpt returns dual information at the optimum — the economic read behind the plan:\n",
"\n",
"start_time = time.time()\n",
"problem.solve(settings)\n",
"solve_time = time.time() - start_time\n",
"\n",
"print(f\"\\nSolve completed in {solve_time:.3f} seconds\")\n",
"print(f\"Solver status: {problem.Status.name}\")\n",
"print(f\"Objective value: ${problem.ObjValue:.2f}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solution Comparison\n",
"- Each nutrition limit carries a **dual value** — at a non-degenerate optimum, how much total cost moves per unit you tighten or relax that limit. The implication runs one way: a limit with slack prices to ~0, while a binding limit (slack ≈ 0) *can* carry a nonzero dual but need not (a binding limit with a zero dual is a form of degeneracy). And a dual is $ per *that constraint's own unit* — per kcal for calories, per mg for sodium — so raw magnitudes aren't comparable across limits; to find where renegotiating pays off most, compare the value of, say, a 1% relaxation of each limit rather than ranking raw duals.\n",
"- A food left at 0 carries a **reduced cost** — roughly how far its per-serving price must fall before it *could* enter the diet without raising total cost. Reduced costs are per serving and serving sizes are arbitrary, so compare each as a fraction of the food's own price (\"needs a 5% price cut\" vs. \"a 40% one\") rather than sorting raw values.\n",
"\n",
"Let's compare the solutions before and after adding the dairy constraint to see the impact.\n"
"Two caveats keep the read honest: under degeneracy a dual is a local, one-sided rate — confirm any rate you quote with a one-unit re-solve — and cuOpt's default first-order (PDLP) path can return duals without a simplex basis, accurate only to the convergence tolerance, so tiny near-zero values are noise, not signal."
]
},
{
Expand All @@ -434,8 +400,17 @@
"metadata": {},
"outputs": [],
"source": [
"# Display the new solution\n",
"print_solution()"
"# Sensitivity analysis — read the LP duals at the optimum\n",
"if problem.Status.name == \"Optimal\":\n",
" print(\"Constraint duals — local marginal cost per unit of each limit (units differ per constraint):\")\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 before it could 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}.\")"
]
},
{
Expand All @@ -451,8 +426,7 @@
"3. **Define an objective function** to minimize total cost\n",
"4. **Add nutritional constraints** with both lower and upper bounds\n",
"5. **Solve the optimization problem** using cuOpt's high-performance solver\n",
"6. **Add additional constraints** to the existing model\n",
"7. **Analyze and compare solutions** before and after constraint modifications\n",
"6. **Read dual values and reduced costs** for a local sensitivity read — which limits drive cost at the margin, and which unused foods sit closest to entering\n",
"\n",
"The cuOpt Python API provides a clean, intuitive interface for building and solving optimization problems, making it easy to model complex real-world scenarios like diet optimization.\n",
"\n",
Expand All @@ -468,7 +442,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