diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d3dcc6e..2faf6c002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Switch to C++20 (#851) - Improved error messages for file-related IO errors (#553) - Add job id to error message for unreachable step (#946) +- Reduce `compute_best_route_split_choice` complexity (#962) ### Fixed @@ -27,6 +28,8 @@ - Comparison of index-based and coordinates-based locations (#935) - `max_travel_time` parameter not taken into account in edge case (#884) - Meaningless `location_index` provided in output for break steps (#877) +- `max_travel_time` not accounted for with vehicle steps in solving mode (#954) +- `max_travel_time` not accounted for in `RouteSplit` (#941) ## [v1.13.0] - 2023-01-31 diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 4e2ce303e..54b1183d6 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -886,6 +886,7 @@ T dynamic_vehicle_choice(const Input& input, template T initial_routes(const Input& input) { T routes; + routes.reserve(input.vehicles.size()); for (Index v = 0; v < input.vehicles.size(); ++v) { routes.emplace_back(input, v, input.zero_amount().size()); @@ -908,10 +909,16 @@ template T initial_routes(const Input& input) { std::to_string(vehicle.id) + "."); } - // Startup load is the sum of deliveries for (single) jobs. + // 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(); + } 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) { @@ -928,6 +935,13 @@ template T initial_routes(const Input& input) { std::to_string(job.id) + "."); } + // 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: { @@ -961,6 +975,17 @@ template T initial_routes(const Input& input) { } } + if (vehicle.has_end() and !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("Route over max_travel_time for vehicle " + + std::to_string(vehicle.id) + "."); + } + if (vehicle.max_tasks < job_ranks.size()) { throw InputException("Too many tasks for vehicle " + std::to_string(vehicle.id) + "."); @@ -971,8 +996,8 @@ template T initial_routes(const Input& input) { std::to_string(vehicle.id) + "."); } - // Now route is OK with regard to capacity, precedence and skills - // constraints. + // 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, diff --git a/src/algorithms/local_search/route_split_utils.h b/src/algorithms/local_search/route_split_utils.h index fac86dc14..1cfdb493f 100644 --- a/src/algorithms/local_search/route_split_utils.h +++ b/src/algorithms/local_search/route_split_utils.h @@ -72,11 +72,21 @@ compute_best_route_split_choice(const Input& input, continue; } - Eval current_end_eval = - utils::route_eval_for_vehicle(input, - v, - source.route.begin() + r, - source.route.end()); + Eval current_end_eval(end_v.fixed_cost(), 0); + current_end_eval += sol_state.fwd_costs[s_vehicle][v].back() - + sol_state.fwd_costs[s_vehicle][v][r]; + if (end_v.has_start()) { + current_end_eval += end_v.eval(end_v.start.value().index(), + input.jobs[source.route[r]].index()); + } + if (end_v.has_end()) { + current_end_eval += end_v.eval(input.jobs[source.route.back()].index(), + end_v.end.value().index()); + } + + if (!end_v.ok_for_travel_time(current_end_eval.duration)) { + continue; + } if (current_end_eval < second_best_end_eval) { // Worth checking end route full validity. @@ -133,11 +143,22 @@ compute_best_route_split_choice(const Input& input, continue; } - Eval current_begin_eval = - utils::route_eval_for_vehicle(input, - v, - source.route.begin(), - source.route.begin() + r); + Eval current_begin_eval(begin_v.fixed_cost(), 0); + current_begin_eval += sol_state.fwd_costs[s_vehicle][v][r - 1]; + if (begin_v.has_start()) { + current_begin_eval += + begin_v.eval(begin_v.start.value().index(), + input.jobs[source.route.front()].index()); + } + if (begin_v.has_end()) { + current_begin_eval += + begin_v.eval(input.jobs[source.route[r - 1]].index(), + begin_v.end.value().index()); + } + + if (!begin_v.ok_for_travel_time(current_begin_eval.duration)) { + continue; + } if (current_begin_eval < second_best_begin_eval) { // Worth checking begin route full validity.