Repository navigation
Correct dual-value scope to LP/QP in numerical-optimization API skills #1408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to say anything about the barrier solver. The solve method isn't part of the API contract for duals.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved. |
||
| 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Commits in PR branch missing DCO sign-off =="
git log --format='%H%n%B%n==END==' origin/main..HEAD \
| awk '
BEGIN { RS="==END==\n"; ORS="\n\n" }
{
if ($0 !~ /Signed-off-by:[[:space:]]+/) {
split($0, lines, "\n")
print lines[1]
}
}
' | sed '/^$/d' || true
echo
echo "== Check for NVSkills signature commit in PR branch =="
git log --oneline origin/main..HEAD | rg -n "Attach NVSkills validation signatures" || true
echo
echo "Expected:"
echo "- No commit hashes printed in DCO section."
echo "- At least one match for 'Attach NVSkills validation signatures' if NVSkills CI has run."Repository: NVIDIA/cuopt Length of output: 303 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== DCO check (commit bodies) =="
git log --format='%H%n%B%n==END==' origin/main..HEAD \
| awk '
BEGIN { RS="==END==\n"; ORS="\n\n" }
{
if ($0 !~ /Signed-off-by:[[:space:]]+/) {
split($0, lines, "\n")
print lines[1]
}
}
' | sed '/^$/d' || true
echo
echo "== NVSkills signature phrase search (commit bodies) =="
git log --format='%H %s%n%B%n---END---\n' origin/main..HEAD \
| rg -n "Attach NVSkills validation signatures" || true
echo
echo "== NVSkills bot presence (commit subjects/bodies) =="
git log --format='%H %s%n%B%n---END---\n' origin/main..HEAD \
| rg -n "nv-nvskill-ci\\[bot\\]|NVSkills CI|NVSkills validation|validation signatures" || trueRepository: NVIDIA/cuopt Length of output: 198 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Git remotes/branch state =="
git remote -v || true
git branch -a || true
git rev-parse --verify origin/main >/dev/null 2>&1 && echo "origin/main exists" || echo "origin/main missing"
echo
echo "== Commit range size (origin/main..HEAD) =="
git log --oneline origin/main..HEAD --max-count=20 || true
echo
git log --oneline origin/main..HEAD | wc -l
echo
echo "== Any Signed-off-by present in range? (case-insens) =="
git log --format='%H%n%B%n==END==' origin/main..HEAD \
| rg -ni "signed-off-by:" || true
echo
echo "== Any nvskills indicators present in commit messages/bodies? (case-insens) =="
git log --format='%H%n%s%n%B%n==END==' origin/main..HEAD \
| rg -ni "nvskills|nv-nvskill-ci|/nvskills-ci|validation signatures|attach nvskills" || true
echo
echo "== Look for NVSkills attach phrase variants =="
git log --format='%H%n%s%n%B%n==END==' origin/main..HEAD \
| rg -ni "attach.*nvskills|nvskills.*attach|validation.*signature|signature.*validation" || trueRepository: NVIDIA/cuopt Length of output: 680 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Commits in origin/main..HEAD (subject) =="
git log --format='%H %s' origin/main..HEAD
echo
echo "== Full commit body(s) (to confirm DCO) =="
git log --format='---\ncommit %H\nsubject: %s\nbody:\n%B' origin/main..HEAD
echo
echo "== Check for NVSkills signature phrase across the same range (body+subject) =="
git log --format='%H%n%s%n%B%n==END==' origin/main..HEAD \
| rg -ni "attach nvskills|nvskills|nv-nvskill-ci\\[bot\\]|validation signatures" || trueRepository: NVIDIA/cuopt Length of output: 1639 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Authors/committers in origin/main..HEAD =="
git log --format='commit %H%n author: %an <%ae>%n committer: %cn <%ce>%n subject: %s%n%n' origin/main..HEAD
echo "== Look for any nvskills/nv-nvskill-ci references by committer/author =="
git log --format='%H%n%an%n%ae%n%cn%n%ce%n%s%n%B==END==' origin/main..HEAD \
| rg -ni "nvskills|nv-nvskill-ci\\[bot\\]|nv-nvskill-ci|/nvskills-ci|validation signatures|attach nvskills" || true
echo "== Show any commits touching skills/ in this range =="
git log --name-only --format='---\ncommit %H %s' origin/main..HEAD -- skills/ | sed -n '1,200p' || trueRepository: NVIDIA/cuopt Length of output: 8682 Fix NVSkills CI gate for skills/ before merge (DCO present, NVSkills signature missing). DCO sign-off is present in the updated skills commit. No commit in the branch contains 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| ## Debugging (MPS / C) | ||
|
|
||
| **MPS parsing:** Required sections in order: NAME, ROWS, COLUMNS, RHS, (optional) BOUNDS, ENDATA. Integer markers: `'MARKER'`, `'INTORG'`, `'INTEND'`. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
same for other lines as well
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have similar workding in skills, do we need to update in README again?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the note entirely instead of reformatting — it duplicated the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call — no need to state it in both. Removed it from the README; the |
||||||
| > 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. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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** — | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duals and reduced costs (no parentheticals)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved. |
||
| 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}") | ||
| ``` | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"]: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's safe or correct to check the dual if the status is PrimalFeasible
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved. |
||
| 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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to continue my push against "shadow prices". I don't think the parentheticals are necessary here. cuOptGetDualSolution returns duals and cuOptGetReducedCosts returns reduces costs. Readers can look at the function documentation for more information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Removed. I commit to no more 'shadow prices' too 😆