Describe the bug
Several C API copy-out accessors write into a caller-sized buffer and report no length. When the underlying data is empty, they copy nothing, return CUOPT_SUCCESS, and leave the caller's buffer at whatever it was initialised to. "Not available" is therefore indistinguishable from "all zeros".
cuOptGetDualSolution and cuOptGetReducedCosts are the clearest cases:
const auto reduced_cost_host = solution_and_stream_view->get_solution()->get_reduced_costs();
std::memcpy(reduced_cost_ptr, reduced_cost_host.data(),
reduced_cost_host.size() * sizeof(cuopt_float_t));
return CUOPT_SUCCESS;
get_reduced_cost_host() returns an empty vector when the solve produced no reduced costs — an infeasible LP, for instance. The memcpy then copies zero bytes and the function still reports success.
Steps/Code to reproduce bug
Solve an infeasible LP, then:
cuopt_float_t reduced_costs[n];
for (int i = 0; i < n; ++i) reduced_costs[i] = 0.0;
cuOptGetReducedCosts(solution, reduced_costs); /* CUOPT_SUCCESS, buffer untouched */
The caller cannot tell this from a solve whose reduced costs are genuinely all zero.
This was hit in practice in #1524. Moving the Java bindings onto cuOptGetReducedCosts turned "no reduced costs" into "all reduced costs are zero", which surfaced as a test failure (ProblemIntegrationTest.problemsBuildAndSolve[10]); the change had to be reverted and the bindings left on the internal C++ interface, which does report the real length.
Expected behavior
A caller should be able to tell "unavailable" from "available and zero" — either a length out-parameter, a separate size query, or a distinct status code when the data is absent.
Additional context
cuOptGetProblemStringArrayAttribute has a related shape problem in the other direction: it requires count to equal the stored name count exactly and returns CUOPT_INVALID_ARGUMENT otherwise, so a problem with no names set cannot be distinguished from a bad argument, and there is no size query to ask first.
Raised by @mlubin in #1703, where the point was correctly made that this is a C API behaviour issue to fix rather than a reason to give Java a special interface. Splitting it out of that issue so it can be fixed on its own terms.
Describe the bug
Several C API copy-out accessors write into a caller-sized buffer and report no length. When the underlying data is empty, they copy nothing, return
CUOPT_SUCCESS, and leave the caller's buffer at whatever it was initialised to. "Not available" is therefore indistinguishable from "all zeros".cuOptGetDualSolutionandcuOptGetReducedCostsare the clearest cases:get_reduced_cost_host()returns an empty vector when the solve produced no reduced costs — an infeasible LP, for instance. Thememcpythen copies zero bytes and the function still reports success.Steps/Code to reproduce bug
Solve an infeasible LP, then:
The caller cannot tell this from a solve whose reduced costs are genuinely all zero.
This was hit in practice in #1524. Moving the Java bindings onto
cuOptGetReducedCoststurned "no reduced costs" into "all reduced costs are zero", which surfaced as a test failure (ProblemIntegrationTest.problemsBuildAndSolve[10]); the change had to be reverted and the bindings left on the internal C++ interface, which does report the real length.Expected behavior
A caller should be able to tell "unavailable" from "available and zero" — either a length out-parameter, a separate size query, or a distinct status code when the data is absent.
Additional context
cuOptGetProblemStringArrayAttributehas a related shape problem in the other direction: it requirescountto equal the stored name count exactly and returnsCUOPT_INVALID_ARGUMENTotherwise, so a problem with no names set cannot be distinguished from a bad argument, and there is no size query to ask first.Raised by @mlubin in #1703, where the point was correctly made that this is a C API behaviour issue to fix rather than a reason to give Java a special interface. Splitting it out of that issue so it can be fixed on its own terms.