From 87f814412a30828949234132cafe87bc2557a80f Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 4 Jun 2026 16:10:25 -0700 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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", From 11cb8739b8bdc8c611602405c04ff7749fe43b7b Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 18 Jun 2026 10:01:42 -0700 Subject: [PATCH 08/11] Use 'dual value' terminology instead of 'shadow price' Signed-off-by: cafzal --- diet_optimization/README.md | 4 ++-- diet_optimization/diet_optimization_lp.ipynb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diet_optimization/README.md b/diet_optimization/README.md index f5632de..2b9fd9e 100644 --- a/diet_optimization/README.md +++ b/diet_optimization/README.md @@ -13,9 +13,9 @@ The diet optimization notebook solves a linear programming problem where: - 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 +**dual values** (`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). +then adding a constraint and reading *its* dual value (the marginal cost of the cap). ### 2. Diet Optimization (MILP) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index f48fd94..ebf4763 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", - "- **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", + "- **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. 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." ] }, From 76c7ad10a8fc790a3308549cc9997e18fcce3c7d Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 29 Jun 2026 14:41:20 -0700 Subject: [PATCH 09/11] Keep diet model canonical; show sensitivity via duals on the base solve (no added constraint) Signed-off-by: cafzal --- diet_optimization/README.md | 3 +- diet_optimization/diet_optimization_lp.ipynb | 89 ++------------------ 2 files changed, 6 insertions(+), 86 deletions(-) diff --git a/diet_optimization/README.md b/diet_optimization/README.md index 2b9fd9e..a462e14 100644 --- a/diet_optimization/README.md +++ b/diet_optimization/README.md @@ -14,8 +14,7 @@ The diet optimization notebook solves a linear programming problem where: 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 and how far each unused food is from entering the diet, -then adding a constraint and reading *its* dual value (the marginal cost of the cap). +nutritional requirements drive the cost and how far each unused food is from entering the diet. ### 2. Diet Optimization (MILP) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index ebf4763..24e8ec5 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 red-meat servings)\n", + "- Satisfy additional constraints (like limiting dairy servings)\n", "\n", "The nutrition guidelines are based on USDA Dietary Guidelines for Americans, 2005.\n" ] @@ -389,7 +389,9 @@ "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. 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." + "- **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.\n", + "\n", + "Together they show where cost is most sensitive to change: relaxing the binding constraint with the largest dual lowers cost the most per unit, and the unused food with the smallest reduced cost is the first that would enter the diet if its price fell." ] }, { @@ -411,86 +413,6 @@ " print(f\"No duals available — solver status is {problem.Status.name}.\")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding Additional Constraints\n", - "\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." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 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\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Solve the problem again with the new 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", - "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": [ - "### 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 meat cap to see the impact." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Display the new solution\n", - "print_solution()" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -504,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** to quantify how sensitive the optimal cost is to each binding constraint\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", From c73b502571948727e560f44e9a296c11c6d9c63d Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 29 Jun 2026 15:04:19 -0700 Subject: [PATCH 10/11] Trim sensitivity section to decision-reads (drop textbook dual/reduced-cost definitions) Signed-off-by: cafzal --- diet_optimization/diet_optimization_lp.ipynb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index 24e8ec5..c756bd3 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -384,14 +384,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Sensitivity Analysis: Duals and Reduced Costs\n", + "## Sensitivity Analysis: Dual Values 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", + "Every variable here is continuous, so cuOpt returns dual information at the optimum — the economic read 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. 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.\n", - "\n", - "Together they show where cost is most sensitive to change: relaxing the binding constraint with the largest dual lowers cost the most per unit, and the unused food with the smallest reduced cost is the first that would enter the diet if its price fell." + "- The binding nutrition limits carry nonzero **dual values** — the marginal cost of each: how much total cost moves per unit you tighten or relax that limit. The largest points to where renegotiating the requirement pays off most.\n", + "- Each food left out of the diet carries a **reduced cost** — how far its per-serving price must fall before buying it would lower total cost. The smallest is the first that would enter if prices shifted." ] }, { From 097b94439044c1f27ef40e7ba034fbdaf5dfc579 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 03:34:53 +0000 Subject: [PATCH 11/11] =?UTF-8?q?diet:=20correct=20the=20duals=20narration?= =?UTF-8?q?=20=E2=80=94=20implication=20direction,=20units,=20degeneracy,?= =?UTF-8?q?=20no-basis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Binding no longer implies a nonzero dual: state complementary slackness one-way (slack > 0 forces ~0 dual; a binding limit with a zero dual is degeneracy) - Stop ranking duals across kcal/g/mg limits: duals are $ per each constraint's own unit, so compare the value of a 1% relaxation, not raw magnitudes; same for reduced costs across arbitrary serving sizes (compare as a fraction of each food's own price) - Present duals/reduced costs as local rates confirmed by a one-unit re-solve, and note cuOpt's PDLP path returns tolerance-accurate duals without a basis, in both prose and printout labels --- diet_optimization/README.md | 3 ++- diet_optimization/diet_optimization_lp.ipynb | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/diet_optimization/README.md b/diet_optimization/README.md index a462e14..b050a32 100644 --- a/diet_optimization/README.md +++ b/diet_optimization/README.md @@ -14,7 +14,8 @@ The diet optimization notebook solves a linear programming problem where: 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 and how far each unused food is from entering the diet. +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) diff --git a/diet_optimization/diet_optimization_lp.ipynb b/diet_optimization/diet_optimization_lp.ipynb index c756bd3..9c3ab3b 100644 --- a/diet_optimization/diet_optimization_lp.ipynb +++ b/diet_optimization/diet_optimization_lp.ipynb @@ -388,8 +388,10 @@ "\n", "Every variable here is continuous, so cuOpt returns dual information at the optimum — the economic read behind the plan:\n", "\n", - "- The binding nutrition limits carry nonzero **dual values** — the marginal cost of each: how much total cost moves per unit you tighten or relax that limit. The largest points to where renegotiating the requirement pays off most.\n", - "- Each food left out of the diet carries a **reduced cost** — how far its per-serving price must fall before buying it would lower total cost. The smallest is the first that would enter if prices shifted." + "- 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", + "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." ] }, { @@ -400,11 +402,11 @@ "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", + " 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 needed to enter the diet:\")\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", @@ -424,7 +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. **Read dual values and reduced costs** to quantify how sensitive the optimal cost is to each binding constraint\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",