Skip to content

Commit 675f902

Browse files
authored
Fix root LP solution copy in diversity_manager_t (#685)
This PR fixes a missing copy in diversity_manager_t after the root node solve, which caused uninitialized data to remain in the lp_optimal_solution vector and causing errors later down the line. ## Summary by CodeRabbit * **Refactor** * Streamlined solution handling to copy LP primal/dual results directly and avoid redundant temporary allocations; preserved synchronization behavior. * Adjusted feasibility validation to operate on a separate scaled copy to prevent unintended mutation. * **Bug Fixes** * Added size and finiteness checks to validate solutions before use. * **User-facing** * Deterministic fallback names for unnamed variables so every column has a label. * **Tests** * Relaxed numerical tolerances in some objective and variable checks. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> Authors: - Alice Boucher (https://github.com/aliceb-nv) Approvers: - Nicolas L. Guidotti (https://github.com/nguidotti) - Ramakrishnap (https://github.com/rgsl888prabhu) - Hugo Linsenmaier (https://github.com/hlinsen) URL: #685
1 parent 61beec8 commit 675f902

File tree

4 files changed

+27
-11
lines changed

4 files changed

+27
-11
lines changed

cpp/src/linear_programming/translate.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ static dual_simplex::user_problem_t<i_t, f_t> cuopt_problem_to_simplex_problem(
8282
if (model.var_names.size() > 0) {
8383
user_problem.col_names.resize(n);
8484
for (int j = 0; j < n; ++j) {
85-
user_problem.col_names[j] = model.var_names[j];
85+
if (j < (int)model.var_names.size()) {
86+
user_problem.col_names[j] = model.var_names[j];
87+
} else {
88+
user_problem.col_names[j] = "_CUOPT_x" + std::to_string(j);
89+
}
8690
}
8791
}
8892
user_problem.obj_constant = model.presolve_data.objective_offset;

cpp/src/mip/diversity/diversity_manager.cu

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,18 +357,24 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
357357
pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2;
358358
pdlp_settings.num_gpus = context.settings.num_gpus;
359359

360-
rmm::device_uvector<f_t> lp_optimal_solution_copy(lp_optimal_solution.size(),
361-
problem_ptr->handle_ptr->get_stream());
362360
timer_t lp_timer(lp_time_limit);
363361
auto lp_result = solve_lp_with_method<i_t, f_t>(*problem_ptr, pdlp_settings, lp_timer);
364362

365363
{
366364
std::lock_guard<std::mutex> guard(relaxed_solution_mutex);
367365
if (!simplex_solution_exists.load()) {
366+
cuopt_assert(lp_result.get_primal_solution().size() == lp_optimal_solution.size(),
367+
"LP optimal solution size mismatch");
368+
cuopt_assert(lp_result.get_dual_solution().size() == lp_dual_optimal_solution.size(),
369+
"LP dual optimal solution size mismatch");
368370
raft::copy(lp_optimal_solution.data(),
369-
lp_optimal_solution_copy.data(),
371+
lp_result.get_primal_solution().data(),
370372
lp_optimal_solution.size(),
371373
problem_ptr->handle_ptr->get_stream());
374+
raft::copy(lp_dual_optimal_solution.data(),
375+
lp_result.get_dual_solution().data(),
376+
lp_dual_optimal_solution.size(),
377+
problem_ptr->handle_ptr->get_stream());
372378
} else {
373379
// copy the lp state
374380
raft::copy(lp_state.prev_primal.data(),
@@ -382,6 +388,11 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
382388
}
383389
problem_ptr->handle_ptr->sync_stream();
384390
}
391+
cuopt_assert(thrust::all_of(problem_ptr->handle_ptr->get_thrust_policy(),
392+
lp_optimal_solution.begin(),
393+
lp_optimal_solution.end(),
394+
[] __host__ __device__(f_t val) { return std::isfinite(val); }),
395+
"LP optimal solution contains non-finite values");
385396
ls.lp_optimal_exists = true;
386397
if (lp_result.get_termination_status() == pdlp_termination_status_t::Optimal) {
387398
set_new_user_bound(lp_result.get_objective_value());

cpp/src/mip/diversity/population.cu

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,11 @@ void population_t<i_t, f_t>::run_solution_callbacks(solution_t<i_t, f_t>& sol)
283283
context.scaling.unscale_solutions(temp_sol.assignment, dummy);
284284
// Need to get unscaled problem as well
285285
problem_t<i_t, f_t> n_problem(*sol.problem_ptr->original_problem_ptr);
286-
temp_sol.problem_ptr = &n_problem;
287-
temp_sol.resize_to_original_problem();
288-
temp_sol.compute_feasibility();
289-
if (!temp_sol.get_feasible()) {
286+
auto scaled_sol(temp_sol);
287+
scaled_sol.problem_ptr = &n_problem;
288+
scaled_sol.resize_to_original_problem();
289+
scaled_sol.compute_feasibility();
290+
if (!scaled_sol.get_feasible()) {
290291
CUOPT_LOG_DEBUG("Discard infeasible after unscaling");
291292
return;
292293
}

python/cuopt/cuopt/tests/linear_programming/test_python_API.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ def test_quadratic_objective_2():
809809
problem.solve()
810810

811811
assert problem.Status.name == "Optimal"
812-
assert x1.getValue() == pytest.approx(0.2295081)
812+
assert x1.getValue() == pytest.approx(0.2295081, abs=1e-3)
813813
assert x2.getValue() == pytest.approx(0.0000000, abs=0.000001)
814-
assert x3.getValue() == pytest.approx(0.1092896)
815-
assert problem.ObjValue == pytest.approx(-0.284153)
814+
assert x3.getValue() == pytest.approx(0.1092896, abs=1e-3)
815+
assert problem.ObjValue == pytest.approx(-0.284153, abs=1e-3)

0 commit comments

Comments
 (0)