Skip to content

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

Merged
mlubin merged 11 commits into
NVIDIA:mainfrom
cafzal:diet-lp-duals-reduced-costs
Jul 7, 2026
Merged

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

Conversation

@cafzal

@cafzalcafzal commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

LP sensitivity analysis (dual values + 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 (cuOpt's dual / sensitivity 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 solve, reads each constraint's dual value (DualValue, Slack) and each variable's reduced cost (ReducedCost) via problem.getConstraints() / problem.getVariables(), with a plain-language binding-vs-slack reading: which nutrition limits drive the cost, and how far each unused food is from entering the diet. The diet model itself is unchanged. 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); run evidence below.

User testing

Run end-to-end on a Colab GPU runtime (cuopt-cu12) — clean, no errors: solve is Optimal at $11.83. The binding constraints here price nonzero — 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 shows a positive reduced cost (macaroni +1.34 the largest), the price drop before it could enter the diet.

raw cell output
Constraint duals — local marginal cost per unit of each limit (units differ per constraint):
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 before it could 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

Notes

  • Dual values 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
cafzaland 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
ContributorAuthor

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

rapids-botBot 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
@cafzalcafzal changed the title Add LP sensitivity analysis (shadow prices + reduced costs) to the diet exampleAdd LP sensitivity analysis (duals + reduced costs) to the diet exampleJun 5, 2026
Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal

cafzal commented Jun 16, 2026

Copy link
Copy Markdown
ContributorAuthor

@ramakrishnap-nv this is all set on my end

Signed-off-by: cafzal <cameron.afzal@gmail.com>
"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?

@cafzalcafzalJun 29, 2026

Copy link
Copy Markdown
ContributorAuthor

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.

cafzal added 2 commits June 29, 2026 14:41
…ve (no added constraint)
Signed-off-by: cafzal <cameron.afzal@gmail.com>
…d-cost definitions)
Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal
cafzal requested a review from mlubinJune 29, 2026 22:19
…eneracy, no-basis
- 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
@cafzal
cafzalforce-pushed the diet-lp-duals-reduced-costs branch from eef6cf9 to 097b944CompareJuly 2, 2026 03:42
@mlubin
mlubin merged commit 3c21801 into NVIDIA:mainJul 7, 2026
Sign up for freeto 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.

4 participants

@cafzal@mlubin@ramakrishnap-nv@claude