From c6bb60992bf053f5701696af89286d081a6b7443 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 15 Aug 2023 17:07:39 +0200 Subject: [PATCH 01/12] Comment out heuristic sort hack for now. --- src/problems/vrp.h | 73 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/problems/vrp.h b/src/problems/vrp.h index 0c1e6ed61..06b3496ed 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -90,42 +90,43 @@ class VRP { break; } - if (!_input.has_homogeneous_costs() and - p.heuristic != HEURISTIC::INIT_ROUTES and h_param.empty() and - p.sort == SORT::CAPACITY) { - // Worth trying another vehicle ordering scheme in - // heuristic. - std::vector other_sol; - - switch (p.heuristic) { - case HEURISTIC::INIT_ROUTES: - assert(false); - break; - case HEURISTIC::BASIC: - other_sol = heuristics::basic>(_input, - p.init, - p.regret_coeff, - SORT::COST); - break; - case HEURISTIC::DYNAMIC: - other_sol = heuristics::dynamic_vehicle_choice< - std::vector>(_input, p.init, p.regret_coeff, SORT::COST); - break; - } - - Eval eval; - Eval other_eval; - for (Index v = 0; v < _input.vehicles.size(); ++v) { - eval += utils::route_eval_for_vehicle(_input, - v, - solutions[rank][v].route); - other_eval += - utils::route_eval_for_vehicle(_input, v, other_sol[v].route); - } - if (other_eval < eval) { - solutions[rank] = std::move(other_sol); - } - } + // if (!_input.has_homogeneous_costs() and + // p.heuristic != HEURISTIC::INIT_ROUTES and h_param.empty() and + // p.sort == SORT::CAPACITY) { + // // Worth trying another vehicle ordering scheme in + // // heuristic. + // std::vector other_sol; + + // switch (p.heuristic) { + // case HEURISTIC::INIT_ROUTES: + // assert(false); + // break; + // case HEURISTIC::BASIC: + // other_sol = heuristics::basic>(_input, + // p.init, + // p.regret_coeff, + // SORT::COST); + // break; + // case HEURISTIC::DYNAMIC: + // other_sol = heuristics::dynamic_vehicle_choice< + // std::vector>(_input, p.init, p.regret_coeff, + // SORT::COST); + // break; + // } + + // Eval eval; + // Eval other_eval; + // for (Index v = 0; v < _input.vehicles.size(); ++v) { + // eval += utils::route_eval_for_vehicle(_input, + // v, + // solutions[rank][v].route); + // other_eval += + // utils::route_eval_for_vehicle(_input, v, other_sol[v].route); + // } + // if (other_eval < eval) { + // solutions[rank] = std::move(other_sol); + // } + // } } } catch (...) { ep_m.lock(); From 8f13677bf2eea6cb7c8043799dbeeea34d1e62bf Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 15 Aug 2023 17:10:49 +0200 Subject: [PATCH 02/12] Make heuristic calls void and pass solution to populate by reference. --- src/algorithms/heuristics/heuristics.cpp | 88 +++++++++++------------- src/algorithms/heuristics/heuristics.h | 22 +++--- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 8d30b62f3..66354cbbe 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -60,15 +60,13 @@ std::vector> get_jobs_vehicles_evals(const Input& input) { return evals; } -template -T basic(const Input& input, INIT init, double lambda, SORT sort) { +template +void basic(const Input& input, + std::vector& routes, + INIT init, + double lambda, + SORT sort) { const auto nb_vehicles = input.vehicles.size(); - T routes; - routes.reserve(nb_vehicles); - - for (Index v = 0; v < nb_vehicles; ++v) { - routes.emplace_back(input, v, input.zero_amount().size()); - } std::set unassigned; for (Index j = 0; j < input.jobs.size(); ++j) { @@ -460,22 +458,15 @@ T basic(const Input& input, INIT init, double lambda, SORT sort) { } } } - - return routes; } -template -T dynamic_vehicle_choice(const Input& input, - INIT init, - double lambda, - SORT sort) { +template +void dynamic_vehicle_choice(const Input& input, + std::vector& routes, + INIT init, + double lambda, + SORT sort) { const auto nb_vehicles = input.vehicles.size(); - T routes; - routes.reserve(nb_vehicles); - - for (Index v = 0; v < nb_vehicles; ++v) { - routes.emplace_back(input, v, input.zero_amount().size()); - } std::set unassigned; for (Index j = 0; j < input.jobs.size(); ++j) { @@ -896,18 +887,13 @@ T dynamic_vehicle_choice(const Input& input, } } } - - return routes; } -template T initial_routes(const Input& input) { - T routes; - routes.reserve(input.vehicles.size()); +template +void initial_routes(const Input& input, std::vector& routes) { for (Index v = 0; v < input.vehicles.size(); ++v) { - routes.emplace_back(input, v, input.zero_amount().size()); - const auto& vehicle = input.vehicles[v]; - auto& current_r = routes.back(); + auto& current_r = routes[v]; // Startup load is the sum of deliveries for (single) jobs. Amount single_jobs_deliveries(input.zero_amount()); @@ -1033,27 +1019,37 @@ template T initial_routes(const Input& input) { 0); } } - - return routes; } using RawSolution = std::vector; using TWSolution = std::vector; -template RawSolution -basic(const Input& input, INIT init, double lambda, SORT sort); - -template RawSolution -dynamic_vehicle_choice(const Input& input, INIT init, double lambda, SORT sort); - -template RawSolution initial_routes(const Input& input); - -template TWSolution -basic(const Input& input, INIT init, double lambda, SORT sort); - -template TWSolution -dynamic_vehicle_choice(const Input& input, INIT init, double lambda, SORT sort); - -template TWSolution initial_routes(const Input& input); +template void basic(const Input& input, + RawSolution& routes, + INIT init, + double lambda, + SORT sort); + +template void dynamic_vehicle_choice(const Input& input, + RawSolution& routes, + INIT init, + double lambda, + SORT sort); + +template void initial_routes(const Input& input, RawSolution& routes); + +template void basic(const Input& input, + TWSolution& routes, + INIT init, + double lambda, + SORT sort); + +template void dynamic_vehicle_choice(const Input& input, + TWSolution& routes, + INIT init, + double lambda, + SORT sort); + +template void initial_routes(const Input& input, TWSolution& routes); } // namespace vroom::heuristics diff --git a/src/algorithms/heuristics/heuristics.h b/src/algorithms/heuristics/heuristics.h index eaa9b06c9..f04e4e12b 100644 --- a/src/algorithms/heuristics/heuristics.h +++ b/src/algorithms/heuristics/heuristics.h @@ -15,18 +15,24 @@ All rights reserved (see LICENSE). namespace vroom::heuristics { // Implementation of a variant of the Solomon I1 heuristic. -template -T basic(const Input& input, INIT init, double lambda, SORT sort); +template +void basic(const Input& input, + std::vector& routes, + INIT init, + double lambda, + SORT sort); // Adjusting the above for situation with heterogeneous fleet. -template -T dynamic_vehicle_choice(const Input& input, - INIT init, - double lambda, - SORT sort); +template +void dynamic_vehicle_choice(const Input& input, + std::vector& routes, + INIT init, + double lambda, + SORT sort); // Populate routes with user-defined vehicle steps. -template T initial_routes(const Input& input); +template +void initial_routes(const Input& input, std::vector& routes); } // namespace vroom::heuristics From d20c20bba978d6cc90517d60b63a245454e438ef Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 15 Aug 2023 17:11:16 +0200 Subject: [PATCH 03/12] Create empty solutions at heuristic call site. --- src/problems/vrp.h | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/problems/vrp.h b/src/problems/vrp.h index 06b3496ed..e504c5a27 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -55,7 +55,14 @@ class VRP { } assert(nb_init_solutions <= parameters.size()); - std::vector> solutions(nb_init_solutions); + std::vector empty_sol; + empty_sol.reserve(_input.vehicles.size()); + + for (Index v = 0; v < _input.vehicles.size(); ++v) { + empty_sol.emplace_back(_input, v, _input.zero_amount().size()); + } + + std::vector> solutions(nb_init_solutions, empty_sol); // Split the heuristic parameters among threads. std::vector> @@ -74,19 +81,21 @@ class VRP { switch (p.heuristic) { case HEURISTIC::INIT_ROUTES: - solutions[rank] = - heuristics::initial_routes>(_input); + heuristics::initial_routes(_input, solutions[rank]); break; case HEURISTIC::BASIC: - solutions[rank] = - heuristics::basic>(_input, - p.init, - p.regret_coeff, - p.sort); + heuristics::basic(_input, + solutions[rank], + p.init, + p.regret_coeff, + p.sort); break; case HEURISTIC::DYNAMIC: - solutions[rank] = heuristics::dynamic_vehicle_choice< - std::vector>(_input, p.init, p.regret_coeff, p.sort); + heuristics::dynamic_vehicle_choice(_input, + solutions[rank], + p.init, + p.regret_coeff, + p.sort); break; } From db059579ffb3adcbd4d87a7042deda9d225bfde6 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 15 Aug 2023 17:14:37 +0200 Subject: [PATCH 04/12] Make sure we only operate on empty solutions for now. --- src/algorithms/heuristics/heuristics.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 66354cbbe..814b8a3aa 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -7,6 +7,7 @@ All rights reserved (see LICENSE). */ +#include #include #include "algorithms/heuristics/heuristics.h" @@ -66,6 +67,10 @@ void basic(const Input& input, INIT init, double lambda, SORT sort) { + assert(std::all_of(routes.cbegin(), routes.cend(), [](const auto& r) { + return r.empty(); + })); + const auto nb_vehicles = input.vehicles.size(); std::set unassigned; @@ -466,6 +471,10 @@ void dynamic_vehicle_choice(const Input& input, INIT init, double lambda, SORT sort) { + assert(std::all_of(routes.cbegin(), routes.cend(), [](const auto& r) { + return r.empty(); + })); + const auto nb_vehicles = input.vehicles.size(); std::set unassigned; @@ -891,6 +900,10 @@ void dynamic_vehicle_choice(const Input& input, template void initial_routes(const Input& input, std::vector& routes) { + assert(std::all_of(routes.cbegin(), routes.cend(), [](const auto& r) { + return r.empty(); + })); + for (Index v = 0; v < input.vehicles.size(); ++v) { const auto& vehicle = input.vehicles[v]; auto& current_r = routes[v]; From 2ecd66c2be465d873fdd0653528b836678fbb166 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 15 Aug 2023 17:44:35 +0200 Subject: [PATCH 05/12] Make heuristics return solution evaluation. --- src/algorithms/heuristics/heuristics.cpp | 130 +++++++++++++---------- src/algorithms/heuristics/heuristics.h | 5 +- src/problems/vrp.h | 22 ++-- 3 files changed, 89 insertions(+), 68 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 814b8a3aa..a84a608cc 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -62,7 +62,7 @@ std::vector> get_jobs_vehicles_evals(const Input& input) { } template -void basic(const Input& input, +Eval basic(const Input& input, std::vector& routes, INIT init, double lambda, @@ -71,6 +71,8 @@ void basic(const Input& input, return r.empty(); })); + Eval sol_eval; + const auto nb_vehicles = input.vehicles.size(); std::set unassigned; @@ -146,7 +148,7 @@ void basic(const Input& input, const auto& vehicle = input.vehicles[v_rank]; - Duration current_route_duration = 0; + Eval current_route_eval; if (init != INIT::NONE) { // Initialize current route with the "best" valid job. @@ -258,7 +260,7 @@ void basic(const Input& input, unassigned.erase(best_job_rank); unassigned.erase(best_job_rank + 1); } - current_route_duration += evals[best_job_rank][v_rank].duration; + current_route_eval += evals[best_job_rank][v_rank]; } } @@ -271,7 +273,7 @@ void basic(const Input& input, Index best_pickup_r = 0; Index best_delivery_r = 0; Amount best_modified_delivery = input.zero_amount(); - Duration best_duration_addition = 0; + Eval best_eval; for (const auto job_rank : unassigned) { if (!input.vehicle_ok_with_job(v_rank, job_rank)) { @@ -285,19 +287,19 @@ void basic(const Input& input, if (input.jobs[job_rank].type == JOB_TYPE::SINGLE and current_r.size() + 1 <= vehicle.max_tasks) { for (Index r = 0; r <= current_r.size(); ++r) { - const auto current_add = utils::addition_cost(input, - job_rank, - vehicle, - current_r.route, - r); + const auto current_eval = utils::addition_cost(input, + job_rank, + vehicle, + current_r.route, + r); double current_cost = - static_cast(current_add.cost) - + static_cast(current_eval.cost) - lambda * static_cast(regrets[v][job_rank]); if (current_cost < best_cost and - (vehicle.ok_for_travel_time(current_route_duration + - current_add.duration)) and + (vehicle.ok_for_travel_time(current_route_eval.duration + + current_eval.duration)) and current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -307,7 +309,7 @@ void basic(const Input& input, best_cost = current_cost; best_job_rank = job_rank; best_r = r; - best_duration_addition = current_add.duration; + best_eval = current_eval; } } } @@ -374,20 +376,20 @@ void basic(const Input& input, continue; } - Eval current_add; + Eval current_eval; if (pickup_r == delivery_r) { - current_add = utils::addition_cost(input, - job_rank, - vehicle, - current_r.route, - pickup_r, - pickup_r + 1); + current_eval = utils::addition_cost(input, + job_rank, + vehicle, + current_r.route, + pickup_r, + pickup_r + 1); } else { - current_add = p_add + d_adds[delivery_r]; + current_eval = p_add + d_adds[delivery_r]; } double current_cost = - current_add.cost - + current_eval.cost - lambda * static_cast(regrets[v][job_rank]); if (current_cost < best_cost) { @@ -395,8 +397,8 @@ void basic(const Input& input, // Update best cost depending on validity. bool valid = - (vehicle.ok_for_travel_time(current_route_duration + - current_add.duration)) && + (vehicle.ok_for_travel_time(current_route_eval.duration + + current_eval.duration)) && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -424,7 +426,7 @@ void basic(const Input& input, best_pickup_r = pickup_r; best_delivery_r = delivery_r; best_modified_delivery = modified_delivery; - best_duration_addition = current_add.duration; + best_eval = current_eval; } } } @@ -459,14 +461,21 @@ void basic(const Input& input, keep_going = true; } - current_route_duration += best_duration_addition; + current_route_eval += best_eval; } } + + if (!current_r.empty()) { + sol_eval += current_route_eval; + sol_eval += Eval(vehicle.fixed_cost(), 0); + } } + + return sol_eval; } template -void dynamic_vehicle_choice(const Input& input, +Eval dynamic_vehicle_choice(const Input& input, std::vector& routes, INIT init, double lambda, @@ -475,6 +484,8 @@ void dynamic_vehicle_choice(const Input& input, return r.empty(); })); + Eval sol_eval; + const auto nb_vehicles = input.vehicles.size(); std::set unassigned; @@ -577,7 +588,7 @@ void dynamic_vehicle_choice(const Input& input, const auto& vehicle = input.vehicles[v_rank]; auto& current_r = routes[v_rank]; - Duration current_route_duration = 0; + Eval current_route_eval; if (init != INIT::NONE) { // Initialize current route with the "best" valid job that is @@ -694,7 +705,7 @@ void dynamic_vehicle_choice(const Input& input, unassigned.erase(best_job_rank); unassigned.erase(best_job_rank + 1); } - current_route_duration += evals[best_job_rank][v_rank].duration; + current_route_eval += evals[best_job_rank][v_rank]; } } @@ -707,7 +718,7 @@ void dynamic_vehicle_choice(const Input& input, Index best_pickup_r = 0; Index best_delivery_r = 0; Amount best_modified_delivery = input.zero_amount(); - Duration best_duration_addition = 0; + Eval best_eval; for (const auto job_rank : unassigned) { if (!input.vehicle_ok_with_job(v_rank, job_rank)) { @@ -721,19 +732,19 @@ void dynamic_vehicle_choice(const Input& input, if (input.jobs[job_rank].type == JOB_TYPE::SINGLE and current_r.size() + 1 <= vehicle.max_tasks) { for (Index r = 0; r <= current_r.size(); ++r) { - const auto current_add = utils::addition_cost(input, - job_rank, - vehicle, - current_r.route, - r); + const auto current_eval = utils::addition_cost(input, + job_rank, + vehicle, + current_r.route, + r); double current_cost = - static_cast(current_add.cost) - + static_cast(current_eval.cost) - lambda * static_cast(regrets[job_rank]); if (current_cost < best_cost and - (vehicle.ok_for_travel_time(current_route_duration + - current_add.duration)) and + (vehicle.ok_for_travel_time(current_route_eval.duration + + current_eval.duration)) and current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -743,7 +754,7 @@ void dynamic_vehicle_choice(const Input& input, best_cost = current_cost; best_job_rank = job_rank; best_r = r; - best_duration_addition = current_add.duration; + best_eval = current_eval; } } } @@ -810,20 +821,20 @@ void dynamic_vehicle_choice(const Input& input, continue; } - Eval current_add; + Eval current_eval; if (pickup_r == delivery_r) { - current_add = utils::addition_cost(input, - job_rank, - vehicle, - current_r.route, - pickup_r, - pickup_r + 1); + current_eval = utils::addition_cost(input, + job_rank, + vehicle, + current_r.route, + pickup_r, + pickup_r + 1); } else { - current_add = p_add + d_adds[delivery_r]; + current_eval = p_add + d_adds[delivery_r]; } double current_cost = - current_add.cost - + current_eval.cost - lambda * static_cast(regrets[job_rank]); if (current_cost < best_cost) { @@ -831,8 +842,8 @@ void dynamic_vehicle_choice(const Input& input, // Update best cost depending on validity. bool valid = - (vehicle.ok_for_travel_time(current_route_duration + - current_add.duration)) && + (vehicle.ok_for_travel_time(current_route_eval.duration + + current_eval.duration)) && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -857,7 +868,7 @@ void dynamic_vehicle_choice(const Input& input, best_pickup_r = pickup_r; best_delivery_r = delivery_r; best_modified_delivery = modified_delivery; - best_duration_addition = current_add.duration; + best_eval = current_eval; } } } @@ -892,10 +903,17 @@ void dynamic_vehicle_choice(const Input& input, keep_going = true; } - current_route_duration += best_duration_addition; + current_route_eval += best_eval; } } + + if (!current_r.empty()) { + sol_eval += current_route_eval; + sol_eval += Eval(vehicle.fixed_cost(), 0); + } } + + return sol_eval; } template @@ -1037,13 +1055,13 @@ void initial_routes(const Input& input, std::vector& routes) { using RawSolution = std::vector; using TWSolution = std::vector; -template void basic(const Input& input, +template Eval basic(const Input& input, RawSolution& routes, INIT init, double lambda, SORT sort); -template void dynamic_vehicle_choice(const Input& input, +template Eval dynamic_vehicle_choice(const Input& input, RawSolution& routes, INIT init, double lambda, @@ -1051,13 +1069,13 @@ template void dynamic_vehicle_choice(const Input& input, template void initial_routes(const Input& input, RawSolution& routes); -template void basic(const Input& input, +template Eval basic(const Input& input, TWSolution& routes, INIT init, double lambda, SORT sort); -template void dynamic_vehicle_choice(const Input& input, +template Eval dynamic_vehicle_choice(const Input& input, TWSolution& routes, INIT init, double lambda, diff --git a/src/algorithms/heuristics/heuristics.h b/src/algorithms/heuristics/heuristics.h index f04e4e12b..c0f13cfb9 100644 --- a/src/algorithms/heuristics/heuristics.h +++ b/src/algorithms/heuristics/heuristics.h @@ -10,13 +10,14 @@ All rights reserved (see LICENSE). */ +#include "structures/vroom/eval.h" #include "structures/vroom/input/input.h" namespace vroom::heuristics { // Implementation of a variant of the Solomon I1 heuristic. template -void basic(const Input& input, +Eval basic(const Input& input, std::vector& routes, INIT init, double lambda, @@ -24,7 +25,7 @@ void basic(const Input& input, // Adjusting the above for situation with heterogeneous fleet. template -void dynamic_vehicle_choice(const Input& input, +Eval dynamic_vehicle_choice(const Input& input, std::vector& routes, INIT init, double lambda, diff --git a/src/problems/vrp.h b/src/problems/vrp.h index e504c5a27..4eb600ea7 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -16,6 +16,7 @@ All rights reserved (see LICENSE). #include "algorithms/heuristics/heuristics.h" #include "algorithms/local_search/local_search.h" +#include "structures/vroom/eval.h" #include "structures/vroom/input/input.h" #include "structures/vroom/solution/solution.h" @@ -79,23 +80,24 @@ class VRP { for (auto rank : param_ranks) { const auto& p = parameters[rank]; + Eval h_eval; switch (p.heuristic) { case HEURISTIC::INIT_ROUTES: heuristics::initial_routes(_input, solutions[rank]); break; case HEURISTIC::BASIC: - heuristics::basic(_input, - solutions[rank], - p.init, - p.regret_coeff, - p.sort); + h_eval = heuristics::basic(_input, + solutions[rank], + p.init, + p.regret_coeff, + p.sort); break; case HEURISTIC::DYNAMIC: - heuristics::dynamic_vehicle_choice(_input, - solutions[rank], - p.init, - p.regret_coeff, - p.sort); + h_eval = heuristics::dynamic_vehicle_choice(_input, + solutions[rank], + p.init, + p.regret_coeff, + p.sort); break; } From fb4b945d19bae6f88c5356239047ed897a85df56 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 15 Aug 2023 17:48:52 +0200 Subject: [PATCH 06/12] Adjust back heuristic sort hack. --- src/problems/vrp.h | 83 +++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/problems/vrp.h b/src/problems/vrp.h index 4eb600ea7..aedb12a2f 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -101,43 +101,52 @@ class VRP { break; } - // if (!_input.has_homogeneous_costs() and - // p.heuristic != HEURISTIC::INIT_ROUTES and h_param.empty() and - // p.sort == SORT::CAPACITY) { - // // Worth trying another vehicle ordering scheme in - // // heuristic. - // std::vector other_sol; - - // switch (p.heuristic) { - // case HEURISTIC::INIT_ROUTES: - // assert(false); - // break; - // case HEURISTIC::BASIC: - // other_sol = heuristics::basic>(_input, - // p.init, - // p.regret_coeff, - // SORT::COST); - // break; - // case HEURISTIC::DYNAMIC: - // other_sol = heuristics::dynamic_vehicle_choice< - // std::vector>(_input, p.init, p.regret_coeff, - // SORT::COST); - // break; - // } - - // Eval eval; - // Eval other_eval; - // for (Index v = 0; v < _input.vehicles.size(); ++v) { - // eval += utils::route_eval_for_vehicle(_input, - // v, - // solutions[rank][v].route); - // other_eval += - // utils::route_eval_for_vehicle(_input, v, other_sol[v].route); - // } - // if (other_eval < eval) { - // solutions[rank] = std::move(other_sol); - // } - // } + if (!_input.has_homogeneous_costs() and + p.heuristic != HEURISTIC::INIT_ROUTES and h_param.empty() and + p.sort == SORT::CAPACITY) { + // Worth trying another vehicle ordering scheme in + // heuristic. + std::vector other_sol = empty_sol; + + Eval h_other_eval; + switch (p.heuristic) { + case HEURISTIC::INIT_ROUTES: + assert(false); + break; + case HEURISTIC::BASIC: + h_other_eval = heuristics::basic(_input, + other_sol, + p.init, + p.regret_coeff, + SORT::COST); + break; + case HEURISTIC::DYNAMIC: + h_other_eval = + heuristics::dynamic_vehicle_choice(_input, + other_sol, + p.init, + p.regret_coeff, + SORT::COST); + break; + } + + // TODO remove unnecessary check. + Eval eval; + Eval other_eval; + for (Index v = 0; v < _input.vehicles.size(); ++v) { + eval += utils::route_eval_for_vehicle(_input, + v, + solutions[rank][v].route); + other_eval += + utils::route_eval_for_vehicle(_input, v, other_sol[v].route); + } + assert(eval == h_eval); + assert(other_eval == h_other_eval); + + if (h_other_eval < h_eval) { + solutions[rank] = std::move(other_sol); + } + } } } catch (...) { ep_m.lock(); From fc26b120ba64adf150759c2b13ab1342d66a0972 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 16 Aug 2023 10:15:58 +0200 Subject: [PATCH 07/12] No need to evaluate solution after heuristic anymore. --- src/problems/vrp.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/problems/vrp.h b/src/problems/vrp.h index aedb12a2f..bb7c2838c 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -130,19 +130,6 @@ class VRP { break; } - // TODO remove unnecessary check. - Eval eval; - Eval other_eval; - for (Index v = 0; v < _input.vehicles.size(); ++v) { - eval += utils::route_eval_for_vehicle(_input, - v, - solutions[rank][v].route); - other_eval += - utils::route_eval_for_vehicle(_input, v, other_sol[v].route); - } - assert(eval == h_eval); - assert(other_eval == h_other_eval); - if (h_other_eval < h_eval) { solutions[rank] = std::move(other_sol); } From 98cd6a34747251c575e5969c51db6a97377dc373 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 16 Aug 2023 15:55:39 +0200 Subject: [PATCH 08/12] Compute all jobs x vehicles evals once and for all in Input. --- src/algorithms/heuristics/heuristics.cpp | 50 +----------------------- src/structures/vroom/input/input.cpp | 48 +++++++++++++++++++++++ src/structures/vroom/input/input.h | 6 +++ 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index a84a608cc..9ae39e35f 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -15,52 +15,6 @@ All rights reserved (see LICENSE). namespace vroom::heuristics { -std::vector> get_jobs_vehicles_evals(const Input& input) { - // For a single job j, evals[j][v] evaluates fetching job j in an - // empty route from vehicle at rank v. For a pickup job j, - // evals[j][v] evaluates fetching job j **and** associated delivery - // in an empty route from vehicle at rank v. - std::vector> evals(input.jobs.size(), - std::vector( - input.vehicles.size())); - for (std::size_t j = 0; j < input.jobs.size(); ++j) { - Index j_index = input.jobs[j].index(); - bool is_pickup = (input.jobs[j].type == JOB_TYPE::PICKUP); - - Index last_job_index = j_index; - if (is_pickup) { - assert((j + 1 < input.jobs.size()) and - (input.jobs[j + 1].type == JOB_TYPE::DELIVERY)); - last_job_index = input.jobs[j + 1].index(); - } - - for (std::size_t v = 0; v < input.vehicles.size(); ++v) { - const auto& vehicle = input.vehicles[v]; - Eval current_eval = - is_pickup ? vehicle.eval(j_index, last_job_index) : Eval(); - if (vehicle.has_start()) { - current_eval += vehicle.eval(vehicle.start.value().index(), j_index); - } - if (vehicle.has_end()) { - current_eval += - vehicle.eval(last_job_index, vehicle.end.value().index()); - } - evals[j][v] = current_eval; - if (is_pickup) { - // Assign same eval to delivery. - evals[j + 1][v] = current_eval; - } - } - - if (is_pickup) { - // Skip delivery. - ++j; - } - } - - return evals; -} - template Eval basic(const Input& input, std::vector& routes, @@ -119,7 +73,7 @@ Eval basic(const Input& input, break; } - auto evals = get_jobs_vehicles_evals(input); + const auto& evals = input.jobs_vehicles_evals(); // regrets[v][j] holds the min cost for reaching job j in an empty // route across all remaining vehicles **after** vehicle at rank v @@ -496,7 +450,7 @@ Eval dynamic_vehicle_choice(const Input& input, std::vector vehicles_ranks(nb_vehicles); std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0); - auto evals = get_jobs_vehicles_evals(input); + const auto& evals = input.jobs_vehicles_evals(); while (!vehicles_ranks.empty() and !unassigned.empty()) { // For any unassigned job at j, jobs_min_costs[j] diff --git a/src/structures/vroom/input/input.cpp b/src/structures/vroom/input/input.cpp index 14676de54..d1a356b78 100644 --- a/src/structures/vroom/input/input.cpp +++ b/src/structures/vroom/input/input.cpp @@ -711,6 +711,52 @@ void Input::set_vehicles_max_tasks() { } } +void Input::set_jobs_vehicles_evals() { + // For a single job j, evals[j][v] evaluates fetching job j in an + // empty route from vehicle at rank v. For a pickup job j, + // evals[j][v] evaluates fetching job j **and** associated delivery + // in an empty route from vehicle at rank v. + _jobs_vehicles_evals = + std::vector>(jobs.size(), + std::vector(vehicles.size())); + + for (std::size_t j = 0; j < jobs.size(); ++j) { + Index j_index = jobs[j].index(); + bool is_pickup = (jobs[j].type == JOB_TYPE::PICKUP); + + Index last_job_index = j_index; + if (is_pickup) { + assert((j + 1 < jobs.size()) and + (jobs[j + 1].type == JOB_TYPE::DELIVERY)); + last_job_index = jobs[j + 1].index(); + } + + for (std::size_t v = 0; v < vehicles.size(); ++v) { + const auto& vehicle = vehicles[v]; + auto& current_eval = _jobs_vehicles_evals[j][v]; + + current_eval = is_pickup ? vehicle.eval(j_index, last_job_index) : Eval(); + if (vehicle.has_start()) { + current_eval += vehicle.eval(vehicle.start.value().index(), j_index); + } + if (vehicle.has_end()) { + current_eval += + vehicle.eval(last_job_index, vehicle.end.value().index()); + } + + if (is_pickup) { + // Assign same eval to delivery. + _jobs_vehicles_evals[j + 1][v] = current_eval; + } + } + + if (is_pickup) { + // Skip delivery. + ++j; + } + } +} + void Input::set_vehicle_steps_ranks() { std::unordered_set planned_job_ids; std::unordered_set planned_pickup_ids; @@ -1002,6 +1048,8 @@ Solution Input::solve(unsigned exploration_level, set_extra_compatibility(); set_vehicles_compatibility(); + set_jobs_vehicles_evals(); + // Add implicit max_tasks constraints derived from capacity and // TW. Note: rely on set_extra_compatibility being run previously to // catch wrong breaks definition. diff --git a/src/structures/vroom/input/input.h b/src/structures/vroom/input/input.h index 705dbebd0..b666425c7 100644 --- a/src/structures/vroom/input/input.h +++ b/src/structures/vroom/input/input.h @@ -61,6 +61,7 @@ class Input { std::unordered_set _matrices_used_index; Index _max_matrices_used_index{0}; bool _all_locations_have_coords{true}; + std::vector> _jobs_vehicles_evals; unsigned _amount_size{0}; Amount _zero; @@ -79,6 +80,7 @@ class Input { void set_vehicles_compatibility(); void set_vehicles_costs(); void set_vehicles_max_tasks(); + void set_jobs_vehicles_evals(); void set_vehicle_steps_ranks(); void set_matrices(unsigned nb_thread); @@ -133,6 +135,10 @@ class Input { return _cost_upper_bound; } + const std::vector>& jobs_vehicles_evals() const { + return _jobs_vehicles_evals; + } + bool has_homogeneous_locations() const; bool has_homogeneous_profiles() const; From 7203d93b497ca4cea0e8e5de828f4850fd540789 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 16 Aug 2023 17:16:49 +0200 Subject: [PATCH 09/12] Pass vehicles to consider as parameter of the heuristics. --- src/algorithms/heuristics/heuristics.cpp | 51 +++++++++++++++--------- src/algorithms/heuristics/heuristics.h | 10 +++-- src/problems/vrp.h | 25 +++++++++--- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 9ae39e35f..bd2bc5f4e 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -15,9 +15,11 @@ All rights reserved (see LICENSE). namespace vroom::heuristics { -template +template Eval basic(const Input& input, std::vector& routes, + const InputIterator vehicles_begin, + const InputIterator vehicles_end, INIT init, double lambda, SORT sort) { @@ -27,17 +29,16 @@ Eval basic(const Input& input, Eval sol_eval; - const auto nb_vehicles = input.vehicles.size(); + const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); std::set unassigned; for (Index j = 0; j < input.jobs.size(); ++j) { unassigned.insert(j); } - // One level of indirection to allow easy ordering of the vehicles - // within the heuristic. + // Perform heuristic ordering of the vehicles on a copy. std::vector vehicles_ranks(nb_vehicles); - std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0); + std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); switch (sort) { case SORT::CAPACITY: @@ -428,9 +429,11 @@ Eval basic(const Input& input, return sol_eval; } -template +template Eval dynamic_vehicle_choice(const Input& input, std::vector& routes, + const InputIterator vehicles_begin, + const InputIterator vehicles_end, INIT init, double lambda, SORT sort) { @@ -440,15 +443,17 @@ Eval dynamic_vehicle_choice(const Input& input, Eval sol_eval; - const auto nb_vehicles = input.vehicles.size(); + const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); std::set unassigned; for (Index j = 0; j < input.jobs.size(); ++j) { unassigned.insert(j); } + // Work on a copy of the vehicles ranks from which we erase values + // each time a route is completed. std::vector vehicles_ranks(nb_vehicles); - std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0); + std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); const auto& evals = input.jobs_vehicles_evals(); @@ -1011,29 +1016,39 @@ using TWSolution = std::vector; template Eval basic(const Input& input, RawSolution& routes, + const std::vector::const_iterator vehicles_begin, + const std::vector::const_iterator vehicles_end, INIT init, double lambda, SORT sort); -template Eval dynamic_vehicle_choice(const Input& input, - RawSolution& routes, - INIT init, - double lambda, - SORT sort); +template Eval +dynamic_vehicle_choice(const Input& input, + RawSolution& routes, + const std::vector::const_iterator vehicles_begin, + const std::vector::const_iterator vehicles_end, + INIT init, + double lambda, + SORT sort); template void initial_routes(const Input& input, RawSolution& routes); template Eval basic(const Input& input, TWSolution& routes, + const std::vector::const_iterator vehicles_begin, + const std::vector::const_iterator vehicles_end, INIT init, double lambda, SORT sort); -template Eval dynamic_vehicle_choice(const Input& input, - TWSolution& routes, - INIT init, - double lambda, - SORT sort); +template Eval +dynamic_vehicle_choice(const Input& input, + TWSolution& routes, + const std::vector::const_iterator vehicles_begin, + const std::vector::const_iterator vehicles_end, + INIT init, + double lambda, + SORT sort); template void initial_routes(const Input& input, TWSolution& routes); diff --git a/src/algorithms/heuristics/heuristics.h b/src/algorithms/heuristics/heuristics.h index c0f13cfb9..595bc073f 100644 --- a/src/algorithms/heuristics/heuristics.h +++ b/src/algorithms/heuristics/heuristics.h @@ -16,17 +16,21 @@ All rights reserved (see LICENSE). namespace vroom::heuristics { // Implementation of a variant of the Solomon I1 heuristic. -template +template Eval basic(const Input& input, std::vector& routes, + const InputIterator vehicles_begin, + const InputIterator vehicles_end, INIT init, double lambda, SORT sort); -// Adjusting the above for situation with heterogeneous fleet. -template +// Adjusting the above for situations with heterogeneous fleet. +template Eval dynamic_vehicle_choice(const Input& input, std::vector& routes, + const InputIterator vehicles_begin, + const InputIterator vehicles_end, INIT init, double lambda, SORT sort); diff --git a/src/problems/vrp.h b/src/problems/vrp.h index bb7c2838c..efe536263 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -56,6 +56,7 @@ class VRP { } assert(nb_init_solutions <= parameters.size()); + // Build empty solutions to be filled by heuristics. std::vector empty_sol; empty_sol.reserve(_input.vehicles.size()); @@ -65,6 +66,10 @@ class VRP { std::vector> solutions(nb_init_solutions, empty_sol); + // Heuristics operate on all vehicles. + std::vector vehicles_ranks(_input.vehicles.size()); + std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0); + // Split the heuristic parameters among threads. std::vector> thread_ranks(nb_threads, std::vector()); @@ -88,16 +93,21 @@ class VRP { case HEURISTIC::BASIC: h_eval = heuristics::basic(_input, solutions[rank], + vehicles_ranks.cbegin(), + vehicles_ranks.cend(), p.init, p.regret_coeff, p.sort); break; case HEURISTIC::DYNAMIC: - h_eval = heuristics::dynamic_vehicle_choice(_input, - solutions[rank], - p.init, - p.regret_coeff, - p.sort); + h_eval = + heuristics::dynamic_vehicle_choice(_input, + solutions[rank], + vehicles_ranks.cbegin(), + vehicles_ranks.cend(), + p.init, + p.regret_coeff, + p.sort); break; } @@ -116,6 +126,8 @@ class VRP { case HEURISTIC::BASIC: h_other_eval = heuristics::basic(_input, other_sol, + vehicles_ranks.cbegin(), + vehicles_ranks.cend(), p.init, p.regret_coeff, SORT::COST); @@ -124,6 +136,9 @@ class VRP { h_other_eval = heuristics::dynamic_vehicle_choice(_input, other_sol, + vehicles_ranks + .cbegin(), + vehicles_ranks.cend(), p.init, p.regret_coeff, SORT::COST); From 23d815313aac9a726c0229ac39f13e1794c8bdd1 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 17 Aug 2023 09:35:48 +0200 Subject: [PATCH 10/12] Pass jobs to consider as parameter of the heuristics. --- src/algorithms/heuristics/heuristics.cpp | 150 +++++++++++++---------- src/algorithms/heuristics/heuristics.h | 4 + src/problems/vrp.h | 12 ++ 3 files changed, 98 insertions(+), 68 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index bd2bc5f4e..9de607472 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -18,6 +18,8 @@ namespace vroom::heuristics { template Eval basic(const Input& input, std::vector& routes, + const InputIterator jobs_begin, + const InputIterator jobs_end, const InputIterator vehicles_begin, const InputIterator vehicles_end, INIT init, @@ -29,14 +31,14 @@ Eval basic(const Input& input, Eval sol_eval; - const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); - + // Consider all jobs as unassigned at first. std::set unassigned; - for (Index j = 0; j < input.jobs.size(); ++j) { - unassigned.insert(j); - } + std::copy(jobs_begin, + jobs_end, + std::inserter(unassigned, unassigned.begin())); // Perform heuristic ordering of the vehicles on a copy. + const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); std::vector vehicles_ranks(nb_vehicles); std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); @@ -84,14 +86,14 @@ Eval basic(const Input& input, // Use own cost for last vehicle regret values. auto& last_regrets = regrets.back(); - for (Index j = 0; j < input.jobs.size(); ++j) { + for (const auto j : unassigned) { last_regrets[j] = evals[j][vehicles_ranks.back()].cost; } for (Index rev_v = 0; rev_v < nb_vehicles - 1; ++rev_v) { // Going trough vehicles backward from second to last. const auto v = nb_vehicles - 2 - rev_v; - for (Index j = 0; j < input.jobs.size(); ++j) { + for (const auto j : unassigned) { regrets[v][j] = std::min(regrets[v + 1][j], (evals[j][vehicles_ranks[v + 1]]).cost); } @@ -115,12 +117,14 @@ Eval basic(const Input& input, Duration earliest_deadline = std::numeric_limits::max(); Index best_job_rank = 0; for (const auto job_rank : unassigned) { + const auto& current_job = input.jobs[job_rank]; + if (!input.vehicle_ok_with_job(v_rank, job_rank) or - input.jobs[job_rank].type == JOB_TYPE::DELIVERY) { + current_job.type == JOB_TYPE::DELIVERY) { continue; } - bool is_pickup = (input.jobs[job_rank].type == JOB_TYPE::PICKUP); + bool is_pickup = (current_job.type == JOB_TYPE::PICKUP); if (current_r.size() + (is_pickup ? 2 : 1) > vehicle.max_tasks) { continue; @@ -129,13 +133,13 @@ Eval basic(const Input& input, bool try_validity = false; if (init == INIT::HIGHER_AMOUNT) { - try_validity |= (higher_amount << input.jobs[job_rank].pickup or - higher_amount << input.jobs[job_rank].delivery); + try_validity |= (higher_amount << current_job.pickup or + higher_amount << current_job.delivery); } if (init == INIT::EARLIEST_DEADLINE) { Duration current_deadline = (is_pickup) ? input.jobs[job_rank + 1].tws.back().end - : input.jobs[job_rank].tws.back().end; + : current_job.tws.back().end; try_validity |= (current_deadline < earliest_deadline); } if (init == INIT::FURTHEST) { @@ -151,11 +155,10 @@ Eval basic(const Input& input, bool is_valid = (vehicle.ok_for_travel_time(evals[job_rank][v_rank].duration)) && - current_r - .is_valid_addition_for_capacity(input, - input.jobs[job_rank].pickup, - input.jobs[job_rank].delivery, - 0); + current_r.is_valid_addition_for_capacity(input, + current_job.pickup, + current_job.delivery, + 0); if (is_pickup) { std::vector p_d({job_rank, static_cast(job_rank + 1)}); is_valid = @@ -166,7 +169,7 @@ Eval basic(const Input& input, 0, 0); } else { - assert(input.jobs[job_rank].type == JOB_TYPE::SINGLE); + assert(current_job.type == JOB_TYPE::SINGLE); is_valid = is_valid && current_r.is_valid_addition_for_tw(input, job_rank, 0); } @@ -180,17 +183,17 @@ Eval basic(const Input& input, assert(false); break; case INIT::HIGHER_AMOUNT: - if (higher_amount << input.jobs[job_rank].pickup) { - higher_amount = input.jobs[job_rank].pickup; + if (higher_amount << current_job.pickup) { + higher_amount = current_job.pickup; } - if (higher_amount << input.jobs[job_rank].delivery) { - higher_amount = input.jobs[job_rank].delivery; + if (higher_amount << current_job.delivery) { + higher_amount = current_job.delivery; } break; case INIT::EARLIEST_DEADLINE: earliest_deadline = (is_pickup) ? input.jobs[job_rank + 1].tws.back().end - : input.jobs[job_rank].tws.back().end; + : current_job.tws.back().end; break; case INIT::FURTHEST: furthest_cost = evals[job_rank][v_rank].cost; @@ -235,11 +238,13 @@ Eval basic(const Input& input, continue; } - if (input.jobs[job_rank].type == JOB_TYPE::DELIVERY) { + const auto& current_job = input.jobs[job_rank]; + + if (current_job.type == JOB_TYPE::DELIVERY) { continue; } - if (input.jobs[job_rank].type == JOB_TYPE::SINGLE and + if (current_job.type == JOB_TYPE::SINGLE and current_r.size() + 1 <= vehicle.max_tasks) { for (Index r = 0; r <= current_r.size(); ++r) { const auto current_eval = utils::addition_cost(input, @@ -255,11 +260,10 @@ Eval basic(const Input& input, if (current_cost < best_cost and (vehicle.ok_for_travel_time(current_route_eval.duration + current_eval.duration)) and - current_r - .is_valid_addition_for_capacity(input, - input.jobs[job_rank].pickup, - input.jobs[job_rank].delivery, - r) and + current_r.is_valid_addition_for_capacity(input, + current_job.pickup, + current_job.delivery, + r) and current_r.is_valid_addition_for_tw(input, job_rank, r)) { best_cost = current_cost; best_job_rank = job_rank; @@ -269,7 +273,7 @@ Eval basic(const Input& input, } } - if (input.jobs[job_rank].type == JOB_TYPE::PICKUP and + if (current_job.type == JOB_TYPE::PICKUP and current_r.size() + 2 <= vehicle.max_tasks) { // Pre-compute cost of addition for matching delivery. std::vector d_adds(current_r.route.size() + 1); @@ -296,10 +300,9 @@ Eval basic(const Input& input, current_r.route, pickup_r); - if (!current_r - .is_valid_addition_for_load(input, - input.jobs[job_rank].pickup, - pickup_r) or + if (!current_r.is_valid_addition_for_load(input, + current_job.pickup, + pickup_r) or !current_r .is_valid_addition_for_tw_without_max_load(input, job_rank, @@ -432,6 +435,8 @@ Eval basic(const Input& input, template Eval dynamic_vehicle_choice(const Input& input, std::vector& routes, + const InputIterator jobs_begin, + const InputIterator jobs_end, const InputIterator vehicles_begin, const InputIterator vehicles_end, INIT init, @@ -443,15 +448,15 @@ Eval dynamic_vehicle_choice(const Input& input, Eval sol_eval; - const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); - + // Consider all jobs as unassigned at first. std::set unassigned; - for (Index j = 0; j < input.jobs.size(); ++j) { - unassigned.insert(j); - } + std::copy(jobs_begin, + jobs_end, + std::inserter(unassigned, unassigned.begin())); // Work on a copy of the vehicles ranks from which we erase values // each time a route is completed. + const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); std::vector vehicles_ranks(nb_vehicles); std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); @@ -561,14 +566,16 @@ Eval dynamic_vehicle_choice(const Input& input, Duration earliest_deadline = std::numeric_limits::max(); Index best_job_rank = 0; for (const auto job_rank : unassigned) { + const auto& current_job = input.jobs[job_rank]; + if (jobs_min_costs[job_rank] < evals[job_rank][v_rank].cost or // One of the remaining vehicles is closest to that job. !input.vehicle_ok_with_job(v_rank, job_rank) or - input.jobs[job_rank].type == JOB_TYPE::DELIVERY) { + current_job.type == JOB_TYPE::DELIVERY) { continue; } - bool is_pickup = (input.jobs[job_rank].type == JOB_TYPE::PICKUP); + bool is_pickup = (current_job.type == JOB_TYPE::PICKUP); if (current_r.size() + (is_pickup ? 2 : 1) > vehicle.max_tasks) { continue; @@ -577,13 +584,13 @@ Eval dynamic_vehicle_choice(const Input& input, bool try_validity = false; if (init == INIT::HIGHER_AMOUNT) { - try_validity |= (higher_amount << input.jobs[job_rank].pickup or - higher_amount << input.jobs[job_rank].delivery); + try_validity |= (higher_amount << current_job.pickup or + higher_amount << current_job.delivery); } if (init == INIT::EARLIEST_DEADLINE) { Duration current_deadline = (is_pickup) ? input.jobs[job_rank + 1].tws.back().end - : input.jobs[job_rank].tws.back().end; + : current_job.tws.back().end; try_validity |= (current_deadline < earliest_deadline); } if (init == INIT::FURTHEST) { @@ -599,11 +606,10 @@ Eval dynamic_vehicle_choice(const Input& input, bool is_valid = (vehicle.ok_for_travel_time(evals[job_rank][v_rank].duration)) && - current_r - .is_valid_addition_for_capacity(input, - input.jobs[job_rank].pickup, - input.jobs[job_rank].delivery, - 0); + current_r.is_valid_addition_for_capacity(input, + current_job.pickup, + current_job.delivery, + 0); if (is_pickup) { std::vector p_d({job_rank, static_cast(job_rank + 1)}); @@ -615,7 +621,7 @@ Eval dynamic_vehicle_choice(const Input& input, 0, 0); } else { - assert(input.jobs[job_rank].type == JOB_TYPE::SINGLE); + assert(current_job.type == JOB_TYPE::SINGLE); is_valid = is_valid && current_r.is_valid_addition_for_tw(input, job_rank, 0); } @@ -629,17 +635,17 @@ Eval dynamic_vehicle_choice(const Input& input, assert(false); break; case INIT::HIGHER_AMOUNT: - if (higher_amount << input.jobs[job_rank].pickup) { - higher_amount = input.jobs[job_rank].pickup; + if (higher_amount << current_job.pickup) { + higher_amount = current_job.pickup; } - if (higher_amount << input.jobs[job_rank].delivery) { - higher_amount = input.jobs[job_rank].delivery; + if (higher_amount << current_job.delivery) { + higher_amount = current_job.delivery; } break; case INIT::EARLIEST_DEADLINE: earliest_deadline = (is_pickup) ? input.jobs[job_rank + 1].tws.back().end - : input.jobs[job_rank].tws.back().end; + : current_job.tws.back().end; break; case INIT::FURTHEST: furthest_cost = evals[job_rank][v_rank].cost; @@ -684,11 +690,13 @@ Eval dynamic_vehicle_choice(const Input& input, continue; } - if (input.jobs[job_rank].type == JOB_TYPE::DELIVERY) { + const auto& current_job = input.jobs[job_rank]; + + if (current_job.type == JOB_TYPE::DELIVERY) { continue; } - if (input.jobs[job_rank].type == JOB_TYPE::SINGLE and + if (current_job.type == JOB_TYPE::SINGLE and current_r.size() + 1 <= vehicle.max_tasks) { for (Index r = 0; r <= current_r.size(); ++r) { const auto current_eval = utils::addition_cost(input, @@ -704,11 +712,10 @@ Eval dynamic_vehicle_choice(const Input& input, if (current_cost < best_cost and (vehicle.ok_for_travel_time(current_route_eval.duration + current_eval.duration)) and - current_r - .is_valid_addition_for_capacity(input, - input.jobs[job_rank].pickup, - input.jobs[job_rank].delivery, - r) and + current_r.is_valid_addition_for_capacity(input, + current_job.pickup, + current_job.delivery, + r) and current_r.is_valid_addition_for_tw(input, job_rank, r)) { best_cost = current_cost; best_job_rank = job_rank; @@ -718,7 +725,7 @@ Eval dynamic_vehicle_choice(const Input& input, } } - if (input.jobs[job_rank].type == JOB_TYPE::PICKUP and + if (current_job.type == JOB_TYPE::PICKUP and current_r.size() + 2 <= vehicle.max_tasks) { // Pre-compute cost of addition for matching delivery. std::vector d_adds(current_r.route.size() + 1); @@ -745,10 +752,9 @@ Eval dynamic_vehicle_choice(const Input& input, current_r.route, pickup_r); - if (!current_r - .is_valid_addition_for_load(input, - input.jobs[job_rank].pickup, - pickup_r) or + if (!current_r.is_valid_addition_for_load(input, + current_job.pickup, + pickup_r) or !current_r .is_valid_addition_for_tw_without_max_load(input, job_rank, @@ -1016,6 +1022,8 @@ using TWSolution = std::vector; template Eval basic(const Input& input, RawSolution& routes, + const std::vector::const_iterator jobs_begin, + const std::vector::const_iterator jobs_end, const std::vector::const_iterator vehicles_begin, const std::vector::const_iterator vehicles_end, INIT init, @@ -1025,6 +1033,8 @@ template Eval basic(const Input& input, template Eval dynamic_vehicle_choice(const Input& input, RawSolution& routes, + const std::vector::const_iterator jobs_begin, + const std::vector::const_iterator jobs_end, const std::vector::const_iterator vehicles_begin, const std::vector::const_iterator vehicles_end, INIT init, @@ -1035,6 +1045,8 @@ template void initial_routes(const Input& input, RawSolution& routes); template Eval basic(const Input& input, TWSolution& routes, + const std::vector::const_iterator jobs_begin, + const std::vector::const_iterator jobs_end, const std::vector::const_iterator vehicles_begin, const std::vector::const_iterator vehicles_end, INIT init, @@ -1044,6 +1056,8 @@ template Eval basic(const Input& input, template Eval dynamic_vehicle_choice(const Input& input, TWSolution& routes, + const std::vector::const_iterator jobs_begin, + const std::vector::const_iterator jobs_end, const std::vector::const_iterator vehicles_begin, const std::vector::const_iterator vehicles_end, INIT init, diff --git a/src/algorithms/heuristics/heuristics.h b/src/algorithms/heuristics/heuristics.h index 595bc073f..a02c33046 100644 --- a/src/algorithms/heuristics/heuristics.h +++ b/src/algorithms/heuristics/heuristics.h @@ -19,6 +19,8 @@ namespace vroom::heuristics { template Eval basic(const Input& input, std::vector& routes, + const InputIterator jobs_begin, + const InputIterator jobs_end, const InputIterator vehicles_begin, const InputIterator vehicles_end, INIT init, @@ -29,6 +31,8 @@ Eval basic(const Input& input, template Eval dynamic_vehicle_choice(const Input& input, std::vector& routes, + const InputIterator jobs_begin, + const InputIterator jobs_end, const InputIterator vehicles_begin, const InputIterator vehicles_end, INIT init, diff --git a/src/problems/vrp.h b/src/problems/vrp.h index efe536263..f9a572e3f 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -66,6 +66,10 @@ class VRP { std::vector> solutions(nb_init_solutions, empty_sol); + // Heuristics operate on all jobs. + std::vector jobs_ranks(_input.jobs.size()); + std::iota(jobs_ranks.begin(), jobs_ranks.end(), 0); + // Heuristics operate on all vehicles. std::vector vehicles_ranks(_input.vehicles.size()); std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0); @@ -93,6 +97,8 @@ class VRP { case HEURISTIC::BASIC: h_eval = heuristics::basic(_input, solutions[rank], + jobs_ranks.cbegin(), + jobs_ranks.cend(), vehicles_ranks.cbegin(), vehicles_ranks.cend(), p.init, @@ -103,6 +109,8 @@ class VRP { h_eval = heuristics::dynamic_vehicle_choice(_input, solutions[rank], + jobs_ranks.cbegin(), + jobs_ranks.cend(), vehicles_ranks.cbegin(), vehicles_ranks.cend(), p.init, @@ -126,6 +134,8 @@ class VRP { case HEURISTIC::BASIC: h_other_eval = heuristics::basic(_input, other_sol, + jobs_ranks.cbegin(), + jobs_ranks.cend(), vehicles_ranks.cbegin(), vehicles_ranks.cend(), p.init, @@ -136,6 +146,8 @@ class VRP { h_other_eval = heuristics::dynamic_vehicle_choice(_input, other_sol, + jobs_ranks.cbegin(), + jobs_ranks.cend(), vehicles_ranks .cbegin(), vehicles_ranks.cend(), From f0b699d5ec09b1b58c320442391a71f852f7ef8e Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 17 Aug 2023 10:13:35 +0200 Subject: [PATCH 11/12] Fix vehicles vector creation. --- src/algorithms/heuristics/heuristics.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 9de607472..ecae823cb 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -39,7 +39,8 @@ Eval basic(const Input& input, // Perform heuristic ordering of the vehicles on a copy. const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); - std::vector vehicles_ranks(nb_vehicles); + std::vector vehicles_ranks; + vehicles_ranks.reserve(nb_vehicles); std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); switch (sort) { @@ -457,7 +458,8 @@ Eval dynamic_vehicle_choice(const Input& input, // Work on a copy of the vehicles ranks from which we erase values // each time a route is completed. const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); - std::vector vehicles_ranks(nb_vehicles); + std::vector vehicles_ranks; + vehicles_ranks.reserve(nb_vehicles); std::copy(vehicles_begin, vehicles_end, std::back_inserter(vehicles_ranks)); const auto& evals = input.jobs_vehicles_evals(); From 3be8f5863292b47d5f0c1f45a37bd38815ad25a7 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 17 Aug 2023 11:16:53 +0200 Subject: [PATCH 12/12] Add changelog entry. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 157c0d881..baef07dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - `Eval::operator<` sorts on cost, then duration (#914) - Improved `vrptw::PDShift` implementation (#852) - Reserve `vector` capacity whenever possible (#915) +- Refactor heuristics to be able to operate on a subset of jobs and vehicles (#837) ### Fixed