Skip to content

Multi-objective Pareto-frontier examples (workforce + portfolio)#151

Open
cafzal wants to merge 15 commits into
NVIDIA:mainfrom
cafzal:multi-objective-examples
Open

Multi-objective Pareto-frontier examples (workforce + portfolio)#151
cafzal wants to merge 15 commits into
NVIDIA:mainfrom
cafzal:multi-objective-examples

Conversation

@cafzal

@cafzal cafzal commented Jun 1, 2026

Copy link
Copy Markdown

Multi-objective (Pareto frontier) examples — companion to the cuopt-multi-objective-exploration skill

Two examples that extend existing folders to demonstrate multi-objective Pareto-frontier exploration with cuOpt — the workflow added as the cuopt-multi-objective-exploration skill in NVIDIA/cuopt#1355 (discussion NVIDIA/cuopt#1351).

  • workforce_optimization/workforce_optimization_multiobjective.ipynb (MILP) — turns the cost-minimizing workforce model into a tradeoff surface: cost vs. coverage (relax coverage == required and sweep a coverage floor) and cost vs. fairness (promote the base model's fixed max_shifts cap to a swept ε-constraint — a fixed constraint treated as a candidate objective). Reads the frontier as an exchange rate (marginal $ per shift), caps every MILP solve with a time_limit, and shows that an integer program has no constraint duals. ε-constraint is the default; weighted-sum is a one-line method note, not a demo.

workforce: cost vs. coverage frontier (left) and cost vs. fairness (right)

  • portfolio_optimization/QP_portfolio_frontier_duals.ipynb (QP) — recognizes the base QP notebook's hand-coded target-return loop as the ε-constraint method, rebuilds it as the named workflow, and adds the piece the manual sweep omits: the return-constraint dual (shadow price d(variance)/d(return)) along the efficient frontier, with the PDLP-tolerance caveat.
portfolio_frontier_and_shadow_price

Both reuse the base notebooks' data, run on cuOpt alone (Colab GPU), and follow the repo's notebook idiom (GPU check → cuopt-cu12 install → solve). Notebooks ship output-stripped (repo convention — every existing cuopt-examples notebook has 0 cell outputs); the run evidence is below.

User testing

Both notebooks were built by following the skill, then run end-to-end on Colab T4, cuOpt 26.4.0 — clean, no API errors:

  • Workforce — cost vs. coverage (MILP). Cheapest full-coverage plan = $468 (all 51 shifts staffed). The ε-constraint sweep traces a 52-point frontier (coverage 0–51); the marginal cost read off the curve rises ~$8 → $12 per added shift; all 52 solved to Optimal (0 FeasibleFound). A single solve was only ever the right-most point.
  • Workforce — cost vs. fairness (MILP). Sweeping the max_shifts cap (the constraint-as-objective move): full coverage holds at $468 down to a cap of 11, then $470 / $473 / $484 at caps 10 / 9 / 8, and goes infeasible at ≤ 7 — a clean price-of-fairness curve plus a feasibility cliff.
  • Portfolio (QP). The efficient frontier plus the return-constraint dual, which rises ~0 → 13.2 as the required return climbs (the marginal risk cost of return). All points solved to Optimal (0 PrimalFeasible), so the PDLP-tolerance caveat is mild here.

Notes

  • Synthetic/toy data — same as the base notebooks; this demonstrates the method, not a staffing or investment study.
  • MILP points all solved to Optimal (0 FeasibleFound) — optimal to cuOpt's gap tolerance (exact here, since labor cost is integer-valued); the per-solve time_limit is a guard that didn't bind.
  • QP duals are optimal to PDLP tolerance (first-order solver); any PrimalFeasible point is flagged (none here).

Companion to the now-merged cuopt-multi-objective-exploration skill (NVIDIA/cuopt#1355).

…io QP duals)

Signed-off-by: cafzal <cameron.afzal@gmail.com>
cafzal added 5 commits June 1, 2026 11:00
…-sum gotcha demo)

Signed-off-by: cafzal <cameron.afzal@gmail.com>
…older READMEs

Signed-off-by: cafzal <cameron.afzal@gmail.com>
… sweeps

Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
…onvention)

Signed-off-by: cafzal <cameron.afzal@gmail.com>
@rgsl888prabhu rgsl888prabhu marked this pull request as ready for review June 4, 2026 14:57
rapids-bot Bot pushed a commit to NVIDIA/cuopt that referenced this pull request Jun 4, 2026
Adds `cuopt-multi-objective-exploration` — a concept skill for problems with **two or more objectives and no fixed weighting**, where the user wants to see the tradeoff instead of accepting one weighted answer. It turns repeated single-objective cuOpt solves into a Pareto frontier (payoff table → ε-constraint / weighted-sum sweep → filter dominated) and supplies the discipline to read it: exchange rates, knee points, deferring the final choice. It adds no solver features and invents no API; it sits above the api-* and formulation skills, orchestrating the solves they already cover.

### cuOpt-specific correctness points
- ε-constraining is most natural on **linear** objectives, so a quadratic objective (risk `xᵀΣx`) can simply stay the objective. A convex one can also be ε-constrained directly: cuOpt routes `xᵀQx ≤ ε` through the barrier solver as a second-order cone.
- PDLP warm-start is LP-only; MILP frontier points are optimal **to the gap you set**, since each solve gets a time limit.
- A hard constraint (coverage floor, budget, fairness cap) is often a latent objective — promote it to a swept ε-constraint (when its level was an assumption, not a firm limit).

### User testing
Used the skill to enrich two existing examples and confirmed it drove the right calls — companion PR NVIDIA/cuopt-examples#151:
- **Workforce (MILP)** — promoted two hard constraints into tradeoffs (coverage → cost vs. coverage; the `max_shifts` cap → cost vs. fairness), chose ε-constraint over weighted-sum, anchored objective ranges, filtered dominated points, capped each solve, and reported no MILP duals.
- **Portfolio (QP)** — recognized the base notebook's hand-coded target-return loop as ε-constraint and added what it omits: the return-constraint dual (shadow price d(variance)/d(return)), with the PDLP-tolerance caveat.

The skill drives the *method*; runnable code still needs `cuopt-numerical-optimization-api-python` plus the worked notebooks, which are Colab-GPU validated (T4, clean end-to-end).

### Validation & gating
Registered in `AGENTS.md` + `marketplace.json`; `ci/utils/validate_skills.sh` passes. The contribution is `SKILL.md` + `evals/evals.json` — `BENCHMARK.md`, the skill card, and `skill.oms.sig` are generated by the NVSkills onboarding pipeline. The official NVSkills-Eval is the gate (PENDING).

Authors:
  - Cameron Afzal (https://github.com/cafzal)

Approvers:
  - Miles Lubin (https://github.com/mlubin)
  - Ramakrishnap (https://github.com/rgsl888prabhu)

URL: #1355
Signed-off-by: cafzal <cameron.afzal@gmail.com>

@rgsl888prabhu rgsl888prabhu left a comment

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.

Few minor suggestions, but test looks good. Awesome work @cafzal.

Comment thread workforce_optimization/workforce_optimization_multiobjective.ipynb Outdated
Comment thread portfolio_optimization/QP_portfolio_frontier_duals.ipynb Outdated
Comment thread portfolio_optimization/QP_portfolio_frontier_duals.ipynb Outdated
Comment thread portfolio_optimization/QP_portfolio_frontier_duals.ipynb Outdated
Comment thread portfolio_optimization/QP_portfolio_frontier_duals.ipynb Outdated
"cell_type": "markdown",
"id": "f330297d",
"metadata": {},
"source": "# Portfolio Optimization \u2014 the Frontier via the Skill, + Shadow Prices (cuOpt QP)\n\nThe base `QP_portfolio_optimization` notebook **hand-codes** an efficient-frontier sweep (a manual loop over target returns). This sibling shows that following the `cuopt-multi-objective-exploration` skill **recreates that frontier as a named, systematic workflow** \u2014 anchor each objective \u2192 \u03b5-constraint sweep (the return floor is the parametric bound) \u2192 filter dominated \u2192 read the frontier \u2014 with less ad-hoc scaffolding, and **adds the one thing the manual sweep omits**: the return-constraint **dual** (shadow price d(variance)/d(return)).\n\nSo the two examples are complementary tests of the skill: here it **reproduces** an existing frontier (return vs risk) with less manual work and surfaces the duals; the workforce MILP (`workforce_optimization/workforce_optimization_multiobjective.ipynb`) is the **net-new** case \u2014 and the deliberate contrast is that **a QP has constraint duals, an integer program does not.**"

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.

I may be missing something but the notebook doesn't really show how the skill was used, it just shows the output. What is the take home for readers of the notebook?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked both intros around the method as explicit steps (recognize → constrain one → sweep → read the dual) and added a "Takeaway" section; cut the "follows the skill" framing. The key point is now explicit: a hand-coded target-return loop already is an ε-constraint sweep.

Comment thread portfolio_optimization/QP_portfolio_frontier_duals.ipynb Outdated
Comment thread workforce_optimization/workforce_optimization_multiobjective.ipynb Outdated
"cell_type": "markdown",
"id": "13784d94",
"metadata": {},
"source": "# Workforce Optimization \u2014 Multi-Objective with cuOpt\n\nThe base `workforce_optimization_milp` notebook minimizes labor cost with coverage **hard-constrained** \u2014 it returns **one plan**. But that plan answers only *\"cheapest way to fully staff.\"* A planner usually faces a **tradeoff with no fixed weighting**: *how much coverage is worth how much cost?* and *how much does fairness cost?* A single solve hides that; you get one point on a curve you can't see.\n\nThis notebook follows the `cuopt-multi-objective-exploration` skill to turn the single solve into the **whole tradeoff curve**, so the planner can see the options and choose. Two tradeoffs, both built by promoting one of the base model's hard constraints into an objective:\n\n1. **cost vs. coverage** \u2014 relax `coverage == required` and sweep a coverage floor.\n2. **cost vs. fairness** \u2014 sweep the base model's fixed `max_shifts` cap."

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.

What does it mean for the notebook to follow the skill? The notebook doesn't show how to use the skill; it just shows the output.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same resolution. Please let me know if the reframe better illustrates the workflow.

…t, drop cross-references, fix QP-dual wording

Signed-off-by: cafzal <cameron.afzal@gmail.com>
cafzal added a commit to cafzal/cuopt-examples that referenced this pull request Jun 5, 2026
cafzal added 5 commits June 5, 2026 12:17
Signed-off-by: cafzal <cameron.afzal@gmail.com>
…k-through

Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
Signed-off-by: cafzal <cameron.afzal@gmail.com>
…h base style

Signed-off-by: cafzal <cameron.afzal@gmail.com>
@cafzal cafzal requested a review from mlubin June 5, 2026 20:40
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