From 9dfbe3a06bc49de80aceaaf6c9ebab2de755efe2 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Thu, 16 Jul 2026 15:08:41 +0200 Subject: [PATCH 1/2] fix seeding the initial population before properly handling the free variables. added a guard to check if the variables are fixed to infinite or NaN bounds. Signed-off-by: Nicolas L. Guidotti --- .../mip_heuristics/diversity/diversity_manager.cu | 14 ++++++++++---- cpp/src/mip_heuristics/problem/problem.cu | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/cpp/src/mip_heuristics/diversity/diversity_manager.cu b/cpp/src/mip_heuristics/diversity/diversity_manager.cu index 23fb60853b..a206e87e08 100644 --- a/cpp/src/mip_heuristics/diversity/diversity_manager.cu +++ b/cpp/src/mip_heuristics/diversity/diversity_manager.cu @@ -240,6 +240,16 @@ void diversity_manager_t::add_user_given_solutions( } if (problem_ptr->pre_process_assignment(init_sol_assignment)) { + // Seed the solution with the provided assignment BEFORE fixing integers and running the + // completion LP. Otherwise the fix uses the solution_t constructor's default assignment + // (the variable lower bounds), which is -inf for free variables; fixing such a variable + // makes fix_given_variables compute a NaN constraint bound (inf - inf) and trips the + // problem-representation validation. Seeding first also lets us keep the LP's repaired + // continuous values (previously overwritten by a post-LP copy). + raft::copy(sol.assignment.data(), + init_sol_assignment.data(), + init_sol_assignment.size(), + sol.handle_ptr->get_stream()); relaxed_lp_settings_t lp_settings; lp_settings.time_limit = std::min(60., timer.remaining_time() / 2); lp_settings.tolerance = problem_ptr->tolerances.absolute_tolerance; @@ -250,10 +260,6 @@ void diversity_manager_t::add_user_given_solutions( problem_ptr->integer_indices, lp_settings, static_cast*>(nullptr)); - raft::copy(sol.assignment.data(), - init_sol_assignment.data(), - init_sol_assignment.size(), - sol.handle_ptr->get_stream()); bool is_feasible = sol.compute_feasibility(); cuopt_func_call(sol.test_variable_bounds(true)); CUOPT_LOG_DEBUG("Adding initial solution success! feas %d objective %f excess %f", diff --git a/cpp/src/mip_heuristics/problem/problem.cu b/cpp/src/mip_heuristics/problem/problem.cu index e38e889495..e0c8ba084d 100644 --- a/cpp/src/mip_heuristics/problem/problem.cu +++ b/cpp/src/mip_heuristics/problem/problem.cu @@ -1706,6 +1706,18 @@ void problem_t::fix_given_variables(problem_t& original_prob variables_to_fix.end(), [variable_fix_mask = make_span(fixing_helpers.variable_fix_mask)] __device__( i_t x) { variable_fix_mask[x] = 1; }); + // Guard: fixing a variable to a non-finite value corrupts the constraint RHS below + // (coeff * floor(+-inf) = +-inf, then original_bound - inf can be inf - inf = NaN), which + // otherwise surfaces much later as an opaque "Constraints bounds are invalid" validation + // error. Fail loudly at the source instead. Callers must seed a finite assignment before + // fixing (e.g. free variables must not be left at their -inf lower-bound default). + cuopt_func_call(thrust::for_each(handle_ptr->get_thrust_policy(), + variables_to_fix.begin(), + variables_to_fix.end(), + [assignment = make_span(assignment)] __device__(i_t x) { + cuopt_assert(isfinite(assignment[x]), + "Fixing a variable to a non-finite value"); + })); const i_t num_segments = original_problem.n_constraints; f_t initial_value{0.}; From c247bfe1496a8902e5f91d0b9c6f7c4577faeeb3 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Thu, 16 Jul 2026 15:52:55 +0200 Subject: [PATCH 2/2] removed comments. compare solution before and after PDLP with variable fixing, take the one that is feasible. Otherwise, keep the original one. Signed-off-by: Nicolas L. Guidotti --- .../diversity/diversity_manager.cu | 19 ++++++++++--------- cpp/src/mip_heuristics/problem/problem.cu | 5 ----- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/cpp/src/mip_heuristics/diversity/diversity_manager.cu b/cpp/src/mip_heuristics/diversity/diversity_manager.cu index a206e87e08..d4fcbfa738 100644 --- a/cpp/src/mip_heuristics/diversity/diversity_manager.cu +++ b/cpp/src/mip_heuristics/diversity/diversity_manager.cu @@ -240,12 +240,6 @@ void diversity_manager_t::add_user_given_solutions( } if (problem_ptr->pre_process_assignment(init_sol_assignment)) { - // Seed the solution with the provided assignment BEFORE fixing integers and running the - // completion LP. Otherwise the fix uses the solution_t constructor's default assignment - // (the variable lower bounds), which is -inf for free variables; fixing such a variable - // makes fix_given_variables compute a NaN constraint bound (inf - inf) and trips the - // problem-representation validation. Seeding first also lets us keep the LP's repaired - // continuous values (previously overwritten by a post-LP copy). raft::copy(sol.assignment.data(), init_sol_assignment.data(), init_sol_assignment.size(), @@ -261,6 +255,14 @@ void diversity_manager_t::add_user_given_solutions( lp_settings, static_cast*>(nullptr)); bool is_feasible = sol.compute_feasibility(); + if (!is_feasible) { + raft::copy(sol.assignment.data(), + init_sol_assignment.data(), + init_sol_assignment.size(), + sol.handle_ptr->get_stream()); + is_feasible = sol.compute_feasibility(); + } + cuopt_func_call(sol.test_variable_bounds(true)); CUOPT_LOG_DEBUG("Adding initial solution success! feas %d objective %f excess %f", is_feasible, @@ -270,9 +272,8 @@ void diversity_manager_t::add_user_given_solutions( initial_sol_vector.emplace_back(std::move(sol)); } else { CUOPT_LOG_ERROR( - "Error cannot add the provided initial solution! \ - Assignment size %lu \ - initial solution size %lu", + "Error cannot add the provided initial solution! Assignment size %lu initial solution size " + "%lu", sol.assignment.size(), init_sol_assignment.size()); } diff --git a/cpp/src/mip_heuristics/problem/problem.cu b/cpp/src/mip_heuristics/problem/problem.cu index e0c8ba084d..ccba2d5f2b 100644 --- a/cpp/src/mip_heuristics/problem/problem.cu +++ b/cpp/src/mip_heuristics/problem/problem.cu @@ -1706,11 +1706,6 @@ void problem_t::fix_given_variables(problem_t& original_prob variables_to_fix.end(), [variable_fix_mask = make_span(fixing_helpers.variable_fix_mask)] __device__( i_t x) { variable_fix_mask[x] = 1; }); - // Guard: fixing a variable to a non-finite value corrupts the constraint RHS below - // (coeff * floor(+-inf) = +-inf, then original_bound - inf can be inf - inf = NaN), which - // otherwise surfaces much later as an opaque "Constraints bounds are invalid" validation - // error. Fail loudly at the source instead. Callers must seed a finite assignment before - // fixing (e.g. free variables must not be left at their -inf lower-bound default). cuopt_func_call(thrust::for_each(handle_ptr->get_thrust_policy(), variables_to_fix.begin(), variables_to_fix.end(),