Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions skills/cuopt-numerical-optimization-api-c/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor Author

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 😆

for **LP and QP with linear constraints** — the barrier solver is primal-dual, so a quadratic

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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" || true

Repository: 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" || true

Repository: 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" || true

Repository: 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' || true

Repository: 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 Attach NVSkills validation signatures (or other NVSkills indicators), so NVSkills validation/signature flow still needs to run—ask for /nvskills-ci from a maintainer/admin and re-check that the NVSkills bot signature commit is attached before merging.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/cuopt-numerical-optimization-api-c/SKILL.md` around lines 38 - 44,
Branch has DCO sign-off but lacks the NVSkills bot signature commit (no commit
titled "Attach NVSkills validation signatures"), so NVSkills CI gating won’t
allow merge; ask a maintainer/admin to trigger the NVSkills validation by
commenting `/nvskills-ci` and confirm that the resulting NVSkills bot signature
commit is appended to the branch before merging, re-checking that a commit from
the NVSkills bot is present in the branch history.

Source: Coding guidelines


## Debugging (MPS / C)

**MPS parsing:** Required sections in order: NAME, ROWS, COLUMNS, RHS, (optional) BOUNDS, ENDATA. Integer markers: `'MARKER'`, `'INTORG'`, `'INTEND'`.
Expand Down
5 changes: 5 additions & 0 deletions skills/cuopt-numerical-optimization-api-c/assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

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.

Suggested change
> **Duals:** `lp_duals` is an LP, but `cuOptGetDualSolution` / `cuOptGetReducedCosts` also return
**Duals:** `lp_duals` is an LP, but `cuOptGetDualSolution` / `cuOptGetReducedCosts` also return

same for other lines as well

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.

If we have similar workding in skills, do we need to update in README again?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the note entirely instead of reformatting — it duplicated the Dual values (LP / QP) section now in SKILL.md. Thanks for catching it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 SKILL.md section is the single source.

> 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.
Expand Down
12 changes: 9 additions & 3 deletions skills/cuopt-numerical-optimization-api-python/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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** —

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duals and reduced costs (no parentheticals)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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}")
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Down