Skip to content

Add LP sensitivity analysis (duals + reduced costs) to the diet example#154

Open
cafzal wants to merge 7 commits into
NVIDIA:mainfrom
cafzal:diet-lp-duals-reduced-costs
Open

Add LP sensitivity analysis (duals + reduced costs) to the diet example#154
cafzal wants to merge 7 commits into
NVIDIA:mainfrom
cafzal:diet-lp-duals-reduced-costs

Conversation

@cafzal
Copy link
Copy Markdown

@cafzal cafzal commented Jun 4, 2026

LP sensitivity analysis (duals + reduced costs) — diet example

Extends the diet LP example to read the dual information cuOpt returns for a solved LP — the economic "why" behind the optimal plan. Motivated by NVIDIA/cuopt#1393, which clarifies cuOpt's dual / sensitivity-analysis support per problem type (LP/QP, not integer models); companion to the QP efficient-frontier-with-duals example in #151 (LP here, QP there).

  • diet_optimization/diet_optimization_lp.ipynb — after the first solve, reads each constraint's dual (DualValue, Slack) and each variable's reduced cost (ReducedCost) via problem.getConstraints() / problem.getVariables(), with a plain-language binding-vs-slack reading. The "add a constraint" step then reads the dual of the added constraint (meat_constraint.DualValue) — the marginal cost of the cap. This also replaces the example's existing dairy (milk + ice cream) <= 6 cap, which is infeasible (max_sodium is binding and dairy is the only low-sodium way to meet the protein/calorie minimums, so the stock notebook's "Solution Comparison" printed "No optimal solution found"), with a feasible, binding hamburger + hot dog <= 0.4. diet_optimization/README.md updated to match.

Runs on cuOpt alone (Colab GPU), follows the repo's notebook idiom (GPU check → cuopt-cu12 install → solve), and ships output-stripped (repo convention — every cuopt-examples notebook has 0 cell outputs); run evidence below.

User testing

Run end-to-end on a Colab GPU runtime (cuopt-cu12) — clean, no errors:

  • First solve Optimal, $11.83. Binding constraints carry nonzero duals — min_protein +0.093, min_calories +0.003, max_sodium −0.002 — while slack ones price to ~0; every food left out of the diet has a positive reduced cost (macaroni +1.34 the largest), i.e. how far its per-serving price must fall to enter the diet.
  • Added-constraint dual. With hamburger + hot dog <= 0.4, the re-solve is Optimal at $11.86 (up from $11.83); the cap binds at hamburger = 0.400 with dual −0.164 (slack 0) — allowing one more serving would lower cost ~$0.16, matching the empirical $0.03 / 0.205 ≈ $0.15.
raw cell output
Constraint duals — change in cost per unit change in the bound:
  min_calories   dual=+0.0034  slack=-0.0000
  max_calories   dual=+0.0000  slack=400.0000
  min_protein    dual=+0.0930  slack=-0.0000
  min_fat        dual=+0.0000  slack=-59.0559
  max_fat        dual=+0.0000  slack=5.9441
  min_sodium     dual=+0.0000  slack=-1779.0000
  max_sodium     dual=-0.0016  slack=0.0000

Reduced costs (variable duals) — for foods at 0, price drop needed to enter the diet:
  hamburger    amount=  0.605  reduced_cost=+0.0001
  chicken      amount=  0.000  reduced_cost=+0.3425
  hot dog      amount=  0.000  reduced_cost=+0.5456
  fries        amount=  0.000  reduced_cost=+0.6421
  macaroni     amount=  0.000  reduced_cost=+1.3371
  pizza        amount=  0.000  reduced_cost=+0.7857
  salad        amount=  0.000  reduced_cost=+0.4400
  milk         amount=  6.970  reduced_cost=-0.0000
  ice cream    amount=  2.591  reduced_cost=-0.0000

limit_meat dual: -0.1643  (cost change per +1 combined serving of hamburger/hot dog allowed)
slack on the cap: 0.0000   (0 means the cap is binding)

Notes

  • Duals are LP/QP only — valid here because every variable is continuous; an integer model returns none.
  • Output-stripped per repo convention; re-run on a GPU runtime to reproduce the numbers above.

@cafzal cafzal marked this pull request as ready for review June 5, 2026 03:24
cafzal and others added 4 commits June 4, 2026 20:30
…mple

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) <noreply@anthropic.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal
Copy link
Copy Markdown
Author

cafzal commented Jun 5, 2026

@rgsl888prabhu For visibility: Here's a small addition to showcase use of shadow price and reduced cost in an existing example.

@rgsl888prabhu rgsl888prabhu requested review from chris-maes and rg20 June 5, 2026 15:11
rapids-bot Bot pushed a commit to NVIDIA/cuopt that referenced this pull request Jun 5, 2026
…1393)

Adds a per-problem-type **dual / sensitivity** capability note to the concepts-only `cuopt-numerical-optimization-formulation` skill, so the agent guides users correctly on what cuOpt exposes after a solve:

- **LP** — shadow prices (constraint duals) + reduced costs
- **MILP** — none (integer optima are not continuous)
- **QP** — shadow prices + reduced costs

Two edits: a `Duals / sensitivity` row in the LP/MILP/QP comparison table, and a short post-solve note giving the decision meaning — **shadow price = where to invest** (marginal objective gain from relaxing a binding constraint) and **reduced cost = near-miss** (how far a left-out option must improve before it enters the solution). As a concepts skill it states *what's supported per type* and *what it means*, and defers *how to read them* to the language-specific API skills (no API symbols inlined).

**Why.** Surfaced from real integration use — wiring solver-exact sensitivity/explainability into a downstream multi-objective decision layer. The formulation skill covered how to *formulate* but not which problem types yield sensitivity information, which is exactly what a user needs before relying on it (e.g. not expecting duals off a MILP).

The note also reflects the review feedback on #1355: a concepts skill carries no maturity ("beta") labels and no specific API symbols — both drift out of date, with no CI to catch the staleness.

**Validation & gating.** `ci/utils/validate_skills.sh` passes (skill structure, marketplace manifest, `AGENTS.md` references). The NVSkills-Eval pipeline — which also (re)generates `BENCHMARK.md`, the skill card, and the signature — is the gate and is **pending**: `skills/**` CI needs a maintainer and cannot be triggered from a fork branch.

**Related.**
- NVIDIA/cuopt-examples#154 — the diet LP duals example: a runnable worked case of the shadow-price / reduced-cost reading this note describes.
- Codebase-side sensitivity gaps (objective/RHS ranging; exposing variable/basis status) are tracked separately as #1394 and #1395.

Authors:
  - Cameron Afzal (https://github.com/cafzal)

Approvers:
  - Ramakrishnap (https://github.com/rgsl888prabhu)

URL: #1393
@cafzal cafzal changed the title Add LP sensitivity analysis (shadow prices + reduced costs) to the diet example Add LP sensitivity analysis (duals + reduced costs) to the diet example Jun 5, 2026
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants