diff --git a/skills/cuopt-numerical-optimization-api-c/SKILL.md b/skills/cuopt-numerical-optimization-api-c/SKILL.md index b25acc14ba..808c110ce8 100644 --- a/skills/cuopt-numerical-optimization-api-c/SKILL.md +++ b/skills/cuopt-numerical-optimization-api-c/SKILL.md @@ -35,6 +35,14 @@ QP uses the same library, include/lib paths, and build pattern as LP/MILP — on - **Continuous variables only** — set `CUOPT_CONTINUOUS` for every variable; integer QP is not supported. - **Q should be PSD** for a convex problem. +## Dual values (LP / QP) + +`cuOptGetDualSolution` (shadow prices) and `cuOptGetReducedCosts` (reduced costs) return values +for **LP and QP with linear constraints** — the barrier solver is primal-dual, so a quadratic +objective still yields duals. cuOpt returns **no duals for a problem with quadratic constraints** +(the returned arrays are filled with `NaN`). See [assets/lp_duals](assets/lp_duals/) for the call +sequence — it is an LP, but the same calls apply to a QP whose constraints are all linear. + ## Debugging (MPS / C) **MPS parsing:** Required sections in order: NAME, ROWS, COLUMNS, RHS, (optional) BOUNDS, ENDATA. Integer markers: `'MARKER'`, `'INTORG'`, `'INTEND'`. diff --git a/skills/cuopt-numerical-optimization-api-c/assets/README.md b/skills/cuopt-numerical-optimization-api-c/assets/README.md index e354988da1..d9bb715d00 100644 --- a/skills/cuopt-numerical-optimization-api-c/assets/README.md +++ b/skills/cuopt-numerical-optimization-api-c/assets/README.md @@ -11,6 +11,11 @@ LP/MILP C API reference implementations. Use as reference when building new appl | [milp_production_planning](milp_production_planning/) | MILP | Production planning with resource constraints | | [mps_solver](mps_solver/) | LP/MILP | Solve from MPS file via `cuOptReadProblem` | +> **Duals:** `lp_duals` is an LP, but `cuOptGetDualSolution` / `cuOptGetReducedCosts` also return +> shadow prices and reduced costs for a **QP with linear constraints** (the barrier solver is +> primal-dual). No duals are returned for problems with **quadratic constraints** — the arrays +> come back filled with `NaN`. + ## Build and run Set include and library paths, then build and run. diff --git a/skills/cuopt-numerical-optimization-api-python/SKILL.md b/skills/cuopt-numerical-optimization-api-python/SKILL.md index a7460c7c7c..07d7e7f632 100644 --- a/skills/cuopt-numerical-optimization-api-python/SKILL.md +++ b/skills/cuopt-numerical-optimization-api-python/SKILL.md @@ -253,12 +253,18 @@ settings.set_parameter("log_to_console", 1) | QP rejected with MAXIMIZE | QP only supports MINIMIZE | Negate the objective: minimize `-f(x)` | | QP returns non-optimal | Q not PSD or variables badly scaled | Check Q is PSD; rescale variables to similar magnitudes | -## Getting Dual Values (LP only) +## Getting Dual Values (LP / QP) + +Shadow prices (`DualValue`) and reduced costs (`ReducedCost`) are returned for **LP and QP** — +cuOpt's barrier solver is primal-dual, so a QP with a quadratic objective and **linear** +constraints returns duals just like an LP. The constraints you read duals from must be linear: +cuOpt returns **no dual variables for a problem that has any quadratic constraint** (every +`DualValue`/`ReducedCost` comes back as `NaN`). MILP returns no duals. ```python if problem.Status.name == "Optimal": - constraint = problem.getConstraint("resource_a") - shadow_price = constraint.DualValue + constraint = problem.getConstraint("resource_a") # linear constraint + shadow_price = constraint.DualValue # NaN if the model has quadratic constraints print(f"Shadow price: {shadow_price}") ``` diff --git a/skills/cuopt-numerical-optimization-api-python/references/qp_examples.md b/skills/cuopt-numerical-optimization-api-python/references/qp_examples.md index 80b9802dbb..10ceba8ac9 100644 --- a/skills/cuopt-numerical-optimization-api-python/references/qp_examples.md +++ b/skills/cuopt-numerical-optimization-api-python/references/qp_examples.md @@ -123,6 +123,36 @@ if problem.Status.name == "Optimal": print(f"Objective = {problem.ObjValue:.4f}") ``` +## Reading Duals from a QP + +A QP with **linear** constraints returns shadow prices and reduced costs just like an LP +(cuOpt's barrier solver is primal-dual). A quadratic *objective* is fine; a quadratic +*constraint* is not — cuOpt returns **no duals for a problem with quadratic constraints** +(every `DualValue`/`ReducedCost` is `NaN`), so read duals only when all constraints are linear. + +```python +""" +minimize x² + y² + z² +subject to x + y + z == 10 (linear) +The equality's shadow price is the marginal change in the optimal objective +per unit increase of the right-hand side. +""" +from cuopt.linear_programming.problem import Problem, MINIMIZE + +problem = Problem("QPDuals") +x = problem.addVariable(lb=0, name="x") +y = problem.addVariable(lb=0, name="y") +z = problem.addVariable(lb=0, name="z") +problem.setObjective(x*x + y*y + z*z, sense=MINIMIZE) +problem.addConstraint(x + y + z == 10, name="budget") +problem.solve() + +if problem.Status.name in ["Optimal", "PrimalFeasible"]: + for c in problem.getConstraints(): + # 'budget' dual magnitude ≈ 6.667 (Δobjective per unit of the RHS) + print(f"{c.ConstraintName} DualValue = {c.DualValue}") +``` + ## Maximization Workaround ```python