Repository navigation
Report absent solution values instead of returning success #1734
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 6 commits
cec42a0
3b6b8d2
fd92801
f09cf96
b6a89dd
39a0697
b24a176
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 |
|---|---|---|
|
|
@@ -1264,10 +1264,15 @@ cuopt_int_t cuOptGetPrimalSolution(cuOptSolution solution, cuopt_float_t* soluti | |
| solution_and_stream_view_t* solution_and_stream_view = | ||
| static_cast<solution_and_stream_view_t*>(solution); | ||
|
|
||
| const auto solution_host = solution_and_stream_view->get_solution()->get_solution_host(); | ||
| std::memcpy( | ||
| solution_values_ptr, solution_host.data(), solution_host.size() * sizeof(cuopt_float_t)); | ||
| return CUOPT_SUCCESS; | ||
| try { | ||
| const auto solution_host = solution_and_stream_view->get_solution()->get_solution_host(); | ||
| if (solution_host.empty()) { return CUOPT_INVALID_ARGUMENT; } | ||
|
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline cpp/src/pdlp/cuopt_c.cpp \
--match 'cuOptCreateProblem|cuOptGetPrimalSolution|cuOptGetDualSolution|cuOptGetReducedCosts' \
--view expanded
rg -n -C 5 --glob '*.{cpp,hpp,h,cu,cuh}' \
'\bnum_variables\b.*(==|<=|<)\s*0|\bnum_variables\b.*(>|>=)\s*0|cuOptCreateProblem\(0' \
cppRepository: NVIDIA/cuopt Length of output: 4336 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- cuOptCreateProblem and nearby code ---'
sed -n '300,470p' cpp/src/pdlp/cuopt_c.cpp
printf '%s\n' '--- solution accessors and nearby code ---'
sed -n '1230,1405p' cpp/src/pdlp/cuopt_c.cpp
printf '%s\n' '--- relevant symbols and declarations ---'
rg -n -C 4 \
'\b(cuOptCreateProblem|cuOptGetPrimalSolution|cuOptGetDualSolution|cuOptGetReducedCosts|solution_host|primal_solution|reduced_cost)\b' \
cpp/src/pdlp cpp/src/tests python 2>/dev/null | head -n 500Repository: NVIDIA/cuopt Length of output: 50368 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- model setter declarations and definitions ---'
rg -n -C 5 \
'set_objective_coefficients|set_variable_lower_bounds|set_variable_upper_bounds|set_variable_types|get_n_variables' \
cpp/src cpp/include --glob '*.{cpp,cu,h,hpp,cuh,cuh}' 2>/dev/null | head -n 800
printf '%s\n' '--- zero-dimension validation and solver guards ---'
rg -n -C 5 \
'n_variables|num_variables|get_n_variables|variable_count|number_of_variables' \
cpp/src --glob '*.{cpp,cu,h,hpp,cuh}' 2>/dev/null \
| rg -C 3 '0|empty|invalid|expect|assert|check|size' \
| head -n 800Repository: NVIDIA/cuopt Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- explicit zero-variable handling ---'
rg -n -C 5 \
'n_variables\s*(==|<=|<)\s*0|num_variables\s*(==|<=|<)\s*0|n_cols\s*==\s*0|n_vars\s*==\s*0' \
cpp/src cpp/include --glob '*.{cpp,cu,h,hpp,cuh}' 2>/dev/null | head -n 1000
printf '%s\n' '--- zero-variable tests and model construction ---'
rg -n -C 6 \
'zero[-_ ]variable|num_variables\s*=\s*0|n_variables\s*=\s*0|CreateProblem\s*\(' \
cpp/src/tests cpp/tests python --glob '*.{cpp,cu,h,hpp,py}' 2>/dev/null | head -n 1000
printf '%s\n' '--- solution vector initialization and empty-result paths ---'
rg -n -C 5 \
'optimization_problem_solution_t|primal_solution_|dual_solution_|reduced_cost_|get_solution_host|get_dual_solution|get_reduced_costs' \
cpp/src/pdlp --glob '*.{cpp,cu,h,hpp,cuh}' 2>/dev/null | head -n 1000Repository: NVIDIA/cuopt Length of output: 18163 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- empty-problem solve paths ---'
rg -n -C 12 '\bproblem\.empty\b|\bempty\b.*solution|solution.*empty|n_variables == 0' \
cpp/src/pdlp cpp/src/mip_heuristics --glob '*.{cpp,cu,h,hpp,cuh}' 2>/dev/null | head -n 1200
printf '%s\n' '--- solution implementation and getters ---'
rg -n -C 8 \
'optimization_problem_solution_t|solution_host_|get_solution_host|get_dual_solution|get_reduced_costs|primal_solution_' \
cpp/include cpp/src --glob '*.{cpp,cu,h,hpp,cuh}' 2>/dev/null | head -n 1600Repository: NVIDIA/cuopt Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
c_api = Path("cpp/src/pdlp/cuopt_c.cpp").read_text()
problem = Path("cpp/src/mip_heuristics/problem/problem.cu").read_text()
solve = Path("cpp/src/mip_heuristics/solve.cu").read_text()
trivial = Path("cpp/src/mip_heuristics/presolve/trivial_presolve.cuh").read_text()
data_model = Path("cpp/src/io/data_model_view.cpp").read_text()
solution = Path("cpp/include/cuopt/mathematical_optimization/cpu_optimization_problem_solution.hpp").read_text()
checks = {
"C API creation has no num_variables nonpositive guard": "if (num_variables <= 0)" not in c_api[c_api.index("cuopt_int_t cuOptCreateProblem"):c_api.index("cuopt_int_t cuOptCreateRangedProblem")],
"model representation accepts zero variables": "if (n_variables == 0)" in problem and "objective_coefficients.is_empty()" in problem,
"zero-variable problem is marked empty": "if (problem.n_variables == 0) { problem.empty = true; }" in trivial,
"empty solve constructs a solution": "if (problem.empty)" in solve and "mip::solution_t<i_t, f_t> solution(problem);" in solve,
"host getters expose stored vectors": "get_primal_solution_host() const override { return primal_solution_; }" in solution,
"primal accessor rejects empty": "if (solution_host.empty()) { return CUOPT_INVALID_ARGUMENT; }" in c_api,
"dual accessor rejects empty": "if (dual_host.empty()) { return CUOPT_INVALID_ARGUMENT; }" in c_api,
"reduced-cost accessor rejects empty": "if (reduced_cost_host.empty()) { return CUOPT_INVALID_ARGUMENT; }" in c_api,
}
for name, ok in checks.items():
print(f"{'PASS' if ok else 'FAIL'}: {name}")
if not all(
("PASS" if ok else "FAIL") == "PASS" for ok in checks.values()
):
raise SystemExit(1)
PY
printf '%s\n' '--- exact solution sizing/getters ---'
rg -n -C 6 \
'solution_t\(.*problem|assignment\(|get_primal_solution_host|get_dual_solution_host|get_reduced_cost_host|get_reduced_costs' \
cpp/include/cuopt cpp/src/mip_heuristics cpp/src/pdlp --glob '*.{cpp,cu,h,hpp,cuh}' 2>/dev/null | head -n 500Repository: NVIDIA/cuopt Length of output: 49530 Use solution availability instead of The model and solver support zero-variable and zero-constraint problems. Valid results can therefore have empty primal, dual, or reduced-cost vectors. The checks at lines 1272, 1338, and 1371 return 🤖 Prompt for AI Agents |
||
| std::memcpy( | ||
| solution_values_ptr, solution_host.data(), solution_host.size() * sizeof(cuopt_float_t)); | ||
| return CUOPT_SUCCESS; | ||
| } catch (const std::logic_error&) { | ||
| return CUOPT_INVALID_ARGUMENT; | ||
| } | ||
| } | ||
|
|
||
| cuopt_int_t cuOptGetObjectiveValue(cuOptSolution solution, cuopt_float_t* objective_value_ptr) | ||
|
|
@@ -1326,6 +1331,10 @@ cuopt_int_t cuOptGetDualSolution(cuOptSolution solution, cuopt_float_t* dual_sol | |
| static_cast<solution_and_stream_view_t*>(solution); | ||
| try { | ||
| const auto dual_host = solution_and_stream_view->get_solution()->get_dual_solution(); | ||
| // Empty here is valid for a problem with no constraints, so the solve is what is tested. | ||
| if (solution_and_stream_view->get_solution()->get_solution_host().empty()) { | ||
|
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. Would it be a big deal to check
Collaborator
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. No big deal, and it is back to Recording why it was not, since it will look like a step backwards otherwise. CodeRabbit flagged that an empty vector does not always mean the values are missing, and it was right about the mechanism: a box-constrained LP with no constraints solves to Your question is the one I should have asked first: what actually goes wrong for that caller? Nothing much. A problem with no constraints has no dual vector, so reporting its absence is a reasonable answer, and it costs a local, obvious check rather than one accessor reaching into another vector. Your review overrides the bot here. So that the behaviour is pinned rather than quietly reintroduced later, the zero-constraint test now asserts it: primal and reduced costs return their values, and the dual reports the absence and leaves the buffer untouched. A future change cannot flip it without failing the test. The defect this PR is actually about is unaffected — an infeasible solve still reports absence from all three rather than returning success having written nothing. 71/71 pass. |
||
| return CUOPT_INVALID_ARGUMENT; | ||
| } | ||
| std::memcpy(dual_solution_ptr, dual_host.data(), dual_host.size() * sizeof(cuopt_float_t)); | ||
| return CUOPT_SUCCESS; | ||
| } catch (const std::logic_error&) { | ||
|
|
@@ -1357,6 +1366,10 @@ cuopt_int_t cuOptGetReducedCosts(cuOptSolution solution, cuopt_float_t* reduced_ | |
| static_cast<solution_and_stream_view_t*>(solution); | ||
| try { | ||
| const auto reduced_cost_host = solution_and_stream_view->get_solution()->get_reduced_costs(); | ||
| // As above: the solve is what is tested, not the length of this vector. | ||
|
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. The reduced cost vector should be populated even when there are no constraints, so we should just check that.
Collaborator
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. Done in For the record on why the indirection was there: it came from a CodeRabbit finding that an empty vector does not always mean the values are missing, since a problem with no constraints has a legitimately empty dual solution. Reduced costs were caught up in the same change for symmetry, which as you point out was unnecessary. |
||
| if (solution_and_stream_view->get_solution()->get_solution_host().empty()) { | ||
| return CUOPT_INVALID_ARGUMENT; | ||
| } | ||
| std::memcpy( | ||
| reduced_cost_ptr, reduced_cost_host.data(), reduced_cost_host.size() * sizeof(cuopt_float_t)); | ||
| return CUOPT_SUCCESS; | ||
|
|
||
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.
Shall we define a new status constant, like
CUOPT_INVALID_OUTPUT, rather than usingCUOPT_INVALID_ARGUMENT?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.
It is the solution that is invalid here. So INVALID_ARGUMENT is a fine return.