From ecf516b09710970a7d7feaa60c68cf5ea837037d Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 29 Oct 2024 15:01:53 +0100 Subject: [PATCH 01/12] Add getter for Input::_has_initial_route. --- src/structures/vroom/input/input.cpp | 4 ++++ src/structures/vroom/input/input.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/structures/vroom/input/input.cpp b/src/structures/vroom/input/input.cpp index dd3267c8a..e3ac562be 100644 --- a/src/structures/vroom/input/input.cpp +++ b/src/structures/vroom/input/input.cpp @@ -433,6 +433,10 @@ bool Input::has_homogeneous_costs() const { return _homogeneous_costs; } +bool Input::has_initial_routes() const { + return _has_initial_routes; +} + bool Input::vehicle_ok_with_vehicle(Index v1_index, Index v2_index) const { return _vehicle_to_vehicle_compatibility[v1_index][v2_index]; } diff --git a/src/structures/vroom/input/input.h b/src/structures/vroom/input/input.h index ae2519f64..fd320bd36 100644 --- a/src/structures/vroom/input/input.h +++ b/src/structures/vroom/input/input.h @@ -173,6 +173,8 @@ class Input { bool has_homogeneous_costs() const; + bool has_initial_routes() const; + bool vehicle_ok_with_job(size_t v_index, size_t j_index) const { return static_cast(_vehicle_to_job_compatibility[v_index][j_index]); } From f7658f79f97acc63e944f80ba05b008a616dbc70 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 29 Oct 2024 15:12:11 +0100 Subject: [PATCH 02/12] Simplify the logic for populating initial solutions. --- src/algorithms/heuristics/heuristics.cpp | 6 +++--- src/algorithms/heuristics/heuristics.h | 2 +- src/problems/vrp.h | 26 +++++++++++------------- src/structures/typedefs.h | 11 +--------- src/structures/vroom/input/input.cpp | 9 ++------ src/utils/output_json.cpp | 3 --- 6 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 5f603660c..7c18a8277 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -866,7 +866,7 @@ Eval dynamic_vehicle_choice(const Input& input, } template -void initial_routes(const Input& input, std::vector& routes) { +void set_initial_routes(const Input& input, std::vector& routes) { assert(std::all_of(routes.cbegin(), routes.cend(), [](const auto& r) { return r.empty(); })); @@ -1032,7 +1032,7 @@ dynamic_vehicle_choice(const Input& input, double lambda, SORT sort); -template void initial_routes(const Input& input, RawSolution& routes); +template void set_initial_routes(const Input& input, RawSolution& routes); template Eval basic(const Input& input, TWSolution& routes, @@ -1055,6 +1055,6 @@ dynamic_vehicle_choice(const Input& input, double lambda, SORT sort); -template void initial_routes(const Input& input, TWSolution& routes); +template void set_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 f234fa693..68e94a5f1 100644 --- a/src/algorithms/heuristics/heuristics.h +++ b/src/algorithms/heuristics/heuristics.h @@ -41,7 +41,7 @@ Eval dynamic_vehicle_choice(const Input& input, // Populate routes with user-defined vehicle steps. template -void initial_routes(const Input& input, std::vector& routes); +void set_initial_routes(const Input& input, std::vector& routes); } // namespace vroom::heuristics diff --git a/src/problems/vrp.h b/src/problems/vrp.h index 956519e35..c8d0d3ae6 100644 --- a/src/problems/vrp.h +++ b/src/problems/vrp.h @@ -53,15 +53,20 @@ class VRP { nb_searches = std::min(nb_searches, static_cast(parameters.size())); - // Build empty solutions to be filled by heuristics. - std::vector empty_sol; - empty_sol.reserve(_input.vehicles.size()); + // Build initial solution to be filled by heuristics. Solution is + // empty at first but populated with input data if provided. + std::vector init_sol; + init_sol.reserve(_input.vehicles.size()); for (Index v = 0; v < _input.vehicles.size(); ++v) { - empty_sol.emplace_back(_input, v, _input.zero_amount().size()); + init_sol.emplace_back(_input, v, _input.zero_amount().size()); } - std::vector> solutions(nb_searches, empty_sol); + if (_input.has_initial_routes()) { + heuristics::set_initial_routes(_input, init_sol); + } + + std::vector> solutions(nb_searches, init_sol); #ifdef LOG_LS std::vector ls_dumps; @@ -97,9 +102,6 @@ class VRP { Eval h_eval; switch (p.heuristic) { - case HEURISTIC::INIT_ROUTES: - heuristics::initial_routes(_input, solutions[rank]); - break; case HEURISTIC::BASIC: h_eval = heuristics::basic(_input, solutions[rank], @@ -125,18 +127,14 @@ class VRP { break; } - if (!_input.has_homogeneous_costs() && - p.heuristic != HEURISTIC::INIT_ROUTES && h_param.empty() && + if (!_input.has_homogeneous_costs() && h_param.empty() && p.sort == SORT::AVAILABILITY) { // Worth trying another vehicle ordering scheme in // heuristic. - std::vector other_sol = empty_sol; + std::vector other_sol = init_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, diff --git a/src/structures/typedefs.h b/src/structures/typedefs.h index abce63dc3..ea9b647be 100644 --- a/src/structures/typedefs.h +++ b/src/structures/typedefs.h @@ -114,7 +114,7 @@ enum class JOB_TYPE { SINGLE, PICKUP, DELIVERY }; enum class STEP_TYPE { START, JOB, BREAK, END }; // Heuristic options. -enum class HEURISTIC { BASIC, DYNAMIC, INIT_ROUTES }; +enum class HEURISTIC { BASIC, DYNAMIC }; enum class INIT { NONE, HIGHER_AMOUNT, NEAREST, FURTHEST, EARLIEST_DEADLINE }; enum class SORT { AVAILABILITY, COST }; @@ -130,15 +130,6 @@ struct HeuristicParameters { SORT sort = SORT::AVAILABILITY) : heuristic(heuristic), init(init), regret_coeff(regret_coeff), sort(sort) { } - - // Only makes sense for user-defined initial routes. - constexpr HeuristicParameters(HEURISTIC heuristic) - : heuristic(heuristic), - init(INIT::NONE), - regret_coeff(0), - sort(SORT::AVAILABILITY) { - assert(heuristic == HEURISTIC::INIT_ROUTES); - } }; // Possible violations. diff --git a/src/structures/vroom/input/input.cpp b/src/structures/vroom/input/input.cpp index e3ac562be..cec6bcb10 100644 --- a/src/structures/vroom/input/input.cpp +++ b/src/structures/vroom/input/input.cpp @@ -1137,13 +1137,8 @@ Solution Input::solve(unsigned nb_searches, } // Solve. - const std::vector h_init_routes(1, - HEURISTIC::INIT_ROUTES); - auto sol = instance->solve(nb_searches, - depth, - nb_thread, - solve_time, - _has_initial_routes ? h_init_routes : h_param); + auto sol = + instance->solve(nb_searches, depth, nb_thread, solve_time, h_param); // Update timing info. sol.summary.computing_times.loading = loading.count(); diff --git a/src/utils/output_json.cpp b/src/utils/output_json.cpp index 0f2bee396..e417b5841 100644 --- a/src/utils/output_json.cpp +++ b/src/utils/output_json.cpp @@ -492,9 +492,6 @@ rapidjson::Value to_json(const ls::log::Dump& dump, case DYNAMIC: heuristic = "DYNAMIC"; break; - case INIT_ROUTES: - heuristic = "INIT_ROUTES"; - break; default: assert(false); } From c81b6f1888459af556076e510ae118bc4fa8929e Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 29 Oct 2024 17:00:22 +0100 Subject: [PATCH 03/12] Account for init routes to compute unassigned jobs in basic heuristic. --- src/algorithms/heuristics/heuristics.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 7c18a8277..a772d2f2c 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -9,6 +9,7 @@ All rights reserved (see LICENSE). #include #include +#include #include "algorithms/heuristics/heuristics.h" #include "utils/helpers.h" @@ -25,17 +26,16 @@ Eval basic(const Input& input, INIT init, double lambda, SORT sort) { - assert(std::all_of(routes.cbegin(), routes.cend(), [](const auto& r) { - return r.empty(); - })); - - Eval sol_eval; - - // Consider all jobs as unassigned at first. + // Find out unassigned jobs. + std::unordered_set assigned; + std::ranges::for_each(routes, [&](const auto& r) { + std::ranges::copy(r.route, std::inserter(assigned, assigned.end())); + }); std::set unassigned; - std::copy(jobs_begin, - jobs_end, - std::inserter(unassigned, unassigned.begin())); + std::copy_if(jobs_begin, + jobs_end, + std::inserter(unassigned, unassigned.begin()), + [&](const auto j) { return !assigned.contains(j); }); // Perform heuristic ordering of the vehicles on a copy. const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); @@ -89,6 +89,8 @@ Eval basic(const Input& input, } } + Eval sol_eval; + for (Index v = 0; v < nb_vehicles; ++v) { auto v_rank = vehicles_ranks[v]; auto& current_r = routes[v_rank]; From 9e6f3c3a93e83780b5da55cfd2774d98106c21f4 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 29 Oct 2024 17:31:55 +0100 Subject: [PATCH 04/12] Account for initial route for cost and INIT in basic heuristic. --- src/algorithms/heuristics/heuristics.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index a772d2f2c..c129cb065 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -97,9 +97,16 @@ Eval basic(const Input& input, const auto& vehicle = input.vehicles[v_rank]; + // Route eval without fixed cost. Eval current_route_eval; + if (!current_r.empty()) { + current_route_eval = + utils::route_eval_for_vehicle(input, v_rank, current_r.route); + assert(vehicle.fixed_cost() <= current_route_eval.cost); + current_route_eval.cost -= vehicle.fixed_cost(); + } - if (init != INIT::NONE) { + if (current_r.empty() && init != INIT::NONE) { // Initialize current route with the "best" valid job. bool init_ok = false; From 9286e62612ecedbcbd7ff1367e4026f402bae66f Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 15:22:32 +0100 Subject: [PATCH 05/12] Extract get_unassigned process into its own function. --- src/algorithms/heuristics/heuristics.cpp | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index c129cb065..c2d02a505 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -17,16 +17,9 @@ All rights reserved (see LICENSE). namespace vroom::heuristics { template -Eval basic(const Input& input, - std::vector& routes, - const Iter jobs_begin, - const Iter jobs_end, - const Iter vehicles_begin, - const Iter vehicles_end, - INIT init, - double lambda, - SORT sort) { - // Find out unassigned jobs. +std::set get_unassigned(const std::vector& routes, + const Iter jobs_begin, + const Iter jobs_end) { std::unordered_set assigned; std::ranges::for_each(routes, [&](const auto& r) { std::ranges::copy(r.route, std::inserter(assigned, assigned.end())); @@ -37,6 +30,21 @@ Eval basic(const Input& input, std::inserter(unassigned, unassigned.begin()), [&](const auto j) { return !assigned.contains(j); }); + return unassigned; +} + +template +Eval basic(const Input& input, + std::vector& routes, + const Iter jobs_begin, + const Iter jobs_end, + const Iter vehicles_begin, + const Iter vehicles_end, + INIT init, + double lambda, + SORT sort) { + auto unassigned = get_unassigned(routes, jobs_begin, jobs_end); + // Perform heuristic ordering of the vehicles on a copy. const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); std::vector vehicles_ranks; From 5d27f3c7ffb2c69cfd4868b7f2a2bfc665a651b1 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 15:31:21 +0100 Subject: [PATCH 06/12] Account for init routes to compute unassigned jobs in dynamic heuristic. --- src/algorithms/heuristics/heuristics.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index c2d02a505..9fa22077b 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -449,17 +449,7 @@ Eval 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(); - })); - - Eval sol_eval; - - // Consider all jobs as unassigned at first. - std::set unassigned; - std::copy(jobs_begin, - jobs_end, - std::inserter(unassigned, unassigned.begin())); + auto unassigned = get_unassigned(routes, jobs_begin, jobs_end); // Work on a copy of the vehicles ranks from which we erase values // each time a route is completed. @@ -470,6 +460,8 @@ Eval dynamic_vehicle_choice(const Input& input, const auto& evals = input.jobs_vehicles_evals(); + Eval sol_eval; + while (!vehicles_ranks.empty() && !unassigned.empty()) { // For any unassigned job at j, jobs_min_costs[j] // (resp. jobs_second_min_costs[j]) holds the min cost From 3e785314cb477ca4a0ef20d425aeaac0f8bd8c21 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 15:31:51 +0100 Subject: [PATCH 07/12] Account for initial route for cost and INIT in dynamic heuristic. --- src/algorithms/heuristics/heuristics.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 9fa22077b..e881bf361 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -545,9 +545,16 @@ Eval dynamic_vehicle_choice(const Input& input, const auto& vehicle = input.vehicles[v_rank]; auto& current_r = routes[v_rank]; + // Route eval without fixed cost. Eval current_route_eval; + if (!current_r.empty()) { + current_route_eval = + utils::route_eval_for_vehicle(input, v_rank, current_r.route); + assert(vehicle.fixed_cost() <= current_route_eval.cost); + current_route_eval.cost -= vehicle.fixed_cost(); + } - if (init != INIT::NONE) { + if (current_r.empty() && init != INIT::NONE) { // Initialize current route with the "best" valid job that is // closest for current vehicle than to any other remaining // vehicle. From d2574cd190dcee1f3e44413fa6cdd84e2fb979d1 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 17:13:20 +0100 Subject: [PATCH 08/12] Clarify things on which initial routes have no impact. --- src/algorithms/heuristics/heuristics.cpp | 25 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index e881bf361..f203e1748 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -45,7 +45,9 @@ Eval basic(const Input& input, SORT sort) { auto unassigned = get_unassigned(routes, jobs_begin, jobs_end); - // Perform heuristic ordering of the vehicles on a copy. + // Perform heuristic ordering of the vehicles on a copy. Ordering is + // based on vehicles description only so do not account for initial + // routes if any. const auto nb_vehicles = std::distance(vehicles_begin, vehicles_end); std::vector vehicles_ranks; vehicles_ranks.reserve(nb_vehicles); @@ -78,7 +80,9 @@ Eval basic(const Input& input, // regrets[v][j] holds the min cost for reaching job j in an empty // route across all remaining vehicles **after** vehicle at rank v - // in vehicles_ranks. + // in vehicles_ranks. Regrets are only computed for available + // vehicles and unassigned jobs, but are based on empty routes + // evaluations so do not account for initial routes if any. std::vector> regrets(nb_vehicles, std::vector(input.jobs.size())); @@ -466,7 +470,8 @@ Eval dynamic_vehicle_choice(const Input& input, // For any unassigned job at j, jobs_min_costs[j] // (resp. jobs_second_min_costs[j]) holds the min cost // (resp. second min cost) of picking the job in an empty route - // for any remaining vehicle. + // for any remaining vehicle. Evaluation are based on empty routes + // so do not account for initial routes if any. std::vector jobs_min_costs(input.jobs.size(), input.get_cost_upper_bound()); std::vector jobs_second_min_costs(input.jobs.size(), @@ -484,8 +489,9 @@ Eval dynamic_vehicle_choice(const Input& input, } } - // Pick vehicle that has the biggest number of compatible jobs - // closest to him than to any other different vehicle. + // Pick vehicle that has the biggest number of compatible + // unassigned jobs closest to him than to any other different + // vehicle still available. std::vector closest_jobs_count(input.vehicles.size(), 0); for (const auto job_rank : unassigned) { for (const auto v_rank : vehicles_ranks) { @@ -530,9 +536,12 @@ Eval dynamic_vehicle_choice(const Input& input, vehicles_ranks.erase(chosen_vehicle); } - // Once current vehicle is decided, regrets[j] holds the min cost - // of picking the job in an empty route for other remaining - // vehicles. + // Once current vehicle is decided, then for any unassigned job at + // j, regrets[j] holds the min cost of picking the job in an empty + // route for other remaining vehicles. Regrets are only computed + // for available vehicles and unassigned jobs, but are based on + // empty routes evaluations so do not account for initial routes + // if any. std::vector regrets(input.jobs.size(), input.get_cost_upper_bound()); for (const auto job_rank : unassigned) { if (jobs_min_costs[job_rank] < evals[job_rank][v_rank].cost) { From e57e91598f267a9ab008e99ff060a48b8b6b8017 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 17:16:57 +0100 Subject: [PATCH 09/12] Mention heuristic application in changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16481e666..00bd15719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ #### Internals +- Apply heuristics to partial solutions provided in input (#977) - LOG_LS flag to generate debug info on the internal solving process (#1124) ### Changed From d7788d817e7f9951b62e569fb8991ee7d4a6ed90 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 17:59:32 +0100 Subject: [PATCH 10/12] Reduce cognitive complexity for set_initial_routes. --- src/algorithms/heuristics/heuristics.cpp | 237 +++++++++++------------ 1 file changed, 117 insertions(+), 120 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index f203e1748..18a8e9d03 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -890,149 +890,146 @@ Eval dynamic_vehicle_choice(const Input& input, return sol_eval; } -template -void set_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]; - - // Startup load is the sum of deliveries for (single) jobs. - Amount single_jobs_deliveries(input.zero_amount()); - for (const auto& step : vehicle.steps) { - if (step.type == STEP_TYPE::JOB) { - assert(step.job_type.has_value()); - - if (step.job_type.value() == JOB_TYPE::SINGLE) { - single_jobs_deliveries += input.jobs[step.rank].delivery; - } - } - } - if (!(single_jobs_deliveries <= vehicle.capacity)) { - throw InputException( - std::format("Route over capacity for vehicle {}.", vehicle.id)); - } - - // Track load and travel time during the route for validity. - Amount current_load = single_jobs_deliveries; - Eval eval_sum; - std::optional previous_index; - if (vehicle.has_start()) { - previous_index = vehicle.start.value().index(); - } +template void set_route(const Input& input, Route& route) { + assert(route.empty()); + const auto& vehicle = input.vehicles[route.vehicle_rank]; + + // Startup load is the sum of deliveries for (single) jobs. + Amount single_jobs_deliveries(input.zero_amount()); + for (const auto& step : vehicle.steps) { + if (step.type == STEP_TYPE::JOB) { + assert(step.job_type.has_value()); - std::vector job_ranks; - job_ranks.reserve(vehicle.steps.size()); - std::unordered_set expected_delivery_ranks; - for (const auto& step : vehicle.steps) { - if (step.type != STEP_TYPE::JOB) { - continue; + if (step.job_type.value() == JOB_TYPE::SINGLE) { + single_jobs_deliveries += input.jobs[step.rank].delivery; } + } + } + if (!(single_jobs_deliveries <= vehicle.capacity)) { + throw InputException( + std::format("Route over capacity for vehicle {}.", vehicle.id)); + } - const auto job_rank = step.rank; - const auto& job = input.jobs[job_rank]; - job_ranks.push_back(job_rank); - - if (!input.vehicle_ok_with_job(v, job_rank)) { - throw InputException( - std::format("Missing skill or step out of reach for vehicle {} and " - "job {}.", - vehicle.id, - job.id)); - } + // Track load and travel time during the route for validity. + Amount current_load = single_jobs_deliveries; + Eval eval_sum; + std::optional previous_index; + if (vehicle.has_start()) { + previous_index = vehicle.start.value().index(); + } - // Update current travel time. - if (previous_index.has_value()) { - eval_sum += vehicle.eval(previous_index.value(), job.index()); - } - previous_index = job.index(); + std::vector job_ranks; + job_ranks.reserve(vehicle.steps.size()); + std::unordered_set expected_delivery_ranks; + for (const auto& step : vehicle.steps) { + if (step.type != STEP_TYPE::JOB) { + continue; + } - // Handle load. - assert(step.job_type.has_value()); - switch (step.job_type.value()) { - case JOB_TYPE::SINGLE: { - current_load += job.pickup; - current_load -= job.delivery; - break; - } - case JOB_TYPE::PICKUP: { - expected_delivery_ranks.insert(job_rank + 1); + const auto job_rank = step.rank; + const auto& job = input.jobs[job_rank]; + job_ranks.push_back(job_rank); - current_load += job.pickup; - break; - } - case JOB_TYPE::DELIVERY: { - auto search = expected_delivery_ranks.find(job_rank); - if (search == expected_delivery_ranks.end()) { - throw InputException( - std::format("Invalid shipment in route for vehicle {}.", - vehicle.id)); - } - expected_delivery_ranks.erase(search); + if (!input.vehicle_ok_with_job(route.vehicle_rank, job_rank)) { + throw InputException( + std::format("Missing skill or step out of reach for vehicle {} and " + "job {}.", + vehicle.id, + job.id)); + } - current_load -= job.delivery; - break; - } - } + // Update current travel time. + if (previous_index.has_value()) { + eval_sum += vehicle.eval(previous_index.value(), job.index()); + } + previous_index = job.index(); + + // Handle load. + assert(step.job_type.has_value()); + switch (step.job_type.value()) { + case JOB_TYPE::SINGLE: { + current_load += job.pickup; + current_load -= job.delivery; + break; + } + case JOB_TYPE::PICKUP: { + expected_delivery_ranks.insert(job_rank + 1); - // Check validity after this step wrt capacity. - if (!(current_load <= vehicle.capacity)) { + current_load += job.pickup; + break; + } + case JOB_TYPE::DELIVERY: { + auto search = expected_delivery_ranks.find(job_rank); + if (search == expected_delivery_ranks.end()) { throw InputException( - std::format("Route over capacity for vehicle {}.", vehicle.id)); + std::format("Invalid shipment in route for vehicle {}.", vehicle.id)); } - } + expected_delivery_ranks.erase(search); - if (vehicle.has_end() && !job_ranks.empty()) { - // Update with last route leg. - assert(previous_index.has_value()); - eval_sum += - vehicle.eval(previous_index.value(), vehicle.end.value().index()); + current_load -= job.delivery; + break; } - if (!vehicle.ok_for_travel_time(eval_sum.duration)) { - throw InputException( - std::format("Route over max_travel_time for vehicle {}.", vehicle.id)); - } - if (!vehicle.ok_for_distance(eval_sum.distance)) { - throw InputException( - std::format("Route over max_distance for vehicle {}.", vehicle.id)); } - if (vehicle.max_tasks < job_ranks.size()) { + // Check validity after this step wrt capacity. + if (!(current_load <= vehicle.capacity)) { throw InputException( - std::format("Too many tasks for vehicle {}.", vehicle.id)); + std::format("Route over capacity for vehicle {}.", vehicle.id)); } + } - if (!expected_delivery_ranks.empty()) { - throw InputException( - std::format("Invalid shipment in route for vehicle {}.", vehicle.id)); - } + if (vehicle.has_end() && !job_ranks.empty()) { + // Update with last route leg. + assert(previous_index.has_value()); + eval_sum += + vehicle.eval(previous_index.value(), vehicle.end.value().index()); + } + if (!vehicle.ok_for_travel_time(eval_sum.duration)) { + throw InputException( + std::format("Route over max_travel_time for vehicle {}.", vehicle.id)); + } + if (!vehicle.ok_for_distance(eval_sum.distance)) { + throw InputException( + std::format("Route over max_distance for vehicle {}.", vehicle.id)); + } - // Now route is OK with regard to capacity, max_travel_time, - // max_tasks, precedence and skills constraints. - if (!job_ranks.empty()) { - if (!current_r.is_valid_addition_for_tw(input, - single_jobs_deliveries, - job_ranks.begin(), - job_ranks.end(), - 0, - 0)) { - throw InputException( - std::format("Infeasible route for vehicle {}.", vehicle.id)); - } + if (vehicle.max_tasks < job_ranks.size()) { + throw InputException( + std::format("Too many tasks for vehicle {}.", vehicle.id)); + } + + if (!expected_delivery_ranks.empty()) { + throw InputException( + std::format("Invalid shipment in route for vehicle {}.", vehicle.id)); + } - current_r.replace(input, - single_jobs_deliveries, - job_ranks.begin(), - job_ranks.end(), - 0, - 0); + // Now route is OK with regard to capacity, max_travel_time, + // max_tasks, precedence and skills constraints. + if (!job_ranks.empty()) { + if (!route.is_valid_addition_for_tw(input, + single_jobs_deliveries, + job_ranks.begin(), + job_ranks.end(), + 0, + 0)) { + throw InputException( + std::format("Infeasible route for vehicle {}.", vehicle.id)); } + + route.replace(input, + single_jobs_deliveries, + job_ranks.begin(), + job_ranks.end(), + 0, + 0); } } +template +void set_initial_routes(const Input& input, std::vector& routes) { + std::ranges::for_each(routes, [&](auto& r) { set_route(input, r); }); +} + using RawSolution = std::vector; using TWSolution = std::vector; From 9969beb75dcfc4a35dadb21d1e8015c091f115b1 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 18:13:14 +0100 Subject: [PATCH 11/12] Fix SonarCloud complaints about bool operands. --- src/algorithms/heuristics/heuristics.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 18a8e9d03..186523619 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -144,20 +144,20 @@ Eval basic(const Input& input, bool try_validity = false; if (init == INIT::HIGHER_AMOUNT) { - try_validity |= (higher_amount < current_job.pickup || - higher_amount < current_job.delivery); + try_validity = (higher_amount < current_job.pickup || + higher_amount < current_job.delivery); } if (init == INIT::EARLIEST_DEADLINE) { Duration current_deadline = is_pickup ? input.jobs[job_rank + 1].tws.back().end : current_job.tws.back().end; - try_validity |= (current_deadline < earliest_deadline); + try_validity = (current_deadline < earliest_deadline); } if (init == INIT::FURTHEST) { - try_validity |= (furthest_cost < evals[job_rank][v_rank].cost); + try_validity = (furthest_cost < evals[job_rank][v_rank].cost); } if (init == INIT::NEAREST) { - try_validity |= (evals[job_rank][v_rank].cost < nearest_cost); + try_validity = (evals[job_rank][v_rank].cost < nearest_cost); } if (!try_validity) { @@ -593,20 +593,20 @@ Eval dynamic_vehicle_choice(const Input& input, bool try_validity = false; if (init == INIT::HIGHER_AMOUNT) { - try_validity |= (higher_amount < current_job.pickup || - higher_amount < current_job.delivery); + try_validity = (higher_amount < current_job.pickup || + higher_amount < current_job.delivery); } if (init == INIT::EARLIEST_DEADLINE) { Duration current_deadline = is_pickup ? input.jobs[job_rank + 1].tws.back().end : current_job.tws.back().end; - try_validity |= (current_deadline < earliest_deadline); + try_validity = (current_deadline < earliest_deadline); } if (init == INIT::FURTHEST) { - try_validity |= (furthest_cost < evals[job_rank][v_rank].cost); + try_validity = (furthest_cost < evals[job_rank][v_rank].cost); } if (init == INIT::NEAREST) { - try_validity |= (evals[job_rank][v_rank].cost < nearest_cost); + try_validity = (evals[job_rank][v_rank].cost < nearest_cost); } if (!try_validity) { From ed836dceb0356a412251cdb55306e0c0a6f823f9 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 5 Nov 2024 18:31:52 +0100 Subject: [PATCH 12/12] Silence a couple other SonarCloud issues. --- src/algorithms/heuristics/heuristics.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 186523619..03433c898 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -87,9 +87,8 @@ Eval basic(const Input& input, std::vector(input.jobs.size())); // Use own cost for last vehicle regret values. - auto& last_regrets = regrets.back(); for (const auto j : unassigned) { - last_regrets[j] = evals[j][vehicles_ranks.back()].cost; + regrets.back()[j] = evals[j][vehicles_ranks.back()].cost; } for (Index rev_v = 0; rev_v < nb_vehicles - 1; ++rev_v) { @@ -969,6 +968,8 @@ template void set_route(const Input& input, Route& route) { current_load -= job.delivery; break; } + default: + assert(false); } // Check validity after this step wrt capacity.