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 16 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.
There was a problem hiding this comment.
Barrier also does not need to return a basis
There was a problem hiding this comment.
Fixed: generalized to any basis-free solve
| - 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>
| > **Continuous models, linear constraints.** Duals and reduced costs exist for **continuous** | ||
| > (LP / QP) solutions off **linear** constraints. Two cases return none: an **integer model | ||
| > (MILP)** — **including the max-supply model** — has no usable duals, and a **quadratic | ||
| > _constraint_** makes cuOpt NaN-fill every dual (a quadratic _objective_ is fine — it is a |
There was a problem hiding this comment.
We have this description of the NaN behavior written down in many skills. What happens in an upcoming release when the behavior changes and duals are available for quadratic constraints?
There was a problem hiding this comment.
Removed, references other skill instead.
| print(f"{name}: dual {dual:+.4g} (marginal rate as this limit relaxes)") | ||
| ``` | ||
|
|
||
| In the max-supply shape the constraints that typically bind are the **resource-hour capacities** |
There was a problem hiding this comment.
What is "max-supply shape"?
There was a problem hiding this comment.
Clarified to "model"
| It is the **near-miss** signal. | ||
|
|
||
| - A variable strictly **between** its bounds prices to ~0. One pressed against an *upper* bound can | ||
| carry a nonzero reduced cost with `Value > 0` — there it reads as the marginal value of raising |
There was a problem hiding this comment.
Is this description consistent with the discussion of signs below?
There was a problem hiding this comment.
Good catch. Now stated as a magnitude
|
|
||
| ## 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.
Barrier also does not need to return a basis
- Defer the duals-availability rule to its canonical statement (the
cuopt-numerical-optimization-api skill, Dual Values) instead of
restating it; keep a runtime check - finite duals are provided,
NaN-filled are not - that stays correct across releases.
- At-upper-bound reduced cost stated as a magnitude, direction read
as for any bound relaxation (Sign conventions).
- No-basis treatment generalized: PDLP or barrier/interior-point
without crossover; the exact-for-its-basis claim scoped to simplex.
- 'max-supply shape' named plainly: the max-supply model, this
agent's multi-period supply-chain planning example.
- Apply reviewer wording suggestion ('This page explains').
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).