Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion cpp/src/routing/adapters/adapted_generator.cu
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ bool adapted_generator_t<i_t, f_t, REQUEST>::make_feasible(
// fprintf(f.file_ptr, "EP size after ejection: %d \n", resource.ges.EP.size());
constexpr i_t perturbation_count = 1;
for (i_t i = 0; i < perturbation_count; ++i) {
resource.ls.run_random_local_search(adapted_solution.sol, false);
resource.ls.run_random_local_search(adapted_solution.sol, true, 1000);
}
resource.ges.fixed_route_loop();

Expand Down
10 changes: 5 additions & 5 deletions cpp/src/routing/adapters/adapted_modifier.cu
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void adapted_modifier_t<i_t, f_t, REQUEST>::perturbate(
// another PR removes it completely
resource.ls.set_active_weights(gpu_weight, std::numeric_limits<double>::max());
for (i_t i = 0; i < perturbation_count; ++i) {
resource.ls.run_random_local_search(adapted_solution.sol, false);
resource.ls.run_random_local_search(adapted_solution.sol, true, 1000);
}
adapted_solution.populate_host_data(true);
adapted_solution.check_device_host_coherence();
Expand All @@ -53,12 +53,12 @@ void adapted_modifier_t<i_t, f_t, REQUEST>::improve(
// set the excess limit to to the total excess with some multiplier
auto gpu_weight = get_cuopt_cost(weight);
bool consider_unserviced = true;
bool time_limit_enabled = true;

resource.ls.set_active_weights(gpu_weight);
resource.ls.start_timer(time_limit);
// Use work estimate derived from time_limit for determinism
int work_estimate_limit = static_cast<int>(time_limit * 1000); // TODO: tune factor
if (work_estimate_limit <= 0) work_estimate_limit = 10000;
resource.ls.run_best_local_search(
adapted_solution.sol, consider_unserviced, time_limit_enabled, run_cycle_finder);
adapted_solution.sol, consider_unserviced, true, work_estimate_limit, run_cycle_finder);
adapted_solution.populate_host_data();
adapted_solution.check_device_host_coherence();
cuopt_func_call(adapted_solution.sol.check_cost_coherence(gpu_weight));
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/routing/ges/execute_insertion.cu
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ bool guided_ejection_search_t<i_t, f_t, REQUEST>::try_single_insert_with_perturb
i_t perturbation_count = std::max(const_1, std::min(100 / solution_ptr->n_routes, const_2));
for (i_t i = 0; i < perturbation_count; ++i) {
solution_ptr->global_runtime_checks(false, false, "try_single_insert_with_perturbation");
local_search_ptr_->run_random_local_search(*solution_ptr, false);
local_search_ptr_->run_random_local_search(*solution_ptr, true, 1000);
}
auto n_found_candidates = find_single_insertion(request);
if (n_found_candidates != 0) { return perform_insertion(request); }
Expand Down
30 changes: 17 additions & 13 deletions cpp/src/routing/ges/squeeze.cu
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ i_t guided_ejection_search_t<i_t, f_t, REQUEST>::try_multiple_feasible_insertion
i_t perturbation_count = std::max(const_1, std::min(100 / solution_ptr->n_routes, const_2));
for (i_t i = 0; i < perturbation_count; ++i) {
solution_ptr->global_runtime_checks(false, true, "try_multiple_insert_with_perturbation_1");
local_search_ptr_->run_random_local_search(*solution_ptr, false);
local_search_ptr_->run_random_local_search(*solution_ptr, true, 1000);
}
}

Expand Down Expand Up @@ -243,14 +243,16 @@ bool guided_ejection_search_t<i_t, f_t, REQUEST>::squeeze_all_and_save()
double total_excess = solution_ptr->get_total_excess(ls_weights_after_squeeze);
local_search_ptr_->set_active_weights(ls_weights_after_squeeze, include_objective);

local_search_ptr_->start_timer(remaining_time());

// Use work estimate instead of time limit for determinism
solution_ptr->global_runtime_checks(true, false, "squeeze_all_and_save_before_ls");
const bool consider_unserviced = false;
const bool enable_time_limit = true;
const bool use_work_estimate = true;
// Derive work_estimate_limit from remaining_time (fallback to default if not available)
int work_estimate_limit = static_cast<int>(remaining_time() * 1000); // TODO: tune factor
if (work_estimate_limit <= 0) work_estimate_limit = 10000;
const bool enable_cycle_finder = false;
local_search_ptr_->run_best_local_search(
*solution_ptr, consider_unserviced, enable_time_limit, enable_cycle_finder);
*solution_ptr, consider_unserviced, use_work_estimate, work_estimate_limit, enable_cycle_finder);
Comment on lines +246 to +255
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

These squeeze-time local-search calls are no longer tied to the remaining solve budget.

All three paths now use a fixed work_estimate_limit = 10000. That makes the wall-clock cost of a squeeze step depend on instance size/hardware instead of the remaining GES budget, so one nested local-search call can overrun the caller's latency target. Please derive this budget from the remaining solver budget, or keep a remaining-time escape hatch around these calls.

Also applies to: 340-346, 401-409

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/routing/ges/squeeze.cu` around lines 246 - 253, The fixed
work_estimate_limit (work_estimate_limit = 10000) passed to
local_search_ptr_->run_best_local_search makes squeeze local-search calls ignore
the remaining solver budget; replace the hardcoded constant by deriving a budget
from the solver's remaining budget (or add a remaining-time escape hatch) and
pass that computed work estimate into run_best_local_search, ensuring
solution_ptr->global_runtime_checks is still invoked to enable deterministic
behavior; apply the same change to the equivalent call sites around the other
noted regions (the other squeeze calls at the later blocks) so all squeeze-time
local-search calls respect the remaining solver budget.


// reset the weights
total_excess = solution_ptr->get_total_excess(original_weights);
Expand Down Expand Up @@ -337,13 +339,14 @@ bool guided_ejection_search_t<i_t, f_t, REQUEST>::try_squeeze_feasible(
local_search_ptr_->set_active_weights(local_search_ptr_->move_candidates.weights,
include_objective);

local_search_ptr_->start_timer(remaining_time());

// Use work estimate instead of time limit for determinism
const bool consider_unserviced = false;
const bool enable_time_limit = true;
const bool use_work_estimate = true;
int work_estimate_limit = static_cast<int>(remaining_time() * 1000); // TODO: tune factor
if (work_estimate_limit <= 0) work_estimate_limit = 10000;
const bool enable_cycle_finder = false;
local_search_ptr_->run_best_local_search(
*solution_ptr, consider_unserviced, enable_time_limit, enable_cycle_finder);
*solution_ptr, consider_unserviced, use_work_estimate, work_estimate_limit, enable_cycle_finder);
// check if solution is feasible at the end
bool feasibilized = solution_ptr->is_feasible();
local_search_ptr_->set_active_weights(local_search_ptr_->move_candidates.weights,
Expand Down Expand Up @@ -398,15 +401,16 @@ bool guided_ejection_search_t<i_t, f_t, REQUEST>::try_squeeze_breaks_feasible()

if (solution_ptr->is_feasible()) { return true; }

local_search_ptr_->start_timer(remaining_time());

// Use work estimate instead of time limit for determinism
auto original_incl_objective = local_search_ptr_->move_candidates.include_objective;
local_search_ptr_->set_active_weights(local_search_ptr_->move_candidates.weights, false);
const bool consider_unserviced = false;
const bool enable_time_limit = true;
const bool use_work_estimate = true;
int work_estimate_limit = static_cast<int>(remaining_time() * 1000); // TODO: tune factor
if (work_estimate_limit <= 0) work_estimate_limit = 10000;
const bool enable_cycle_finder = false;
local_search_ptr_->run_best_local_search(
*solution_ptr, consider_unserviced, enable_time_limit, enable_cycle_finder);
*solution_ptr, consider_unserviced, use_work_estimate, work_estimate_limit, enable_cycle_finder);

local_search_ptr_->set_active_weights(local_search_ptr_->move_candidates.weights,
original_incl_objective);
Expand Down
34 changes: 23 additions & 11 deletions cpp/src/routing/local_search/local_search.cu
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,13 @@ bool local_search_t<i_t, f_t, REQUEST>::run_fast_search(solution_t<i_t, f_t, r_t
template <typename i_t, typename f_t, request_t REQUEST>
void local_search_t<i_t, f_t, REQUEST>::run_best_local_search(solution_t<i_t, f_t, REQUEST>& sol,
const bool consider_unserviced,
const bool time_limit_enabled,
const bool use_work_estimate_,
const i_t work_estimate_limit_,
const bool run_cycle_finder)
{
use_work_estimate = use_work_estimate_;
work_limit = work_estimate_limit_;
reset_work_counter();
// Handle a corner case when there is no single task that is feasible
if (sol.n_routes == 0) { return; }
// for production use working weights
Expand All @@ -263,13 +267,15 @@ void local_search_t<i_t, f_t, REQUEST>::run_best_local_search(solution_t<i_t, f_
consider_unserviced && !sol.problem_ptr->has_prize_collection();
sol.global_runtime_checks(should_all_nodes_be_served, false, "run_best_local_search_begin");
[[maybe_unused]] double cost_before = 0., cost_after = 0.;
while (iter < iter_limit) {
while (iter < iter_limit && !check_work_estimate()) {
if constexpr (REQUEST == request_t::VRP) { extract_nodes_to_search(sol, move_candidates); }
iter++;
iter++;
increment_work_counter();
// fast loop, insider this sliding, fast vrp search and fast cross search happens
while (true) {
if (time_limit_enabled && local_search_t<i_t, f_t, REQUEST>::check_time_limit()) { break; }
if (use_work_estimate && check_work_estimate()) { break; }
iter++;
increment_work_counter();
if (run_fast_search(sol, sol.problem_ptr->is_tsp && iter == 2)) { continue; }
if (consider_unserviced && sol.problem_ptr->has_prize_collection() &&
run_collect_prizes(sol)) {
Expand Down Expand Up @@ -318,9 +324,8 @@ void local_search_t<i_t, f_t, REQUEST>::run_best_local_search(solution_t<i_t, f_
}

// If there is no improvement at all, break the local search loop
bool time_limit_reached =
(time_limit_enabled && local_search_t<i_t, f_t, REQUEST>::check_time_limit());
if (time_limit_reached || !improved) {
bool work_estimate_reached = (use_work_estimate && check_work_estimate());
if (work_estimate_reached || !improved) {
cuopt_func_call(sol.check_cost_coherence(move_candidates.weights));
break;
}
Expand All @@ -331,8 +336,12 @@ void local_search_t<i_t, f_t, REQUEST>::run_best_local_search(solution_t<i_t, f_

template <typename i_t, typename f_t, request_t REQUEST>
void local_search_t<i_t, f_t, REQUEST>::run_random_local_search(solution_t<i_t, f_t, REQUEST>& sol,
bool time_limit_enabled)
bool use_work_estimate_,
i_t work_estimate_limit_)
{
use_work_estimate = use_work_estimate_;
work_limit = work_estimate_limit_;
reset_work_counter();
// Handle a corner case when there is no single task that is feasible
if (sol.n_routes == 0) { return; }
if (sol.n_routes > 1024) { return; }
Expand All @@ -346,20 +355,23 @@ void local_search_t<i_t, f_t, REQUEST>::run_random_local_search(solution_t<i_t,
move_candidates.reset(sol.sol_handle);
move_candidates.random_move_candidates.reset(sol.sol_handle);
calculate_route_compatibility(sol);
if (use_work_estimate) increment_work_counter();
find_insertions<i_t, f_t, REQUEST>(sol, move_candidates, search_type_t::RANDOM);
if (use_work_estimate) increment_work_counter();

RAFT_CHECK_CUDA(sol.sol_handle->get_stream());
sol.sol_handle->sync_stream();
populate_random_moves(sol);
if (use_work_estimate) increment_work_counter();

bool time_limit_reached =
(time_limit_enabled && local_search_t<i_t, f_t, REQUEST>::check_time_limit());
bool work_estimate_reached = (use_work_estimate && check_work_estimate());
// if there is no more insertions found
if (move_candidates.move_path.n_insertions.value(sol.sol_handle->get_stream()) == 0 ||
time_limit_reached) {
work_estimate_reached) {
return;
}
perform_moves(sol, move_candidates);
if (use_work_estimate) increment_work_counter();

sol.global_runtime_checks(false, is_originally_feasible, "run_random_local_search_end");
sol.sol_handle->sync_stream();
Expand Down
13 changes: 11 additions & 2 deletions cpp/src/routing/local_search/local_search.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,12 @@ class local_search_t {

void run_best_local_search(solution_t<i_t, f_t, REQUEST>& sol,
const bool consider_unserviced,
const bool time_limit_enabled,
const bool use_work_estimate,
const i_t work_estimate_limit,
const bool run_cycle_finder);
void run_random_local_search(solution_t<i_t, f_t, REQUEST>& sol, bool time_limit_enabled = true);
void run_random_local_search(solution_t<i_t, f_t, REQUEST>& sol,
bool use_work_estimate = false,
i_t work_estimate_limit = std::numeric_limits<i_t>::max());

void perturb_solution(solution_t<i_t, f_t, REQUEST>& sol, i_t perturb_count = -1);
void perform_moves(solution_t<i_t, f_t, REQUEST>& solution,
Expand Down Expand Up @@ -166,6 +169,12 @@ class local_search_t {
}

i_t max_iterations = std::numeric_limits<i_t>::max();
i_t work_counter = 0;
i_t work_limit = std::numeric_limits<i_t>::max();
bool use_work_estimate = false;
void reset_work_counter() { work_counter = 0; }
bool check_work_estimate() const { return use_work_estimate && work_counter >= work_limit; }
void increment_work_counter(i_t inc = 1) { work_counter += inc; }
// move candidates
move_candidates_t<i_t, f_t> move_candidates;
// hvrp
Expand Down