cuopt-agent: add duals-interpretation guidance to the debugging skill#159
cuopt-agent: add duals-interpretation guidance to the debugging skill#159cafzal wants to merge 14 commits into
Conversation
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
|
@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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…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>
| 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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
"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).
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Variables need to be in comparable units for this sorting to make sense, see above.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
cuOpt often doesn't return a basis. It depends on which solver ran.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Most QPs are not strictly convex.
There was a problem hiding this comment.
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
95076da to
ec792c7
Compare
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
…oss, could-enter wording)
…-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>
What
Adds
resources/interpreting_duals.mdto the cuopt-agent'scuopt-debuggingskill: guidance for turning a solved LP/QP'sDualValue/ReducedCost/Slackinto 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'sSKILL.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
resources/markdown plus a one-lineSKILL.mdpointer; no code or model changes.Companion to the dual / sensitivity work in #154 (LP duals in the diet example) and #157 (the agent's multi-objective scenario).