From 010a394810ea5e243402d5452f1bcce9698d94b5 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Fri, 7 Oct 2022 10:14:26 +0200 Subject: [PATCH 1/3] Implement cvrp::RouteShift operator. --- src/problems/cvrp/operators/route_shift.cpp | 173 ++++++++++++++++++++ src/problems/cvrp/operators/route_shift.h | 56 +++++++ 2 files changed, 229 insertions(+) create mode 100644 src/problems/cvrp/operators/route_shift.cpp create mode 100644 src/problems/cvrp/operators/route_shift.h diff --git a/src/problems/cvrp/operators/route_shift.cpp b/src/problems/cvrp/operators/route_shift.cpp new file mode 100644 index 000000000..fdd934d27 --- /dev/null +++ b/src/problems/cvrp/operators/route_shift.cpp @@ -0,0 +1,173 @@ +/* + +This file is part of VROOM. + +Copyright (c) 2015-2022, Julien Coupey. +All rights reserved (see LICENSE). + +*/ + +#include "problems/cvrp/operators/route_shift.h" + +namespace vroom { +namespace cvrp { + +RouteShift::RouteShift(const Input& input, + const utils::SolutionState& sol_state, + RawRoute& s_route, + Index s_vehicle, + RawRoute& t_route, + Index t_vehicle) + : Operator(OperatorName::RouteShift, + input, + sol_state, + s_route, + s_vehicle, + 0, // Dummy value + t_route, + t_vehicle, + 0), // Dummy value + shift_to_start(false), + shift_to_end(false) { + assert(s_vehicle != t_vehicle); + assert(s_route.size() >= 3); + assert(!t_route.empty()); + + assert(_sol_state.bwd_skill_rank[s_vehicle][t_vehicle] == 0); +} + +Eval RouteShift::gain_upper_bound() { + const auto& s_v = _input.vehicles[s_vehicle]; + const auto& t_v = _input.vehicles[t_vehicle]; + + // Source route gain. + auto first_s_index = _input.jobs[s_route.front()].index(); + if (s_v.has_start()) { + s_gain += s_v.eval(s_v.start.value().index(), first_s_index); + } + + auto last_s_index = _input.jobs[s_route.back()].index(); + if (s_v.has_end()) { + s_gain += s_v.eval(last_s_index, s_v.end.value().index()); + } + + s_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); + + // Target route gain options when inserting source route at start or + // end or target route. + if (t_v.has_start()) { + auto first_t_index = _input.jobs[t_route.front()].index(); + _start_t_gain += t_v.eval(t_v.start.value().index(), first_t_index); + _start_t_gain -= t_v.eval(t_v.start.value().index(), first_s_index); + _start_t_gain -= t_v.eval(last_s_index, first_t_index); + } + + if (t_v.has_end()) { + auto last_t_index = _input.jobs[t_route.back()].index(); + _end_t_gain += t_v.eval(last_t_index, t_v.end.value().index()); + _end_t_gain -= t_v.eval(last_s_index, t_v.end.value().index()); + _end_t_gain -= t_v.eval(last_t_index, first_s_index); + } + + _start_t_gain -= _sol_state.fwd_costs[s_vehicle][t_vehicle].back(); + _end_t_gain -= _sol_state.fwd_costs[s_vehicle][t_vehicle].back(); + + _gain_upper_bound_computed = true; + + return s_gain + std::max(_start_t_gain, _end_t_gain); +} + +void RouteShift::compute_gain() { + assert(_gain_upper_bound_computed); + assert(is_start_valid or is_end_valid); + + stored_gain = s_gain; + + if (_start_t_gain < _end_t_gain) { + if (is_end_valid) { + shift_to_end = true; + stored_gain += _end_t_gain; + } else { + shift_to_start = true; + stored_gain += _start_t_gain; + } + } else { + if (is_start_valid) { + shift_to_start = true; + stored_gain += _start_t_gain; + } else { + shift_to_end = true; + stored_gain += _end_t_gain; + } + } + + gain_computed = true; +} + +bool RouteShift::is_valid() { + assert(_gain_upper_bound_computed); + + const auto& s_delivery = source.job_deliveries_sum(); + const auto& s_pickup = source.job_pickups_sum(); + + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + + is_start_valid = + target.is_valid_addition_for_capacity(_input, s_pickup, s_delivery, 0); + + if (is_start_valid) { + is_start_valid = + (t_travel_time <= t_v.max_travel_time + _start_t_gain.duration) and + target.is_valid_addition_for_capacity_inclusion(_input, + s_delivery, + s_route.begin(), + s_route.end(), + 0, + 0); + } + + is_end_valid = target.is_valid_addition_for_capacity(_input, + s_pickup, + s_delivery, + t_route.size()); + + if (is_end_valid) { + is_end_valid = + (t_travel_time <= t_v.max_travel_time + _end_t_gain.duration) and + target.is_valid_addition_for_capacity_inclusion(_input, + s_delivery, + s_route.begin(), + s_route.end(), + t_route.size(), + t_route.size()); + } + + return (is_start_valid or is_end_valid); +} + +void RouteShift::apply() { + if (shift_to_start) { + t_route.insert(t_route.begin(), s_route.begin(), s_route.end()); + } else { + assert(shift_to_end); + + t_route.insert(t_route.end(), s_route.begin(), s_route.end()); + } + + s_route.erase(s_route.begin(), s_route.end()); + + source.update_amounts(_input); + target.update_amounts(_input); +} + +std::vector RouteShift::addition_candidates() const { + return {s_vehicle, t_vehicle}; +} + +std::vector RouteShift::update_candidates() const { + return {s_vehicle, t_vehicle}; +} + +} // namespace cvrp +} // namespace vroom diff --git a/src/problems/cvrp/operators/route_shift.h b/src/problems/cvrp/operators/route_shift.h new file mode 100644 index 000000000..aa7c4d7be --- /dev/null +++ b/src/problems/cvrp/operators/route_shift.h @@ -0,0 +1,56 @@ +#ifndef CVRP_ROUTE_SHIFT_H +#define CVRP_ROUTE_SHIFT_H + +/* + +This file is part of VROOM. + +Copyright (c) 2015-2022, Julien Coupey. +All rights reserved (see LICENSE). + +*/ + +#include "algorithms/local_search/operator.h" + +namespace vroom { +namespace cvrp { + +class RouteShift : public ls::Operator { +private: + bool _gain_upper_bound_computed; + Eval _start_t_gain; + Eval _end_t_gain; + +protected: + bool is_start_valid; + bool is_end_valid; + bool shift_to_start; + bool shift_to_end; + + virtual void compute_gain() override; + +public: + RouteShift(const Input& input, + const utils::SolutionState& sol_state, + RawRoute& s_route, + Index s_vehicle, + RawRoute& t_route, + Index t_vehicle); + + // Compute and store all possible costs depending on whether the + // insertion happens at the beginning or the end of target route. + Eval gain_upper_bound(); + + virtual bool is_valid() override; + + virtual void apply() override; + + virtual std::vector addition_candidates() const override; + + virtual std::vector update_candidates() const override; +}; + +} // namespace cvrp +} // namespace vroom + +#endif From 75d41cfe20b3fd97c98e044c6a8cfadf816e8517 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Fri, 7 Oct 2022 10:15:31 +0200 Subject: [PATCH 2/3] Implement vrptw::RouteShift operator. --- src/problems/vrptw/operators/route_shift.cpp | 75 ++++++++++++++++++++ src/problems/vrptw/operators/route_shift.h | 39 ++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/problems/vrptw/operators/route_shift.cpp create mode 100644 src/problems/vrptw/operators/route_shift.h diff --git a/src/problems/vrptw/operators/route_shift.cpp b/src/problems/vrptw/operators/route_shift.cpp new file mode 100644 index 000000000..ab73ad82a --- /dev/null +++ b/src/problems/vrptw/operators/route_shift.cpp @@ -0,0 +1,75 @@ +/* + +This file is part of VROOM. + +Copyright (c) 2015-2022, Julien Coupey. +All rights reserved (see LICENSE). + +*/ + +#include "problems/vrptw/operators/route_shift.h" + +namespace vroom { +namespace vrptw { + +RouteShift::RouteShift(const Input& input, + const utils::SolutionState& sol_state, + TWRoute& tw_s_route, + Index s_vehicle, + TWRoute& tw_t_route, + Index t_vehicle) + : cvrp::RouteShift(input, + sol_state, + static_cast(tw_s_route), + s_vehicle, + static_cast(tw_t_route), + t_vehicle), + _tw_s_route(tw_s_route), + _tw_t_route(tw_t_route) { +} + +bool RouteShift::is_valid() { + bool valid = cvrp::RouteShift::is_valid(); + + if (valid) { + is_start_valid = + is_start_valid && _tw_t_route.is_valid_addition_for_tw(_input, + s_route.begin(), + s_route.end(), + 0, + 0); + + is_end_valid = + is_end_valid && _tw_t_route.is_valid_addition_for_tw(_input, + s_route.begin(), + s_route.end(), + t_route.size(), + t_route.size()); + + valid = is_start_valid or is_end_valid; + } + + return valid; +} + +void RouteShift::apply() { + if (shift_to_start) { + _tw_t_route.replace(_input, s_route.begin(), s_route.end(), 0, 0); + } else { + assert(shift_to_end); + + _tw_t_route.replace(_input, + s_route.begin(), + s_route.end(), + t_route.size(), + t_route.size()); + } + + _tw_s_route.remove(_input, 0, s_route.size()); + + source.update_amounts(_input); + target.update_amounts(_input); +} + +} // namespace vrptw +} // namespace vroom diff --git a/src/problems/vrptw/operators/route_shift.h b/src/problems/vrptw/operators/route_shift.h new file mode 100644 index 000000000..b5cd49677 --- /dev/null +++ b/src/problems/vrptw/operators/route_shift.h @@ -0,0 +1,39 @@ +#ifndef VRPTW_ROUTE_SHIFT_H +#define VRPTW_ROUTE_SHIFT_H + +/* + +This file is part of VROOM. + +Copyright (c) 2015-2022, Julien Coupey. +All rights reserved (see LICENSE). + +*/ + +#include "problems/cvrp/operators/route_shift.h" + +namespace vroom { +namespace vrptw { + +class RouteShift : public cvrp::RouteShift { +private: + TWRoute& _tw_s_route; + TWRoute& _tw_t_route; + +public: + RouteShift(const Input& input, + const utils::SolutionState& sol_state, + TWRoute& tw_s_route, + Index s_vehicle, + TWRoute& tw_t_route, + Index t_vehicle); + + virtual bool is_valid() override; + + virtual void apply() override; +}; + +} // namespace vrptw +} // namespace vroom + +#endif From bd5b14ca58d0758ef9219d849524498409f7144b Mon Sep 17 00:00:00 2001 From: jcoupey Date: Fri, 7 Oct 2022 10:16:40 +0200 Subject: [PATCH 3/3] Use new operator in local search. --- src/algorithms/local_search/local_search.cpp | 156 +++++++++++++------ src/algorithms/local_search/local_search.h | 3 +- src/problems/cvrp/cvrp.cpp | 4 +- src/problems/vrptw/vrptw.cpp | 4 +- src/structures/typedefs.h | 1 + src/utils/helpers.h | 3 +- 6 files changed, 119 insertions(+), 52 deletions(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 1ce6e645c..37390fa75 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -22,6 +22,7 @@ All rights reserved (see LICENSE). #include "problems/vrptw/operators/relocate.h" #include "problems/vrptw/operators/reverse_two_opt.h" #include "problems/vrptw/operators/route_exchange.h" +#include "problems/vrptw/operators/route_shift.h" #include "problems/vrptw/operators/swap_star.h" #include "problems/vrptw/operators/two_opt.h" #include "problems/vrptw/operators/unassigned_exchange.h" @@ -46,7 +47,8 @@ template + class RouteExchange, + class RouteShift> LocalSearch::LocalSearch(const Input& input, - std::vector& sol, - unsigned max_nb_jobs_removal, - const Timeout& timeout) + RouteExchange, + RouteShift>::LocalSearch(const Input& input, + std::vector& sol, + unsigned max_nb_jobs_removal, + const Timeout& timeout) : _input(input), _nb_vehicles(_input.vehicles.size()), _max_nb_jobs_removal(max_nb_jobs_removal), @@ -129,7 +132,8 @@ template + class RouteExchange, + class RouteShift> void LocalSearch::try_job_additions(const std::vector& - routes, - double regret_coeff) { + RouteExchange, + RouteShift>::try_job_additions(const std::vector& + routes, + double regret_coeff) { bool job_added; @@ -303,7 +308,8 @@ template + class RouteExchange, + class RouteShift> void LocalSearch::run_ls_step() { + RouteExchange, + RouteShift>::run_ls_step() { // Store best move involving a pair of routes. std::vector>> best_ops(_nb_vehicles); for (std::size_t v = 0; v < _nb_vehicles; ++v) { @@ -930,6 +937,46 @@ void LocalSearch 0 or + best_priorities[s_t.second] > 0 or _sol[s_t.first].size() <= 2 or + _sol[s_t.second].empty() or + _sol_state.bwd_skill_rank[s_t.first][s_t.second] != 0) { + // Moving a (pair of) job is already taken care of in other + // operators. Moving to an empty route is a + // RouteExchange. Whole route should be transferable. + continue; + } + + // TODO stop if homogeneous vehicles and target size under 3. + + const auto& t_v = _input.vehicles[s_t.second]; + + if (_sol[s_t.first].size() + _sol[s_t.second].size() > t_v.max_tasks) { + continue; + } + + // TODO handle vehicles/jobs compatibility and ranks restrictions. + +#ifdef LOG_LS_OPERATORS + ++tried_moves[OperatorName::RouteShift]; +#endif + RouteShift r(_input, + _sol_state, + _sol[s_t.first], + s_t.first, + _sol[s_t.second], + s_t.second); + + auto& current_best = best_gains[s_t.first][s_t.second]; + if (r.gain_upper_bound() > current_best and r.is_valid() and + r.gain() > current_best) { + current_best = r.gain(); + best_ops[s_t.first][s_t.second] = std::make_unique(r); + } + } + if (_input.has_jobs()) { // Move(s) that don't make sense for shipment-only instances. @@ -1756,7 +1803,8 @@ template + class RouteExchange, + class RouteShift> void LocalSearch::run() { + RouteExchange, + RouteShift>::run() { bool try_ls_step = true; bool first_step = true; @@ -1860,7 +1909,8 @@ template + class RouteExchange, + class RouteShift> std::array LocalSearch::get_stats() const { + RouteExchange, + RouteShift>::get_stats() const { std::array stats; for (auto op = 0; op < OperatorName::MAX; ++op) { stats[op] = OperatorStats(tried_moves.at(op), applied_moves.at(op)); @@ -1904,7 +1955,8 @@ template + class RouteExchange, + class RouteShift> Eval LocalSearch::job_route_cost(Index v_target, - Index v, - Index r) { + RouteExchange, + RouteShift>::job_route_cost(Index v_target, Index v, Index r) { assert(v != v_target); Eval eval(INFINITE_COST, 0); @@ -1975,7 +2026,8 @@ template + class RouteExchange, + class RouteShift> Eval LocalSearch::relocate_cost_lower_bound(Index v, Index r) { + RouteExchange, + RouteShift>::relocate_cost_lower_bound(Index v, Index r) { Eval best_bound(INFINITE_COST, 0); for (std::size_t other_v = 0; other_v < _sol.size(); ++other_v) { @@ -2023,7 +2076,8 @@ template + class RouteExchange, + class RouteShift> Eval LocalSearch::relocate_cost_lower_bound(Index v, - Index r1, - Index r2) { + RouteExchange, + RouteShift>::relocate_cost_lower_bound(Index v, + Index r1, + Index r2) { Eval best_bound(INFINITE_COST, 0); for (std::size_t other_v = 0; other_v < _sol.size(); ++other_v) { @@ -2075,7 +2130,8 @@ template + class RouteExchange, + class RouteShift> void LocalSearch::remove_from_routes() { + RouteExchange, + RouteShift>::remove_from_routes() { // Store nearest job from and to any job in any route for constant // time access down the line. for (std::size_t v1 = 0; v1 < _nb_vehicles; ++v1) { @@ -2223,25 +2280,26 @@ template -utils::SolutionIndicators -LocalSearch::indicators() const { + class RouteExchange, + class RouteShift> +utils::SolutionIndicators LocalSearch::indicators() const { return _best_sol_indicators; } @@ -2261,7 +2319,8 @@ template class LocalSearch; + vrptw::RouteExchange, + vrptw::RouteShift>; template class LocalSearch; + cvrp::RouteExchange, + cvrp::RouteShift>; } // namespace ls } // namespace vroom diff --git a/src/algorithms/local_search/local_search.h b/src/algorithms/local_search/local_search.h index 90309286e..34ce17213 100644 --- a/src/algorithms/local_search/local_search.h +++ b/src/algorithms/local_search/local_search.h @@ -32,7 +32,8 @@ template + class RouteExchange, + class RouteShift> class LocalSearch { private: const Input& _input; diff --git a/src/problems/cvrp/cvrp.cpp b/src/problems/cvrp/cvrp.cpp index b1c1d9e4e..00e104a17 100644 --- a/src/problems/cvrp/cvrp.cpp +++ b/src/problems/cvrp/cvrp.cpp @@ -26,6 +26,7 @@ All rights reserved (see LICENSE). #include "problems/cvrp/operators/relocate.h" #include "problems/cvrp/operators/reverse_two_opt.h" #include "problems/cvrp/operators/route_exchange.h" +#include "problems/cvrp/operators/route_shift.h" #include "problems/cvrp/operators/swap_star.h" #include "problems/cvrp/operators/two_opt.h" #include "problems/cvrp/operators/unassigned_exchange.h" @@ -54,7 +55,8 @@ using LocalSearch = ls::LocalSearch; + cvrp::RouteExchange, + cvrp::RouteShift>; } // namespace cvrp const std::vector CVRP::homogeneous_parameters = diff --git a/src/problems/vrptw/vrptw.cpp b/src/problems/vrptw/vrptw.cpp index 296e2b420..a22cf749e 100644 --- a/src/problems/vrptw/vrptw.cpp +++ b/src/problems/vrptw/vrptw.cpp @@ -25,6 +25,7 @@ All rights reserved (see LICENSE). #include "problems/vrptw/operators/relocate.h" #include "problems/vrptw/operators/reverse_two_opt.h" #include "problems/vrptw/operators/route_exchange.h" +#include "problems/vrptw/operators/route_shift.h" #include "problems/vrptw/operators/swap_star.h" #include "problems/vrptw/operators/two_opt.h" #include "problems/vrptw/operators/unassigned_exchange.h" @@ -53,7 +54,8 @@ using LocalSearch = ls::LocalSearch; + vrptw::RouteExchange, + vrptw::RouteShift>; } // namespace vrptw const std::vector VRPTW::homogeneous_parameters = diff --git a/src/structures/typedefs.h b/src/structures/typedefs.h index 5e6bcdcfa..04c5ea071 100644 --- a/src/structures/typedefs.h +++ b/src/structures/typedefs.h @@ -133,6 +133,7 @@ enum OperatorName { IntraTwoOpt, PDShift, RouteExchange, + RouteShift, MAX }; diff --git a/src/utils/helpers.h b/src/utils/helpers.h index 12d872ed3..e42e16e23 100644 --- a/src/utils/helpers.h +++ b/src/utils/helpers.h @@ -74,7 +74,8 @@ const std::array "IntraOrOpt", "IntraTwoOpt", "PDShift", - "RouteExchange"}); + "RouteExchange", + "RouteShift"}); inline void log_LS_operators( const std::vector>&