From ca090960fa0656f5fd6378a0ead554ca5ced81e9 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 19 Dec 2024 11:58:20 +0100 Subject: [PATCH 1/3] Revert "Merge branch 'core/recreate-complexity'" This reverts commit 81b2a955e7a50ba0a17d07e88c3702ef799b2666, reversing changes made to f6ae9957a20615984a21988bc2cc128d9fa0eceb. --- CHANGELOG.md | 1 - src/algorithms/local_search/local_search.cpp | 237 +++++++++++++------ src/algorithms/local_search/local_search.h | 7 +- src/structures/vroom/job.cpp | 13 - src/structures/vroom/job.h | 11 - src/utils/helpers.cpp | 8 +- 6 files changed, 170 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0701522..9394c639d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,6 @@ - Refactor heuristics to reduce code duplication (#1181) - Refactor `Matrix` template class (#1089) - Refactor to use `std::format` whenever possible (#1081) -- Reduce complexity for recreation process (#1155) - Refactor `SolutionIndicators` (#1169) - Remove amount consistency checks in `parse` in favor of upstream checks in `Input` (#1086) - Reduce code duplication in routing wrappers (#1184) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 561ece74c..d956d393d 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -161,98 +161,180 @@ void LocalSearch::recreate(const std::vector& routes + TSPFix>::try_job_additions(const std::vector& routes, + double regret_coeff #ifdef LOG_LS - , - bool log_addition_step + , + bool log_addition_step #endif ) { - std::vector ordered_jobs(_sol_state.unassigned.begin(), - _sol_state.unassigned.end()); - std::ranges::sort(ordered_jobs, [this](const Index lhs, const Index rhs) { - return _input.jobs[lhs] < _input.jobs[rhs]; - }); + bool job_added; - std::unordered_set modified_vehicles; + std::vector> route_job_insertions; + route_job_insertions.reserve(routes.size()); - for (const auto j : ordered_jobs) { - const auto& current_job = _input.jobs[j]; - if (current_job.type == JOB_TYPE::DELIVERY) { - continue; + for (std::size_t i = 0; i < routes.size(); ++i) { + route_job_insertions.emplace_back(_input.jobs.size(), + RouteInsertion(_input.get_amount_size())); + + const auto v = routes[i]; + const auto fixed_cost = + _sol[v].empty() ? _input.vehicles[v].fixed_cost() : 0; + + for (const auto j : _sol_state.unassigned) { + if (const auto& current_job = _input.jobs[j]; + current_job.type == JOB_TYPE::DELIVERY) { + continue; + } + route_job_insertions[i][j] = + compute_best_insertion(_input, _sol_state, j, v, _sol[v]); + + if (route_job_insertions[i][j].eval != NO_EVAL) { + route_job_insertions[i][j].eval.cost += fixed_cost; + } } + } + + std::unordered_set modified_vehicles; + do { + Priority best_priority = 0; RouteInsertion best_insertion(_input.get_amount_size()); - Index best_v = 0; + double best_cost = std::numeric_limits::max(); + Index best_job_rank = 0; + Index best_route = 0; + std::size_t best_route_idx = 0; - for (const auto v : routes) { - if (const unsigned added_tasks = - (current_job.type == JOB_TYPE::PICKUP) ? 2 : 1; - _sol[v].size() + added_tasks > _input.vehicles[v].max_tasks) { + for (const auto j : _sol_state.unassigned) { + const auto& current_job = _input.jobs[j]; + if (current_job.type == JOB_TYPE::DELIVERY) { continue; } - auto current_best_insertion = - compute_best_insertion(_input, _sol_state, j, v, _sol[v]); + const auto job_priority = current_job.priority; - if (current_best_insertion.eval != NO_EVAL && _sol[v].empty()) { - current_best_insertion.eval.cost += _input.vehicles[v].fixed_cost(); + if (job_priority < best_priority) { + // Insert higher priority jobs first. + continue; } - if (current_best_insertion.eval < best_insertion.eval) { - best_insertion = current_best_insertion; - best_v = v; + auto smallest = _input.get_cost_upper_bound(); + auto second_smallest = _input.get_cost_upper_bound(); + std::size_t smallest_idx = std::numeric_limits::max(); + + for (std::size_t i = 0; i < routes.size(); ++i) { + if (route_job_insertions[i][j].eval.cost < smallest) { + smallest_idx = i; + second_smallest = smallest; + smallest = route_job_insertions[i][j].eval.cost; + } else if (route_job_insertions[i][j].eval.cost < second_smallest) { + second_smallest = route_job_insertions[i][j].eval.cost; + } } - } - if (best_insertion.eval == NO_EVAL) { - // Current job cannot be inserted. - continue; - } + // Find best route for current job based on cost of addition and + // regret cost of not adding. + for (std::size_t i = 0; i < routes.size(); ++i) { + if (route_job_insertions[i][j].eval == NO_EVAL) { + continue; + } - // Found a valid insertion spot for current job. - _sol_state.unassigned.erase(j); + const auto& current_r = _sol[routes[i]]; + const auto& vehicle = _input.vehicles[routes[i]]; - if (current_job.type == JOB_TYPE::SINGLE) { - _sol[best_v].add(_input, j, best_insertion.single_rank); - } else { - assert(current_job.type == JOB_TYPE::PICKUP); + if (bool is_pickup = (_input.jobs[j].type == JOB_TYPE::PICKUP); + current_r.size() + (is_pickup ? 2 : 1) > vehicle.max_tasks) { + continue; + } - std::vector modified_with_pd; - modified_with_pd.reserve(best_insertion.delivery_rank - - best_insertion.pickup_rank + 2); - modified_with_pd.push_back(j); - - std::copy(_sol[best_v].route.begin() + best_insertion.pickup_rank, - _sol[best_v].route.begin() + best_insertion.delivery_rank, - std::back_inserter(modified_with_pd)); - modified_with_pd.push_back(j + 1); - - _sol[best_v].replace(_input, - best_insertion.delivery, - modified_with_pd.begin(), - modified_with_pd.end(), - best_insertion.pickup_rank, - best_insertion.delivery_rank); - - assert(_sol_state.unassigned.find(j + 1) != _sol_state.unassigned.end()); - _sol_state.unassigned.erase(j + 1); + const auto regret_cost = + (i == smallest_idx) ? second_smallest : smallest; + + const double current_cost = + static_cast(route_job_insertions[i][j].eval.cost) - + regret_coeff * static_cast(regret_cost); + + if ((job_priority > best_priority) || + (job_priority == best_priority && current_cost < best_cost)) { + best_priority = job_priority; + best_job_rank = j; + best_route = routes[i]; + best_insertion = route_job_insertions[i][j]; + best_cost = current_cost; + best_route_idx = i; + } + } } - // Update best route data, required for consistency. - modified_vehicles.insert(best_v); - _sol_state.update_route_eval(_sol[best_v].route, best_v); - _sol_state.set_insertion_ranks(_sol[best_v], best_v); + job_added = (best_cost < std::numeric_limits::max()); + + if (job_added) { + _sol_state.unassigned.erase(best_job_rank); + + if (const auto& best_job = _input.jobs[best_job_rank]; + best_job.type == JOB_TYPE::SINGLE) { + _sol[best_route].add(_input, best_job_rank, best_insertion.single_rank); + } else { + assert(best_job.type == JOB_TYPE::PICKUP); + + std::vector modified_with_pd; + modified_with_pd.reserve(best_insertion.delivery_rank - + best_insertion.pickup_rank + 2); + modified_with_pd.push_back(best_job_rank); + + std::copy(_sol[best_route].route.begin() + best_insertion.pickup_rank, + _sol[best_route].route.begin() + best_insertion.delivery_rank, + std::back_inserter(modified_with_pd)); + modified_with_pd.push_back(best_job_rank + 1); + + _sol[best_route].replace(_input, + best_insertion.delivery, + modified_with_pd.begin(), + modified_with_pd.end(), + best_insertion.pickup_rank, + best_insertion.delivery_rank); + + assert(_sol_state.unassigned.find(best_job_rank + 1) != + _sol_state.unassigned.end()); + _sol_state.unassigned.erase(best_job_rank + 1); + } + + // Update best_route data required for consistency. + modified_vehicles.insert(best_route); + _sol_state.update_route_eval(_sol[best_route].route, best_route); + _sol_state.set_insertion_ranks(_sol[best_route], best_route); + + const auto fixed_cost = + _sol[best_route].empty() ? _input.vehicles[best_route].fixed_cost() : 0; + + for (const auto j : _sol_state.unassigned) { + if (const auto& current_job = _input.jobs[j]; + current_job.type == JOB_TYPE::DELIVERY) { + continue; + } + route_job_insertions[best_route_idx][j] = + compute_best_insertion(_input, + _sol_state, + j, + best_route, + _sol[best_route]); + + if (route_job_insertions[best_route_idx][j].eval != NO_EVAL) { + route_job_insertions[best_route_idx][j].eval.cost += fixed_cost; + } + } #ifdef LOG_LS - if (log_addition_step) { - steps.emplace_back(utils::now(), - log::EVENT::JOB_ADDITION, - OperatorName::MAX, - utils::SolutionIndicators(_input, _sol), - std::nullopt); - } + if (log_addition_step) { + steps.emplace_back(utils::now(), + log::EVENT::JOB_ADDITION, + OperatorName::MAX, + utils::SolutionIndicators(_input, _sol), + std::nullopt); + } #endif - } + } + } while (job_added); for (const auto v : modified_vehicles) { _sol_state.update_route_bbox(_sol[v].route, v); @@ -1805,14 +1887,16 @@ void LocalSearchaddition_candidates() + try_job_additions(best_ops[best_source][best_target] + ->addition_candidates(), + 0 #ifdef LOG_LS - , - true + , + true #endif ); @@ -1859,8 +1943,8 @@ void LocalSearchrequired_unassigned()) { if (!_sol_state.unassigned.contains(req_u)) { // This move should be invalidated because a required - // unassigned job has been added by recreate step in the - // meantime. + // unassigned job has been added by try_job_additions in + // the meantime. invalidate_move = true; break; } @@ -2003,11 +2087,12 @@ void LocalSearch steps; #endif - void recreate(const std::vector& routes + void try_job_additions(const std::vector& routes, + double regret_coeff #ifdef LOG_LS - , - bool log_addition_step = false + , + bool log_addition_step = false #endif ); diff --git a/src/structures/vroom/job.cpp b/src/structures/vroom/job.cpp index ee8371733..bc02f930b 100644 --- a/src/structures/vroom/job.cpp +++ b/src/structures/vroom/job.cpp @@ -10,19 +10,8 @@ All rights reserved (see LICENSE). #include "structures/vroom/job.h" #include "utils/helpers.h" -#include - namespace vroom { -inline Duration get_tw_length(const std::vector& tws) { - return std::accumulate(tws.begin(), - tws.end(), - 0, - [](auto sum, const auto& tw) { - return sum + tw.length; - }); -}; - Job::Job(Id id, const Location& location, UserDuration setup, @@ -43,7 +32,6 @@ Job::Job(Id id, skills(std::move(skills)), priority(priority), tws(tws), - tw_length(get_tw_length(tws)), description(std::move(description)) { utils::check_tws(tws, id, "job"); utils::check_priority(priority, id, "job"); @@ -69,7 +57,6 @@ Job::Job(Id id, skills(std::move(skills)), priority(priority), tws(tws), - tw_length(get_tw_length(tws)), description(std::move(description)) { assert(type == JOB_TYPE::PICKUP || type == JOB_TYPE::DELIVERY); utils::check_tws(tws, id, "job"); diff --git a/src/structures/vroom/job.h b/src/structures/vroom/job.h index 6723c9041..9cc9cd0c3 100644 --- a/src/structures/vroom/job.h +++ b/src/structures/vroom/job.h @@ -30,7 +30,6 @@ struct Job { const Skills skills; const Priority priority; const std::vector tws; - const Duration tw_length; const std::string description; // Constructor for regular one-stop job (JOB_TYPE::SINGLE). @@ -65,16 +64,6 @@ struct Job { } bool is_valid_start(Duration time) const; - - friend bool operator<(const Job& lhs, const Job& rhs) { - // Sort by: - // - decreasing priority - // - increasing TW length - // - decreasing delivery amount - // - decreasing pickup amount - return std::tie(rhs.priority, lhs.tw_length, rhs.delivery, rhs.pickup) < - std::tie(lhs.priority, rhs.tw_length, lhs.delivery, lhs.pickup); - } }; } // namespace vroom diff --git a/src/utils/helpers.cpp b/src/utils/helpers.cpp index 81018bf87..5a7565887 100644 --- a/src/utils/helpers.cpp +++ b/src/utils/helpers.cpp @@ -230,7 +230,7 @@ void check_priority(const Priority priority, } } -inline std::vector get_unassigned_jobs_from_ranks( +std::vector get_unassigned_jobs_from_ranks( const Input& input, const std::unordered_set& unassigned_ranks) { std::vector unassigned_jobs; @@ -397,7 +397,8 @@ Solution format_solution(const Input& input, const RawSolution& raw_routes) { return Solution(input.zero_amount(), std::move(routes), - get_unassigned_jobs_from_ranks(input, unassigned_ranks)); + std::move( + get_unassigned_jobs_from_ranks(input, unassigned_ranks))); } Route format_route(const Input& input, @@ -899,7 +900,8 @@ Solution format_solution(const Input& input, const TWSolution& tw_routes) { return Solution(input.zero_amount(), std::move(routes), - get_unassigned_jobs_from_ranks(input, unassigned_ranks)); + std::move( + get_unassigned_jobs_from_ranks(input, unassigned_ranks))); } } // namespace vroom::utils From 9ea05f453b58d8ab2640407ec429418d04c826df Mon Sep 17 00:00:00 2001 From: jcoupey Date: Mon, 16 Sep 2024 16:15:04 +0200 Subject: [PATCH 2/3] Partial cherry-pick of 498438a0. --- src/utils/helpers.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/utils/helpers.cpp b/src/utils/helpers.cpp index 5a7565887..81018bf87 100644 --- a/src/utils/helpers.cpp +++ b/src/utils/helpers.cpp @@ -230,7 +230,7 @@ void check_priority(const Priority priority, } } -std::vector get_unassigned_jobs_from_ranks( +inline std::vector get_unassigned_jobs_from_ranks( const Input& input, const std::unordered_set& unassigned_ranks) { std::vector unassigned_jobs; @@ -397,8 +397,7 @@ Solution format_solution(const Input& input, const RawSolution& raw_routes) { return Solution(input.zero_amount(), std::move(routes), - std::move( - get_unassigned_jobs_from_ranks(input, unassigned_ranks))); + get_unassigned_jobs_from_ranks(input, unassigned_ranks)); } Route format_route(const Input& input, @@ -900,8 +899,7 @@ Solution format_solution(const Input& input, const TWSolution& tw_routes) { return Solution(input.zero_amount(), std::move(routes), - std::move( - get_unassigned_jobs_from_ranks(input, unassigned_ranks))); + get_unassigned_jobs_from_ranks(input, unassigned_ranks)); } } // namespace vroom::utils From c0448099d7ed55102c2a300bfd90bd64c292bb83 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 19 Dec 2024 14:03:34 +0100 Subject: [PATCH 3/3] Yet another const var. --- src/algorithms/local_search/local_search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index d956d393d..457ddd529 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -242,7 +242,7 @@ void LocalSearch vehicle.max_tasks) { continue; }