From 87f814412a30828949234132cafe87bc2557a80f Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 4 Jun 2026 16:10:25 -0700 Subject: [PATCH 1/7] Add LP sensitivity-analysis cells (duals + reduced costs) to diet example Adds a Sensitivity Analysis section after the first solve (constraint .DualValue/.Slack and variable .ReducedCost via getConstraints()/getVariables()) and a dairy-cap shadow-price cell after the re-solve (dairy_constraint.DualValue). Surfaces cuOpt's LP dual information, which the example previously omitted. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- diet_optimization/diet_optimization_lp.ipynb | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index de3e0b5..4788544 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -380,6 +380,37 @@ "print_solution()\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sensitivity Analysis: Shadow Prices 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", + "- **Shadow price (constraint dual)** — how much the minimum cost changes per unit change in a constraint's bound. A *binding* nutrient floor has a nonzero shadow price; a slack constraint 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(\"Shadow prices (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": {}, @@ -419,6 +450,28 @@ "print(f\"Objective value: ${problem.ObjValue:.2f}\")\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Shadow Price of the Dairy Cap\n", + "\n", + "We just capped dairy at 6 servings, and the cost rose. The cap's **dual** gives the marginal price of that limit directly — the change in total cost per additional serving of dairy allowed — without re-solving at every level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Marginal cost of the dairy cap, read straight off its dual\n", + "if problem.Status.name == \"Optimal\":\n", + " print(f\"limit_dairy shadow price: {dairy_constraint.DualValue:+.4f} \"\n", + " f\"(cost change per +1 serving of dairy allowed)\")\n", + " print(f\"slack on the cap: {dairy_constraint.Slack:.4f} (0 means the cap is binding)\")" + ] + }, { "cell_type": "markdown", "metadata": {}, From fadbf2d0035b9285bbe6e15377bf6e0bb5f1ebb4 Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 4 Jun 2026 19:59:01 -0700 Subject: [PATCH 2/7] Swap infeasible dairy cap for a feasible hamburger/hot-dog cap The stock dairy<=6 cap makes the model PrimalInfeasible (sodium is binding and dairy is the only low-sodium protein/calorie source), so the added-constraint shadow-price demo couldn't run. Cap hamburger+hot dog<=0.4 instead: it frees the binding sodium constraint (stays feasible) while binding (cost rises), yielding a real shadow price. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- diet_optimization/diet_optimization_lp.ipynb | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index 4788544..7022165 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -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" ] @@ -417,7 +417,7 @@ "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 **shadow price**. We'll cap **hamburger + hot dog at 0.4 servings** combined — say, a red-meat preference — and re-solve." ] }, { @@ -426,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\")" ] }, { @@ -438,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", @@ -454,9 +454,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### The Shadow Price of the Dairy Cap\n", + "### The Shadow Price of the Meat Cap\n", "\n", - "We just capped dairy at 6 servings, and the cost rose. The cap's **dual** gives the marginal price of that limit directly — the change in total cost per additional serving of dairy allowed — without re-solving at every level." + "Capping hamburger and hot dog tightens the model and nudges the cost up. The cap's **dual** gives the marginal price of that limit directly — how much total cost changes per additional combined serving you allow — without re-solving at every level." ] }, { @@ -465,11 +465,11 @@ "metadata": {}, "outputs": [], "source": [ - "# Marginal cost of the dairy cap, read straight off its dual\n", + "# Marginal cost of the meat cap, read straight off its dual\n", "if problem.Status.name == \"Optimal\":\n", - " print(f\"limit_dairy shadow price: {dairy_constraint.DualValue:+.4f} \"\n", - " f\"(cost change per +1 serving of dairy allowed)\")\n", - " print(f\"slack on the cap: {dairy_constraint.Slack:.4f} (0 means the cap is binding)\")" + " print(f\"limit_meat shadow price: {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)\")" ] }, { @@ -478,7 +478,7 @@ "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." ] }, { From 5cdbb40f1347cc7d0cc3b7c8d0a2d40e71e0c99d Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 4 Jun 2026 20:24:25 -0700 Subject: [PATCH 3/7] Generalize shadow-price wording (binding floor -> binding constraint) Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- diet_optimization/diet_optimization_lp.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index 7022165..9863fb3 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -388,7 +388,7 @@ "\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", - "- **Shadow price (constraint dual)** — how much the minimum cost changes per unit change in a constraint's bound. A *binding* nutrient floor has a nonzero shadow price; a slack constraint is approximately zero.\n", + "- **Shadow price (constraint dual)** — how much the minimum cost changes per unit change in a constraint's bound. A *binding* constraint has a nonzero shadow price; 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." ] }, From c5235e813d83e90b596a4ff0922f77acadfc4e67 Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 4 Jun 2026 20:26:28 -0700 Subject: [PATCH 4/7] Document sensitivity analysis in diet README Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- diet_optimization/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/diet_optimization/README.md b/diet_optimization/README.md index 3145795..f5632de 100644 --- a/diet_optimization/README.md +++ b/diet_optimization/README.md @@ -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) From 88e49346a74aa50c02c728eae761aed87e44c7af Mon Sep 17 00:00:00 2001 From: Ramakrishnap <42624703+rgsl888prabhu@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:26:27 -0500 Subject: [PATCH 5/7] Update diet_optimization_lp.ipynb --- diet_optimization/diet_optimization_lp.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index 9863fb3..b89c992 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -521,7 +521,7 @@ "metadata": {}, "source": [ "\n", - "SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", + "SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n", "\n", "SPDX-License-Identifier: Apache-2.0\n", "\n", From d05f2f451a97f302cc4b51524d55239d8256fe0f Mon Sep 17 00:00:00 2001 From: cafzal Date: Fri, 5 Jun 2026 12:16:51 -0700 Subject: [PATCH 6/7] Prefer 'dual'/'sensitivity' over 'shadow price' (review feedback on #151) Signed-off-by: cafzal --- diet_optimization/diet_optimization_lp.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index b89c992..fd8617e 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -384,11 +384,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Sensitivity Analysis: Shadow Prices and Reduced Costs\n", + "## 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", - "- **Shadow price (constraint dual)** — how much the minimum cost changes per unit change in a constraint's bound. A *binding* constraint has a nonzero shadow price; a slack one is approximately zero.\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." ] }, @@ -400,7 +400,7 @@ "source": [ "# Sensitivity analysis — read the LP duals at the optimum\n", "if problem.Status.name == \"Optimal\":\n", - " print(\"Shadow prices (constraint duals) — change in cost per unit change in the bound:\")\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", @@ -417,7 +417,7 @@ "source": [ "## Adding Additional Constraints\n", "\n", - "Now let's add a constraint to the existing model and read its **shadow price**. We'll cap **hamburger + hot dog at 0.4 servings** combined — say, a red-meat preference — and re-solve." + "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." ] }, { @@ -454,9 +454,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### The Shadow Price of the Meat Cap\n", + "### 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 price of that limit directly — how much total cost changes per additional combined serving you allow — without re-solving at every level." + "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." ] }, { @@ -467,9 +467,9 @@ "source": [ "# Marginal cost of the meat cap, read straight off its dual\n", "if problem.Status.name == \"Optimal\":\n", - " print(f\"limit_meat shadow price: {meat_constraint.DualValue:+.4f} \"\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)\")" + " print(f\"slack on the cap: {meat_constraint.Slack:.4f} (0 means the cap is binding)\")" ] }, { From 0c1585d2efe195ae36c0d8f2da2ca143c7013839 Mon Sep 17 00:00:00 2001 From: cafzal Date: Fri, 5 Jun 2026 12:19:58 -0700 Subject: [PATCH 7/7] Set notebook copyright to 2026 Signed-off-by: cafzal --- diet_optimization/diet_optimization_lp.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index fd8617e..f48fd94 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -521,7 +521,7 @@ "metadata": {}, "source": [ "\n", - "SPDX-FileCopyrightText: Copyright (c) 2025-2026 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",