From fe13bd50f9ba8789466bb1f070e03aa9a92af79c Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 8 Jun 2026 14:57:14 -0700 Subject: [PATCH 01/11] Read epsilon-constraint duals as frontier slope / exchange rate in multi-objective skill Extends the dual/sensitivity work from #1393 into the multi-objective layer: the dual on a swept epsilon-constraint is the local slope of the Pareto frontier (the exchange rate between the two objectives), available off each LP/QP solve at no extra cost. - Step 3: note that the swept constraint's dual is the frontier's local tangent; defers "what duals are / which problem types expose them" to the formulation skill (no API symbols inlined). - Step 4: a practical-notes bullet to refine the sweep where the slope changes, spending fewer solves than a uniform grid (LP/QP; MILP falls back to gaps between primal objective values). - Step 5: the exchange rate is read from the dual on LP/QP (differencing on MILP), and the epsilon-constraint duals are the implicit weights per point. - evals: add a QP risk-return frontier task exercising dual-as-exchange-rate; clarify the MILP supplier eval to estimate the rate by differencing. Concepts/workflow skill: LP/QP-only scoping preserved, no maturity labels. BENCHMARK.md, the skill card, and the signature are regenerated by NVSkills CI. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- .../cuopt-multi-objective-exploration/SKILL.md | 7 +++++-- .../evals/evals.json | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index cfb8da0b6..11f70e4b0 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -96,6 +96,8 @@ Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). +**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. + **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. ## Step 4 — sweep, collect, and filter @@ -117,14 +119,15 @@ Practical notes: - **Cap each MILP solve.** Set a per-solve time limit on MILP sweeps (see `cuopt-numerical-optimization-api-python`) — a sweep is many solves, and branch-and-bound can over-spend certifying optimality past a tiny gap, while cuOpt sets no limit by default and won't warn. Report the points as optimal *to the gap you set*, not certified optimal. - **Filter dominated points.** A correct sweep can still emit dominated points (especially weighted-sum near the hull, or MILP). Drop them; they are not part of the frontier. - **Resolution is a budget.** Curve fidelity trades against solve count. Start coarse to see the shape, then refine the grid only where the curve bends. +- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps, there's a knee — refine there. This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. MILP has no duals, so judge where to refine from the gaps between primal objective values instead. - **Verify, don't assume.** When you claim one method beats another, measure it — e.g. count the efficient points ε-constraint recovered that weighted-sum missed — rather than asserting it; and flag any solve returning feasible-but-not-`Optimal` so a non-certified point is never read as exact. ## Step 5 — interpret the frontier -- **Report tradeoffs, not single numbers.** A frontier point means nothing in isolation. Quote the exchange rate — "≈ $4k of extra cost per 1% of added coverage in this region" — so the user can judge whether a move is worth it. +- **Report tradeoffs, not single numbers.** A frontier point means nothing in isolation. Quote the exchange rate — "≈ $4k of extra cost per 1% of added coverage in this region" — so the user can judge whether a move is worth it. On an LP/QP frontier this exchange rate *is* the swept constraint's dual at that point (exact and local); on MILP, estimate it from the gap to the adjacent frontier point. - **Flag knee points; don't auto-pick them.** The "knee" is where the curve bends most sharply — beyond it you pay a lot for a little. It's often the best-balanced compromise and worth highlighting, but the final choice is the user's preference, not a rule. - **Treat dominated or gappy output as a diagnostic.** If dominated points survive filtering, or the frontier is implausibly sparse or perfectly linear, suspect the sweep or the model — most often weighted-sum hiding a concave region (switch to ε-constraint) or a normalization mistake. -- **State the weighting/ε you used.** Every reported point is conditional on its scalarization. Make that explicit so a single solve is never mistaken for "the" optimum. +- **State the weighting/ε you used.** Every reported point is conditional on its scalarization. Make that explicit so a single solve is never mistaken for "the" optimum. On LP/QP, the ε-constraint duals are the *implicit weights* at that point — the effective price the solution puts on each constrained objective, and the weights a weighted-sum solve would need to reproduce it. Reporting them makes the accepted tradeoff ratio explicit. ## Interfaces diff --git a/skills/cuopt-multi-objective-exploration/evals/evals.json b/skills/cuopt-multi-objective-exploration/evals/evals.json index 2e2bcef63..b4fec36d4 100644 --- a/skills/cuopt-multi-objective-exploration/evals/evals.json +++ b/skills/cuopt-multi-objective-exploration/evals/evals.json @@ -24,7 +24,22 @@ "Builds a payoff table (each objective alone) for ranges/normalization", "Traces the Pareto frontier via repeated single-objective cuOpt solves; prefers epsilon-constraint over weighted-sum for completeness on a non-convex/MILP problem", "Filters dominated and duplicate portfolios", - "Reports the tradeoff and flags the knee, leaving the final pick to the lead" + "Reports the tradeoff and flags the knee, leaving the final pick to the lead", + "Since supplier selection is MILP (no duals), estimates the cost/resilience exchange rate by differencing adjacent frontier points, not from constraint duals" + ] + }, + { + "id": "multiobj-explore-eval-004-dual-exchange-rate", + "question": "An analyst is building a risk-return efficient frontier across a set of assets: minimize portfolio variance and maximize expected return, with no fixed risk appetite. Using cuOpt, how would you trace the frontier, and at each point how would you tell the investor the rate at which they are paying variance for extra return without spending additional solves?", + "expected_skill": "cuopt-multi-objective-exploration", + "expected_script": null, + "ground_truth": "The agent recognizes a two-objective tradeoff (variance vs return) with no fixed weighting and traces the frontier by epsilon-constraint: it keeps the quadratic variance as the objective and sweeps a linear return floor (return >= epsilon), each value a single convex QP solve. At each solved point it reads the dual value on the binding return-floor constraint as the marginal variance per unit of required return, which is the local exchange rate, i.e. the slope (tangent) of the frontier at that point. That number is taken directly off the solve at no extra cost rather than by differencing two adjacent points. The agent notes this dual reading applies to LP/QP only, since MILP optima have no duals. It filters dominated points, flags the knee, and leaves the risk appetite to the investor, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", + "expected_behavior": [ + "Frames variance vs return as two competing objectives with no agreed weighting", + "Traces the frontier via epsilon-constraint, keeping the quadratic variance as the objective and sweeping a linear return floor", + "Reads the swept constraint's dual as the local exchange rate (marginal variance per unit return) and slope of the frontier, taken straight off each solve rather than by differencing", + "Notes the dual / exchange-rate reading applies to LP/QP only, not MILP", + "Filters dominated points, flags the knee, and leaves the final risk appetite to the investor" ] }, { From 8ec84d8f0339413c9bcd6ec77c736a5333a590e9 Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 8 Jun 2026 15:29:50 -0700 Subject: [PATCH 02/11] Refine dual-accuracy wording: solve tolerance, knee two-sidedness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses duality-review feedback (a PDLP co-author's lens). cuOpt's default LP method is Concurrent — it races PDLP (first-order), dual simplex, and barrier — so a returned dual is exact-basic only when dual simplex solved it and accurate only to the optimality tolerance otherwise. Three concepts-level, stale-robust edits (no method names in the skill text): - Step 5: drop "exact and local"; the exchange rate is the dual "accurate to the solve's optimality tolerance (tighten it before relying on a dual)." - Step 4: treat a slope change as a knee only when it exceeds the solve tolerance — smaller differences are solver noise, not curvature. - Step 5: at a knee the slope is two-sided; quote the exchange rate as a range. Consistent with the "PDLP-tolerance caveat" already noted in #1355. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 11f70e4b0..1ef03fad3 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -119,13 +119,13 @@ Practical notes: - **Cap each MILP solve.** Set a per-solve time limit on MILP sweeps (see `cuopt-numerical-optimization-api-python`) — a sweep is many solves, and branch-and-bound can over-spend certifying optimality past a tiny gap, while cuOpt sets no limit by default and won't warn. Report the points as optimal *to the gap you set*, not certified optimal. - **Filter dominated points.** A correct sweep can still emit dominated points (especially weighted-sum near the hull, or MILP). Drop them; they are not part of the frontier. - **Resolution is a budget.** Curve fidelity trades against solve count. Start coarse to see the shape, then refine the grid only where the curve bends. -- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps, there's a knee — refine there. This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. MILP has no duals, so judge where to refine from the gaps between primal objective values instead. +- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps by more than the solve tolerance, there's a knee — refine there (smaller differences are solver noise, not curvature). This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. MILP has no duals, so judge where to refine from the gaps between primal objective values instead. - **Verify, don't assume.** When you claim one method beats another, measure it — e.g. count the efficient points ε-constraint recovered that weighted-sum missed — rather than asserting it; and flag any solve returning feasible-but-not-`Optimal` so a non-certified point is never read as exact. ## Step 5 — interpret the frontier -- **Report tradeoffs, not single numbers.** A frontier point means nothing in isolation. Quote the exchange rate — "≈ $4k of extra cost per 1% of added coverage in this region" — so the user can judge whether a move is worth it. On an LP/QP frontier this exchange rate *is* the swept constraint's dual at that point (exact and local); on MILP, estimate it from the gap to the adjacent frontier point. -- **Flag knee points; don't auto-pick them.** The "knee" is where the curve bends most sharply — beyond it you pay a lot for a little. It's often the best-balanced compromise and worth highlighting, but the final choice is the user's preference, not a rule. +- **Report tradeoffs, not single numbers.** A frontier point means nothing in isolation. Quote the exchange rate — "≈ $4k of extra cost per 1% of added coverage in this region" — so the user can judge whether a move is worth it. On an LP/QP frontier this exchange rate is the swept constraint's dual at that point — the local slope of the frontier, accurate to the solve's optimality tolerance (tighten it before relying on a dual); on MILP, estimate it from the gap to the adjacent frontier point. +- **Flag knee points; don't auto-pick them.** The "knee" is where the curve bends most sharply — beyond it you pay a lot for a little. It's often the best-balanced compromise and worth highlighting, but the final choice is the user's preference, not a rule. At the knee the slope is two-sided — the dual just below differs from just above — so quote the exchange rate there as a range, not one number. - **Treat dominated or gappy output as a diagnostic.** If dominated points survive filtering, or the frontier is implausibly sparse or perfectly linear, suspect the sweep or the model — most often weighted-sum hiding a concave region (switch to ε-constraint) or a normalization mistake. - **State the weighting/ε you used.** Every reported point is conditional on its scalarization. Make that explicit so a single solve is never mistaken for "the" optimum. On LP/QP, the ε-constraint duals are the *implicit weights* at that point — the effective price the solution puts on each constrained objective, and the weights a weighted-sum solve would need to reproduce it. Reporting them makes the accepted tradeoff ratio explicit. From c8d65c732d83e237828c9bae2a7044a585370674 Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 8 Jun 2026 15:39:34 -0700 Subject: [PATCH 03/11] Fact-check fixes: LP eval + no-dual-for-quadratic-constraint caveat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against the cuOpt repo + docs. Two corrections: - cuOpt returns no dual variables for problems with quadratic constraints (docs/cuopt/source/cuopt-c/convex/convex-examples.rst). The skill offers a quadratic ε-constraint (xᵀQx ≤ ε) option, which would have no dual to read. Step 3 now says: read the dual off a *linear* ε-constraint — keep a quadratic objective as f1 and ε-constrain the linear objectives. - Switch the new eval from a QP risk-return frontier to an LP cost-vs-quality blend. LP duals are the tested, documented path (lp_duals asset); QP-dual reading is untested and the api-* skills scope duals to "LP only", so the eval should exercise the verified path. Skill prose stays "LP/QP" per the formulation skill. Separate (not in this PR): the formulation skill's "no quadratic constraints" is stale (cuOpt supports convex quadratic constraints via barrier/SOC), and the api-python/api-c "dual values (LP only)" likely needs "LP/QP, linear constraints" — flagged for follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 2 +- .../evals/evals.json | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 1ef03fad3..b97893558 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -96,7 +96,7 @@ Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. +**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. And read it off a *linear* ε-constraint — cuOpt returns no dual for a quadratic constraint, so keep any quadratic objective as `f1` and ε-constrain the linear objectives, rather than imposing `xᵀQx ≤ ε`. **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. diff --git a/skills/cuopt-multi-objective-exploration/evals/evals.json b/skills/cuopt-multi-objective-exploration/evals/evals.json index b4fec36d4..46f8d7199 100644 --- a/skills/cuopt-multi-objective-exploration/evals/evals.json +++ b/skills/cuopt-multi-objective-exploration/evals/evals.json @@ -30,16 +30,16 @@ }, { "id": "multiobj-explore-eval-004-dual-exchange-rate", - "question": "An analyst is building a risk-return efficient frontier across a set of assets: minimize portfolio variance and maximize expected return, with no fixed risk appetite. Using cuOpt, how would you trace the frontier, and at each point how would you tell the investor the rate at which they are paying variance for extra return without spending additional solves?", + "question": "A blend planner mixes feedstocks into a product: minimize total cost and maximize a quality score, with no fixed weighting between them and linear blending constraints. Using cuOpt, how would you trace the cost-vs-quality frontier, and at each point how would you tell the planner the rate at which extra quality costs money without running additional solves?", "expected_skill": "cuopt-multi-objective-exploration", "expected_script": null, - "ground_truth": "The agent recognizes a two-objective tradeoff (variance vs return) with no fixed weighting and traces the frontier by epsilon-constraint: it keeps the quadratic variance as the objective and sweeps a linear return floor (return >= epsilon), each value a single convex QP solve. At each solved point it reads the dual value on the binding return-floor constraint as the marginal variance per unit of required return, which is the local exchange rate, i.e. the slope (tangent) of the frontier at that point. That number is taken directly off the solve at no extra cost rather than by differencing two adjacent points. The agent notes this dual reading applies to LP/QP only, since MILP optima have no duals. It filters dominated points, flags the knee, and leaves the risk appetite to the investor, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", + "ground_truth": "The agent recognizes a two-objective tradeoff (cost vs quality) with no fixed weighting and traces the frontier by epsilon-constraint: minimize cost subject to a quality floor (quality >= epsilon), sweeping the floor, each value a single LP solve. At each solved point it reads the dual value on the binding quality-floor constraint as the marginal cost per unit of required quality -- the local exchange rate, i.e. the slope (tangent) of the frontier at that point -- taken directly off the solve at no extra cost rather than by differencing two adjacent points, and accurate to the solve's optimality tolerance. It notes this dual reading applies to continuous LP/QP solutions, not MILP. It filters dominated points, flags the knee, and leaves the final balance to the planner, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", "expected_behavior": [ - "Frames variance vs return as two competing objectives with no agreed weighting", - "Traces the frontier via epsilon-constraint, keeping the quadratic variance as the objective and sweeping a linear return floor", - "Reads the swept constraint's dual as the local exchange rate (marginal variance per unit return) and slope of the frontier, taken straight off each solve rather than by differencing", - "Notes the dual / exchange-rate reading applies to LP/QP only, not MILP", - "Filters dominated points, flags the knee, and leaves the final risk appetite to the investor" + "Frames cost vs quality as two competing objectives with no agreed weighting", + "Traces the frontier via epsilon-constraint (minimize cost subject to a swept quality floor), each a single LP solve", + "Reads the swept constraint's dual as the local exchange rate (marginal cost per unit quality) and slope of the frontier, taken straight off each solve rather than by differencing", + "Notes the dual / exchange-rate reading applies to continuous LP/QP, not MILP, and is accurate to the solve tolerance", + "Filters dominated points, flags the knee, and leaves the final pick to the planner" ] }, { From 992e14d3856f5a9c48a4a154d7c8d53385526c93 Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 8 Jun 2026 15:53:05 -0700 Subject: [PATCH 04/11] Restore QP eval: QP duals confirmed (barrier, 26.06) Reverts the over-conservative QP->LP eval switch. Git history confirms QP duals are real and recent: barrier extended for SOCP (#1290, 2026-05-30) and general convex quadratic constraints (#1361, 2026-06-02); the barrier solver is primal-dual and pdlp/solve.cu returns dual_solution + reduced_cost for the Barrier method. The api-python/api-c "dual values (LP only)" wording predates this (#1183, 2026-05-07) and is the stale part, not the formulation skill's "LP/QP" (#1393). The QP risk-return eval sweeps a *linear* return floor, whose dual is returned (cuOpt returns no dual for a quadratic constraint, so the quadratic stays the objective). Skill prose unchanged (already "LP/QP" + linear-constraint caveat). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- .../evals/evals.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/evals/evals.json b/skills/cuopt-multi-objective-exploration/evals/evals.json index 46f8d7199..7c41f15d0 100644 --- a/skills/cuopt-multi-objective-exploration/evals/evals.json +++ b/skills/cuopt-multi-objective-exploration/evals/evals.json @@ -30,16 +30,16 @@ }, { "id": "multiobj-explore-eval-004-dual-exchange-rate", - "question": "A blend planner mixes feedstocks into a product: minimize total cost and maximize a quality score, with no fixed weighting between them and linear blending constraints. Using cuOpt, how would you trace the cost-vs-quality frontier, and at each point how would you tell the planner the rate at which extra quality costs money without running additional solves?", + "question": "An analyst is building a risk-return efficient frontier across a set of assets: minimize portfolio variance and maximize expected return, with no fixed risk appetite. Using cuOpt, how would you trace the frontier, and at each point how would you tell the investor the rate at which they are paying variance for extra return without spending additional solves?", "expected_skill": "cuopt-multi-objective-exploration", "expected_script": null, - "ground_truth": "The agent recognizes a two-objective tradeoff (cost vs quality) with no fixed weighting and traces the frontier by epsilon-constraint: minimize cost subject to a quality floor (quality >= epsilon), sweeping the floor, each value a single LP solve. At each solved point it reads the dual value on the binding quality-floor constraint as the marginal cost per unit of required quality -- the local exchange rate, i.e. the slope (tangent) of the frontier at that point -- taken directly off the solve at no extra cost rather than by differencing two adjacent points, and accurate to the solve's optimality tolerance. It notes this dual reading applies to continuous LP/QP solutions, not MILP. It filters dominated points, flags the knee, and leaves the final balance to the planner, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", + "ground_truth": "The agent recognizes a two-objective tradeoff (variance vs return) with no fixed weighting and traces the frontier by epsilon-constraint: it keeps the quadratic variance as the objective and sweeps a linear return floor (return >= epsilon), each value a single convex QP solve. At each solved point it reads the dual value on the binding (linear) return-floor constraint as the marginal variance per unit of required return -- the local exchange rate, i.e. the slope (tangent) of the frontier at that point -- taken directly off the solve at no extra cost rather than by differencing two adjacent points, and accurate to the solve's optimality tolerance. It notes the dual reading applies to continuous LP/QP solutions, not MILP, and that the swept constraint must be linear since cuOpt returns no dual for a quadratic constraint. It filters dominated points, flags the knee, and leaves the risk appetite to the investor, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", "expected_behavior": [ - "Frames cost vs quality as two competing objectives with no agreed weighting", - "Traces the frontier via epsilon-constraint (minimize cost subject to a swept quality floor), each a single LP solve", - "Reads the swept constraint's dual as the local exchange rate (marginal cost per unit quality) and slope of the frontier, taken straight off each solve rather than by differencing", + "Frames variance vs return as two competing objectives with no agreed weighting", + "Traces the frontier via epsilon-constraint, keeping the quadratic variance as the objective and sweeping a linear return floor (each a single QP solve)", + "Reads the swept (linear) constraint's dual as the local exchange rate (marginal variance per unit return) and slope of the frontier, taken straight off each solve rather than by differencing", "Notes the dual / exchange-rate reading applies to continuous LP/QP, not MILP, and is accurate to the solve tolerance", - "Filters dominated points, flags the knee, and leaves the final pick to the planner" + "Filters dominated points, flags the knee, and leaves the final risk appetite to the investor" ] }, { From 84a985650914157ae52314e3b56f90c0ac02dbb1 Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 8 Jun 2026 16:06:48 -0700 Subject: [PATCH 05/11] =?UTF-8?q?Note=20zero=20dual=20=3D=20slack=20=CE=B5?= =?UTF-8?q?-bound=20(complementary=20slackness)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A zero shadow price means the constraint is slack (not binding), so during a sweep a zero dual on an ε-bound signals the objectives aren't trading off there — the sweep has run past the frontier's edge. Small precision/diagnostic addition; consistent with the dual-as-slope content already in Step 3. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index b97893558..40554cd84 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -96,7 +96,7 @@ Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. And read it off a *linear* ε-constraint — cuOpt returns no dual for a quadratic constraint, so keep any quadratic objective as `f1` and ε-constrain the linear objectives, rather than imposing `xᵀQx ≤ ε`. +**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. A **zero** dual means that ε-bound is slack rather than binding — the objectives aren't trading off against it there, a sign the sweep has run past the frontier's edge. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. And read it off a *linear* ε-constraint — cuOpt returns no dual for a quadratic constraint, so keep any quadratic objective as `f1` and ε-constrain the linear objectives, rather than imposing `xᵀQx ≤ ε`. **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. From 2f407c102ee149af640ca7a42a464500842586c9 Mon Sep 17 00:00:00 2001 From: cafzal Date: Mon, 8 Jun 2026 16:20:27 -0700 Subject: [PATCH 06/11] Align quadratic-constraint dual caveat with verified whole-solve behavior cpp/src/pdlp/solve.cu (has_quadratic_constraints -> thrust::fill the entire dual_solution + reduced_cost with quiet_NaN) shows a single quadratic constraint suppresses duals for the *whole* solve, not just that constraint's row. Reword "no dual for a quadratic constraint" -> "any quadratic constraint makes cuOpt return no duals for the whole solve" in Step 3 and the QP eval, for consistency with PRs #1407 (formulation) and #1408 (api skills, "NaN-filled"). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 2 +- skills/cuopt-multi-objective-exploration/evals/evals.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 40554cd84..545d569c8 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -96,7 +96,7 @@ Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. A **zero** dual means that ε-bound is slack rather than binding — the objectives aren't trading off against it there, a sign the sweep has run past the frontier's edge. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. And read it off a *linear* ε-constraint — cuOpt returns no dual for a quadratic constraint, so keep any quadratic objective as `f1` and ε-constrain the linear objectives, rather than imposing `xᵀQx ≤ ε`. +**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. A **zero** dual means that ε-bound is slack rather than binding — the objectives aren't trading off against it there, a sign the sweep has run past the frontier's edge. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. And read it off a *linear* ε-constraint — any quadratic constraint makes cuOpt return no duals for the whole solve, so keep any quadratic objective as `f1` and ε-constrain the linear objectives, rather than imposing `xᵀQx ≤ ε`. **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. diff --git a/skills/cuopt-multi-objective-exploration/evals/evals.json b/skills/cuopt-multi-objective-exploration/evals/evals.json index 7c41f15d0..ff2158104 100644 --- a/skills/cuopt-multi-objective-exploration/evals/evals.json +++ b/skills/cuopt-multi-objective-exploration/evals/evals.json @@ -33,7 +33,7 @@ "question": "An analyst is building a risk-return efficient frontier across a set of assets: minimize portfolio variance and maximize expected return, with no fixed risk appetite. Using cuOpt, how would you trace the frontier, and at each point how would you tell the investor the rate at which they are paying variance for extra return without spending additional solves?", "expected_skill": "cuopt-multi-objective-exploration", "expected_script": null, - "ground_truth": "The agent recognizes a two-objective tradeoff (variance vs return) with no fixed weighting and traces the frontier by epsilon-constraint: it keeps the quadratic variance as the objective and sweeps a linear return floor (return >= epsilon), each value a single convex QP solve. At each solved point it reads the dual value on the binding (linear) return-floor constraint as the marginal variance per unit of required return -- the local exchange rate, i.e. the slope (tangent) of the frontier at that point -- taken directly off the solve at no extra cost rather than by differencing two adjacent points, and accurate to the solve's optimality tolerance. It notes the dual reading applies to continuous LP/QP solutions, not MILP, and that the swept constraint must be linear since cuOpt returns no dual for a quadratic constraint. It filters dominated points, flags the knee, and leaves the risk appetite to the investor, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", + "ground_truth": "The agent recognizes a two-objective tradeoff (variance vs return) with no fixed weighting and traces the frontier by epsilon-constraint: it keeps the quadratic variance as the objective and sweeps a linear return floor (return >= epsilon), each value a single convex QP solve. At each solved point it reads the dual value on the binding (linear) return-floor constraint as the marginal variance per unit of required return -- the local exchange rate, i.e. the slope (tangent) of the frontier at that point -- taken directly off the solve at no extra cost rather than by differencing two adjacent points, and accurate to the solve's optimality tolerance. It notes the dual reading applies to continuous LP/QP solutions, not MILP, and that the swept constraint must be linear since any quadratic constraint makes cuOpt return no duals for the whole solve. It filters dominated points, flags the knee, and leaves the risk appetite to the investor, deferring per-solve mechanics to the api-* skills and formulation to cuopt-numerical-optimization-formulation.", "expected_behavior": [ "Frames variance vs return as two competing objectives with no agreed weighting", "Traces the frontier via epsilon-constraint, keeping the quadratic variance as the objective and sweeping a linear return floor (each a single QP solve)", From 46a684c536789d5288c215bb6bb745a14e5601c5 Mon Sep 17 00:00:00 2001 From: cafzal Date: Tue, 9 Jun 2026 09:48:18 -0700 Subject: [PATCH 07/11] Tighten Step 3 dual note: MECE, distilled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Condense the dual-as-slope note to four non-overlapping points — what it is (slope/exchange rate/tangent, read off the solve free), the zero-dual=slack diagnostic, scope+fallback (LP/QP, linear ε-constraint only; MILP and quadratic constraints have none, so difference instead), and the formulation cross-ref. Same facts, ~20% shorter; drops "exact" (kept in Step 5 as "to solve tolerance"). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 545d569c8..0e59fe4df 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -96,7 +96,7 @@ Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**What that dual is: the frontier's local slope.** On each ε-constraint solve, the dual value on `f_k(x) ≤ ε_k` is the marginal change in the kept objective `f1` per unit change in `ε_k` — the exchange rate between the two objectives at that point, available straight off the solve at no extra cost (`cuopt-numerical-optimization-formulation` covers which problem types expose duals and what a shadow price means). It is the *tangent* of the frontier there, where differencing two solved points gives only a secant smeared across whatever bend lies between them. A **zero** dual means that ε-bound is slack rather than binding — the objectives aren't trading off against it there, a sign the sweep has run past the frontier's edge. MILP optima have no duals, so on a MILP frontier the exchange rate comes from differencing adjacent points instead. And read it off a *linear* ε-constraint — any quadratic constraint makes cuOpt return no duals for the whole solve, so keep any quadratic objective as `f1` and ε-constrain the linear objectives, rather than imposing `xᵀQx ≤ ε`. +**That dual is the frontier's local slope.** The dual on a swept ε-constraint is how much the kept objective `f1` moves per unit of that bound — the exchange rate between the two objectives, and the frontier's tangent at that point, read off the solve for free (differencing two solves gives only a secant across any bend between them). A **zero** dual means the bound is slack, not binding — no tradeoff there, so the sweep has run past the frontier's edge. It applies to **LP/QP** off a **linear** ε-constraint only: MILP optima have no duals, and any quadratic constraint voids them for the whole solve, so keep a quadratic objective as `f1` and ε-constrain the linear ones; where no dual is available, difference adjacent points instead. (`cuopt-numerical-optimization-formulation` defines shadow price and per-type dual support.) **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. From d08d29559e0d89e2a6be073ebcf7332dd6ff0c92 Mon Sep 17 00:00:00 2001 From: cafzal Date: Tue, 9 Jun 2026 14:35:46 -0700 Subject: [PATCH 08/11] Align dual wording with #1408 review: drop "shadow price" and "barrier solver" - Step 5 cross-ref: "defines shadow price ..." -> "covers what duals mean and which problem types expose them". - Step 3 quadratic-constraint note: drop "the barrier solver" (the solve method isn't part of the dual contract); keep the second-order-cone constraint form. Consistent with the trim applied to the api-* skills in #1408. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 0e59fe4df..0d086f189 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -92,11 +92,11 @@ subject to f2(x) ≤ ε2 Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` combination is a single standard cuOpt solve. This recovers the **full** frontier, including the concave regions weighted-sum cannot reach, which is why it's the default when completeness matters. The cost is more solves (a grid over the constrained objectives) and bookkeeping of the ε values. -ε-constrain *linear* objectives directly. A quadratic objective (e.g. risk `xᵀΣx`) is simplest kept as the objective `f1` while you ε-constrain the linear ones. A **convex** quadratic objective *can* instead be ε-constrained directly: add it as a quadratic constraint `xᵀQx ≤ ε` (Q positive semidefinite, inequality only), which cuOpt routes through the barrier solver as a second-order cone. Non-convex or equality quadratic constraints are unsupported, and the MILP path stays linear-constraint only. +ε-constrain *linear* objectives directly. A quadratic objective (e.g. risk `xᵀΣx`) is simplest kept as the objective `f1` while you ε-constrain the linear ones. A **convex** quadratic objective *can* instead be ε-constrained directly: add it as a quadratic constraint `xᵀQx ≤ ε` (Q positive semidefinite, inequality only), which cuOpt handles as a second-order cone. Non-convex or equality quadratic constraints are unsupported, and the MILP path stays linear-constraint only. Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**That dual is the frontier's local slope.** The dual on a swept ε-constraint is how much the kept objective `f1` moves per unit of that bound — the exchange rate between the two objectives, and the frontier's tangent at that point, read off the solve for free (differencing two solves gives only a secant across any bend between them). A **zero** dual means the bound is slack, not binding — no tradeoff there, so the sweep has run past the frontier's edge. It applies to **LP/QP** off a **linear** ε-constraint only: MILP optima have no duals, and any quadratic constraint voids them for the whole solve, so keep a quadratic objective as `f1` and ε-constrain the linear ones; where no dual is available, difference adjacent points instead. (`cuopt-numerical-optimization-formulation` defines shadow price and per-type dual support.) +**That dual is the frontier's local slope.** The dual on a swept ε-constraint is how much the kept objective `f1` moves per unit of that bound — the exchange rate between the two objectives, and the frontier's tangent at that point, read off the solve for free (differencing two solves gives only a secant across any bend between them). A **zero** dual means the bound is slack, not binding — no tradeoff there, so the sweep has run past the frontier's edge. It applies to **LP/QP** off a **linear** ε-constraint only: MILP optima have no duals, and any quadratic constraint voids them for the whole solve, so keep a quadratic objective as `f1` and ε-constrain the linear ones; where no dual is available, difference adjacent points instead. (`cuopt-numerical-optimization-formulation` covers what duals mean and which problem types expose them.) **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. From 65d958ef3dc100f191d07ed0b11b57777baa3067 Mon Sep 17 00:00:00 2001 From: cafzal Date: Tue, 9 Jun 2026 21:41:04 -0700 Subject: [PATCH 09/11] Final-pass refinements: drop redundant parenthetical, precise bend/weights wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Step 3: drop "(Q positive semidefinite, inequality only)" — the sentence already says convex, shows the <= form, and the next sentence covers the non-convex/equality exclusion. Same over-specification flagged on the companion formulation PR. - Step 4: a dual jump between two solved points locates a bend between them, not necessarily the knee (Step 5 defines the knee as the sharpest bend). - Step 5: weighted-sum with the epsilon-duals as weights reproduces that tradeoff, not necessarily the identical point (flat segments tie). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 0d086f189..70279f128 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -92,7 +92,7 @@ subject to f2(x) ≤ ε2 Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` combination is a single standard cuOpt solve. This recovers the **full** frontier, including the concave regions weighted-sum cannot reach, which is why it's the default when completeness matters. The cost is more solves (a grid over the constrained objectives) and bookkeeping of the ε values. -ε-constrain *linear* objectives directly. A quadratic objective (e.g. risk `xᵀΣx`) is simplest kept as the objective `f1` while you ε-constrain the linear ones. A **convex** quadratic objective *can* instead be ε-constrained directly: add it as a quadratic constraint `xᵀQx ≤ ε` (Q positive semidefinite, inequality only), which cuOpt handles as a second-order cone. Non-convex or equality quadratic constraints are unsupported, and the MILP path stays linear-constraint only. +ε-constrain *linear* objectives directly. A quadratic objective (e.g. risk `xᵀΣx`) is simplest kept as the objective `f1` while you ε-constrain the linear ones. A **convex** quadratic objective *can* instead be ε-constrained directly: add it as a quadratic constraint `xᵀQx ≤ ε`, which cuOpt handles as a second-order cone. Non-convex or equality quadratic constraints are unsupported, and the MILP path stays linear-constraint only. Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). @@ -119,7 +119,7 @@ Practical notes: - **Cap each MILP solve.** Set a per-solve time limit on MILP sweeps (see `cuopt-numerical-optimization-api-python`) — a sweep is many solves, and branch-and-bound can over-spend certifying optimality past a tiny gap, while cuOpt sets no limit by default and won't warn. Report the points as optimal *to the gap you set*, not certified optimal. - **Filter dominated points.** A correct sweep can still emit dominated points (especially weighted-sum near the hull, or MILP). Drop them; they are not part of the frontier. - **Resolution is a budget.** Curve fidelity trades against solve count. Start coarse to see the shape, then refine the grid only where the curve bends. -- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps by more than the solve tolerance, there's a knee — refine there (smaller differences are solver noise, not curvature). This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. MILP has no duals, so judge where to refine from the gaps between primal objective values instead. +- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps by more than the solve tolerance, the frontier bends between those points — refine there (smaller differences are solver noise, not curvature). This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. MILP has no duals, so judge where to refine from the gaps between primal objective values instead. - **Verify, don't assume.** When you claim one method beats another, measure it — e.g. count the efficient points ε-constraint recovered that weighted-sum missed — rather than asserting it; and flag any solve returning feasible-but-not-`Optimal` so a non-certified point is never read as exact. ## Step 5 — interpret the frontier @@ -127,7 +127,7 @@ Practical notes: - **Report tradeoffs, not single numbers.** A frontier point means nothing in isolation. Quote the exchange rate — "≈ $4k of extra cost per 1% of added coverage in this region" — so the user can judge whether a move is worth it. On an LP/QP frontier this exchange rate is the swept constraint's dual at that point — the local slope of the frontier, accurate to the solve's optimality tolerance (tighten it before relying on a dual); on MILP, estimate it from the gap to the adjacent frontier point. - **Flag knee points; don't auto-pick them.** The "knee" is where the curve bends most sharply — beyond it you pay a lot for a little. It's often the best-balanced compromise and worth highlighting, but the final choice is the user's preference, not a rule. At the knee the slope is two-sided — the dual just below differs from just above — so quote the exchange rate there as a range, not one number. - **Treat dominated or gappy output as a diagnostic.** If dominated points survive filtering, or the frontier is implausibly sparse or perfectly linear, suspect the sweep or the model — most often weighted-sum hiding a concave region (switch to ε-constraint) or a normalization mistake. -- **State the weighting/ε you used.** Every reported point is conditional on its scalarization. Make that explicit so a single solve is never mistaken for "the" optimum. On LP/QP, the ε-constraint duals are the *implicit weights* at that point — the effective price the solution puts on each constrained objective, and the weights a weighted-sum solve would need to reproduce it. Reporting them makes the accepted tradeoff ratio explicit. +- **State the weighting/ε you used.** Every reported point is conditional on its scalarization. Make that explicit so a single solve is never mistaken for "the" optimum. On LP/QP, the ε-constraint duals are the *implicit weights* at that point — the effective price the solution puts on each constrained objective, and the weights a weighted-sum solve would need to reproduce that tradeoff. Reporting them makes the accepted tradeoff ratio explicit. ## Interfaces From ea69febd31674694349ba86a89d6b5078f91d23a Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 11 Jun 2026 09:59:42 -0700 Subject: [PATCH 10/11] Address review: apply suggestion, qualify slope claim, trim Step 3 dual note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Line 95: apply reviewer suggestion — "which cuOpt supports" (drop the second-order-cone mechanism; not part of the contract). - Step 3 paragraph cut to a third and the claim qualified: the dual is the frontier's slope only where the frontier is smooth; at a kink it gives a one-sided rate. Drops content carried elsewhere (the keep-quadratic-as-f1 advice in the paragraph above; the formulation cross-ref at lines 43/47). Keeps what is unique: smoothness caveat, zero-dual = slack diagnostic, LP/QP + linear-constraint scope with the differencing fallback. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 70279f128..37f2cf5b7 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -92,11 +92,11 @@ subject to f2(x) ≤ ε2 Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` combination is a single standard cuOpt solve. This recovers the **full** frontier, including the concave regions weighted-sum cannot reach, which is why it's the default when completeness matters. The cost is more solves (a grid over the constrained objectives) and bookkeeping of the ε values. -ε-constrain *linear* objectives directly. A quadratic objective (e.g. risk `xᵀΣx`) is simplest kept as the objective `f1` while you ε-constrain the linear ones. A **convex** quadratic objective *can* instead be ε-constrained directly: add it as a quadratic constraint `xᵀQx ≤ ε`, which cuOpt handles as a second-order cone. Non-convex or equality quadratic constraints are unsupported, and the MILP path stays linear-constraint only. +ε-constrain *linear* objectives directly. A quadratic objective (e.g. risk `xᵀΣx`) is simplest kept as the objective `f1` while you ε-constrain the linear ones. A **convex** quadratic objective *can* instead be ε-constrained directly: add it as a quadratic constraint `xᵀQx ≤ ε`, which cuOpt supports. Non-convex or equality quadratic constraints are unsupported, and the MILP path stays linear-constraint only. Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**That dual is the frontier's local slope.** The dual on a swept ε-constraint is how much the kept objective `f1` moves per unit of that bound — the exchange rate between the two objectives, and the frontier's tangent at that point, read off the solve for free (differencing two solves gives only a secant across any bend between them). A **zero** dual means the bound is slack, not binding — no tradeoff there, so the sweep has run past the frontier's edge. It applies to **LP/QP** off a **linear** ε-constraint only: MILP optima have no duals, and any quadratic constraint voids them for the whole solve, so keep a quadratic objective as `f1` and ε-constrain the linear ones; where no dual is available, difference adjacent points instead. (`cuopt-numerical-optimization-formulation` covers what duals mean and which problem types expose them.) +**Read that dual as the local exchange rate.** Where the frontier is smooth, the dual on a swept ε-constraint is its slope — how much the kept objective `f1` moves per unit of the bound — at no cost beyond the solve already run; at a kink it gives only a one-sided rate, and a **zero** dual means the bound is slack: the sweep has run past the frontier's edge. This reading needs LP/QP and a *linear* ε-constraint (MILP optima and problems with quadratic constraints return no duals) — where duals are unavailable, difference adjacent frontier points instead. **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. From 740a2800b5f288357975462d624092aa7310aec1 Mon Sep 17 00:00:00 2001 From: cafzal Date: Thu, 11 Jun 2026 10:58:56 -0700 Subject: [PATCH 11/11] Polish: split three-idea sentence in Step 3, drop repeated MILP clause in Step 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Step 3: the smooth/kink/zero-dual sentence carried three ideas; split after the kink clause. - Step 4: "MILP has no duals" is established in Step 3 — the bullet keeps just the action ("On MILP, judge where to refine from the gaps..."). Co-Authored-By: Claude Fable 5 Signed-off-by: cafzal --- skills/cuopt-multi-objective-exploration/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/cuopt-multi-objective-exploration/SKILL.md b/skills/cuopt-multi-objective-exploration/SKILL.md index 37f2cf5b7..af973e4dd 100644 --- a/skills/cuopt-multi-objective-exploration/SKILL.md +++ b/skills/cuopt-multi-objective-exploration/SKILL.md @@ -96,7 +96,7 @@ Sweep each `ε_k` across the range from the payoff table. Each `(ε2, ε3, …)` Spot it in existing code: a hand-coded loop over a target or budget value (a return target, a cost cap) is already the ε-constraint method — name it as such, filter dominated points, and read the swept constraint's dual (LP/QP only). -**Read that dual as the local exchange rate.** Where the frontier is smooth, the dual on a swept ε-constraint is its slope — how much the kept objective `f1` moves per unit of the bound — at no cost beyond the solve already run; at a kink it gives only a one-sided rate, and a **zero** dual means the bound is slack: the sweep has run past the frontier's edge. This reading needs LP/QP and a *linear* ε-constraint (MILP optima and problems with quadratic constraints return no duals) — where duals are unavailable, difference adjacent frontier points instead. +**Read that dual as the local exchange rate.** Where the frontier is smooth, the dual on a swept ε-constraint is its slope — how much the kept objective `f1` moves per unit of the bound — at no cost beyond the solve already run; at a kink it gives only a one-sided rate. A **zero** dual means the bound is slack: the sweep has run past the frontier's edge. This reading needs LP/QP and a *linear* ε-constraint (MILP optima and problems with quadratic constraints return no duals) — where duals are unavailable, difference adjacent frontier points instead. **Picking a method:** weighted-sum for a quick convex sketch or when you know the frontier is convex (e.g. a pure-LP/QP tradeoff); ε-constraint when the problem is MILP, when the frontier may be non-convex, or when the user needs a faithful and complete curve. @@ -119,7 +119,7 @@ Practical notes: - **Cap each MILP solve.** Set a per-solve time limit on MILP sweeps (see `cuopt-numerical-optimization-api-python`) — a sweep is many solves, and branch-and-bound can over-spend certifying optimality past a tiny gap, while cuOpt sets no limit by default and won't warn. Report the points as optimal *to the gap you set*, not certified optimal. - **Filter dominated points.** A correct sweep can still emit dominated points (especially weighted-sum near the hull, or MILP). Drop them; they are not part of the frontier. - **Resolution is a budget.** Curve fidelity trades against solve count. Start coarse to see the shape, then refine the grid only where the curve bends. -- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps by more than the solve tolerance, the frontier bends between those points — refine there (smaller differences are solver noise, not curvature). This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. MILP has no duals, so judge where to refine from the gaps between primal objective values instead. +- **Spend the budget where the slope changes (LP/QP).** Because the ε-constraint dual is the frontier's local slope, compare it across solved points: where it barely changes, the curve is nearly straight — interpolate rather than add solves; where it jumps by more than the solve tolerance, the frontier bends between those points — refine there (smaller differences are solver noise, not curvature). This concentrates solves where the curve actually bends instead of spreading them over a uniform grid. On MILP, judge where to refine from the gaps between primal objective values instead. - **Verify, don't assume.** When you claim one method beats another, measure it — e.g. count the efficient points ε-constraint recovered that weighted-sum missed — rather than asserting it; and flag any solve returning feasible-but-not-`Optimal` so a non-certified point is never read as exact. ## Step 5 — interpret the frontier