Skip to content

cuopt-agent: add duals-interpretation guidance to the debugging skill#159

Open
cafzal wants to merge 14 commits into
NVIDIA:mainfrom
cafzal:agent-duals-narration
Open

cuopt-agent: add duals-interpretation guidance to the debugging skill#159
cafzal wants to merge 14 commits into
NVIDIA:mainfrom
cafzal:agent-duals-narration

Conversation

@cafzal

@cafzal cafzal commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What

Adds resources/interpreting_duals.md to the cuopt-agent's cuopt-debugging skill: guidance for turning a solved LP/QP's DualValue / ReducedCost / Slack into a decision read — which constraint is the binding bottleneck and what relaxing it is worth (its dual value), and which unused option is the closest near-miss (reduced cost) — with the one-way implications and degeneracy / no-basis caveats that keep those reads honest (rankings as shortlists; any quoted number confirmed by a differencing re-solve). Linked from the skill's SKILL.md.

Why

The skill already shows how to read duals (diagnostic_snippets.md) but not what they mean for the decision. It also makes the scope explicit — consistent with the main-repo dual/sensitivity work (NVIDIA/cuopt#1393, #1406): duals exist for continuous (LP/QP) solves off linear constraints. Two cases return none — an integer model (the max-supply model is a MILP) and a quadratic constraint (a quadratic objective is fine) — with the difference-adjacent-solves / LP-relaxation fallback called out for the MILP.

Notes

  • Pure guidance — a skill resources/ markdown plus a one-line SKILL.md pointer; no code or model changes.
  • Grounded in the max-supply shape (resource-hour caps and per-period supply limits as the binding bottlenecks).

Companion to the dual / sensitivity work in #154 (LP duals in the diet example) and #157 (the agent's multi-objective scenario).

cafzal added 3 commits June 18, 2026 09:13
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal cafzal marked this pull request as ready for review June 18, 2026 17:26
@cafzal

cafzal commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@ramakrishnap-nv small one: adds duals-interpretation guidance (decision read of DualValue/ReducedCost) to the agent's debugging skill. Ready for review.

At a degenerate optimum (many constraints binding at once) the basis is non-unique, so a single dual is one-sided. Report the ranking of binding constraints and present a dual as a direction (confirmed by the one-unit re-solve) rather than a hard per-unit rate. LP/simplex effect; strictly convex QP duals stay unique.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>

## Constraint dual value — the marginal value of relaxing a limit

A constraint's `DualValue` is the **sensitivity** of the optimum to that constraint: the change in

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.

The smooth sensitivity interpretation is true only in the absence of degeneracy, and degeneracy is very common in real-world problems. I don't know how to precisely describe the perturbation sensitivity of the objective without invoking subgradients.

@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.

Thanks @mlubin . Follow-up commits make the ranking of binding constraints a more robust read and demote a single dual to a direction:

  • The headline now conditions the exact per-unit equality on a non-degenerate optimum; under degeneracy it is one-sided.
  • The bullet under it drops the unconditional "relax by one unit, objective improves by DualValue."
  • The When a dual is soft section adds the one-sided, non-unique caveat, the trap that the dual looks most precise where it is least reliable, and a one-unit re-solve to confirm any rate before quoting it.

Happy to tighten further if anything still reads too strong. We can also cover subgradients explicitly if you think it's warranted.

cafzal and others added 2 commits June 29, 2026 14:28
…n-degeneracy

Review feedback: the headline stated the smooth sensitivity reading unconditionally. Hedge it where the claim is made — exact change per unit only at a non-degenerate optimum; one-sided under degeneracy (the common case), read as a direction. Detail stays in the existing degeneracy note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
…t rate

Follow-through on the degeneracy hedge. The bullet under the headline still
quoted the unconditional per-unit reading ("relax by one unit and the objective
improves by DualValue") two lines below the hedge that conditions it. Align it:
the ranking of binding constraints by |DualValue| is the degeneracy-robust read;
a single dual is a direction, not a guaranteed per-unit rate. Points to the
existing 'When a dual is soft' section.

Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal cafzal requested a review from mlubin June 29, 2026 22:19
that is the exact change in objective per unit relaxed; under **degeneracy** (common in practice) it
is one-sided, so read it as a direction (see *When a dual is soft*).

- A **binding** constraint (`Slack ≈ 0`) carries a nonzero dual — it is actively limiting

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.

The implication is in one direction. Slack > 0 implies zero dual. Slack = 0 does not imply nonzero dual (if it doesn't hold, you have a form of degeneracy)

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.

Fixed: now stated one-way, with the 'binding but no dual' case noted as degeneracy.

- A **binding** constraint (`Slack ≈ 0`) carries a nonzero dual — it is actively limiting
the objective. A **slack** constraint (`Slack > 0`) prices to ~0: relaxing it changes
nothing, because it is not the bottleneck.
- **Rank the binding constraints by `|DualValue|`** → the largest is the highest-leverage limit to

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.

You need some level of comparability of the units of the constraints for this ranking to make sense. You can always scale a constraint (multiply the coefficients and right-hand side by a constant) to increase or decrease the optimal dual, which would change this ranking but have no real meaning in practice.

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.

Fixed: ranking scoped to comparable units, with a common-scale conversion for mixed ones

## Reduced cost — how far an unused option is from entering

A variable resting at a bound (often `0`) carries a `ReducedCost`: how much its objective
coefficient must improve before it would enter the optimal solution. It is the **near-miss** signal.

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.

"enter the optimal solution" - the variable is already in the optimal solution, with a value of zero.
"how much its objective coefficient must improve" - what does this mean precisely? You're allowed to have a variable at it's bound 0 with zero reduced cost (again, degeneracy).

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.

Fixed: now stated as the pay-off threshold on the coefficient with the at-bound zero-reduced-cost case noted as degeneracy

coefficient must improve before it would enter the optimal solution. It is the **near-miss** signal.

- A variable with `Value > 0` is already in the mix; its reduced cost is ~0.
- Among the variables left at `0`, the one with the **smallest `|ReducedCost|`** is closest to

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.

Variables need to be in comparable units for this sorting to make sense, see above.

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.

Fixed: same unit scoping for reduced costs, or compared as a fraction of each variable's own coefficient.


## When a dual is soft (degeneracy)

A dual is exact for the basis the solver returned, but at a **degenerate** optimum — many

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.

cuOpt often doesn't return a basis. It depends on which solver ran.

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.

Fixed: the degeneracy section now covers the no-basis (PDLP) case.

- Report the **ranking** of binding constraints as solid; present a single dual as a *direction*
("this is the lever to renegotiate"), not a hard per-unit rate.
- Confirm any rate you quote with the one-unit re-solve (below): if the objective change does not
match `DualValue`, the optimum is degenerate — give the direction, not the number.

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.

This isn't correct. Even if there's no degeneracy, the dual just gives you the marginal change but not the actual change for a unit perturbation; the basis could change multiple times in the process of solving the perturbed problem.

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.

Fixed: the dual is now presented as a marginal rate, with any finite change quoted from a differencing re-solve

- Confirm any rate you quote with the one-unit re-solve (below): if the objective change does not
match `DualValue`, the optimum is degenerate — give the direction, not the number.

An LP / simplex effect; a strictly convex QP (quadratic _objective_, not constraint) has unique

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.

Most QPs are not strictly convex.

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.

Fixed: cut the claim.

…arability, no-basis duals

- State the slack/dual implication one-way: slack > 0 forces a ~0 dual, but
  a binding constraint can still price to 0 (degeneracy)
- Caveat the |DualValue| ranking: only meaningful across constraints in
  comparable units; suggest a common scale (e.g. value of a 1% relaxation)
- Same units caveat for sorting unused variables by |ReducedCost|
- Note cuOpt often returns no basis (PDLP / concurrent path): duals are
  tolerance-accurate directions and reduced costs lose the crisp at-bound split
@cafzal cafzal force-pushed the agent-duals-narration branch from 95076da to ec792c7 Compare July 2, 2026 03:42
claude and others added 7 commits July 2, 2026 03:57
The one-way implication, unit-comparability, and no-basis caveats were
stated once but contradicted downstream. Align the rest of the doc:

- Ranking snippets, the decision-read summary, the max-supply example,
  and the soft-dual bullet now all scope |dual| / |reduced cost|
  comparisons to comparable-unit groups
- Sign-conventions re-solve check conditioned on non-degeneracy instead
  of asserting the difference always matches the dual
- QP aside no longer claims unique duals outright (degenerate active
  constraints can leave multipliers non-unique)
- Drop a redundant restatement of the implication direction
… drop strict-convexity aside

- Even without degeneracy the active set can change within a finite
  step, so dual x step is not the realized change: present the dual as
  a derivative and route any finite-change quote through a differencing
  re-solve (intro, snippet label, soft-dual bullet, sign conventions,
  decision read, MILP callout)
- Remove the strictly-convex-QP reassurance: most QPs are not strictly
  convex, and the claim carried no decision weight
…-RC-at-bound as degenerate

Signed-off-by: cafzal <cameron.afzal@gmail.com>
…, rankings as shortlists, between-bounds not Value>0

Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal cafzal requested a review from mlubin July 2, 2026 04:44
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.

3 participants