From 9894cce2b4fe8fc3283d7b2d92e2781318eacbce Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 6 Sep 2022 15:46:22 +0200 Subject: [PATCH 01/40] Store max_travel_time value in Vehicle. --- src/structures/vroom/vehicle.cpp | 4 +++- src/structures/vroom/vehicle.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/structures/vroom/vehicle.cpp b/src/structures/vroom/vehicle.cpp index af3073bd6..fce19b25d 100644 --- a/src/structures/vroom/vehicle.cpp +++ b/src/structures/vroom/vehicle.cpp @@ -25,6 +25,7 @@ Vehicle::Vehicle(Id id, const std::string& description, double speed_factor, const size_t max_tasks, + const Duration max_travel_time, const std::vector& input_steps) : id(id), start(start), @@ -36,7 +37,8 @@ Vehicle::Vehicle(Id id, breaks(breaks), description(description), cost_wrapper(speed_factor), - max_tasks(max_tasks) { + max_tasks(max_tasks), + max_travel_time(max_travel_time) { if (!static_cast(start) and !static_cast(end)) { throw InputException("No start or end specified for vehicle " + std::to_string(id) + '.'); diff --git a/src/structures/vroom/vehicle.h b/src/structures/vroom/vehicle.h index 0e5e160e9..63c8337b4 100644 --- a/src/structures/vroom/vehicle.h +++ b/src/structures/vroom/vehicle.h @@ -36,6 +36,7 @@ struct Vehicle { const std::string description; CostWrapper cost_wrapper; size_t max_tasks; + Duration max_travel_time; std::vector steps; std::unordered_map break_id_to_rank; @@ -51,6 +52,7 @@ struct Vehicle { const std::string& description = "", double speed_factor = 1., const size_t max_tasks = std::numeric_limits::max(), + const Duration max_travel_time = std::numeric_limits::max(), const std::vector& input_steps = std::vector()); bool has_start() const; From 0c4535cbee46582a7a4a6762e7557d10803db8e4 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 6 Sep 2022 15:51:48 +0200 Subject: [PATCH 02/40] Parse vehicle.max_travel_time key at json level. --- src/utils/input_parser.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/utils/input_parser.cpp b/src/utils/input_parser.cpp index c704f0668..fd99361a1 100644 --- a/src/utils/input_parser.cpp +++ b/src/utils/input_parser.cpp @@ -105,7 +105,7 @@ inline Duration get_duration(const rapidjson::Value& object, const char* key) { return duration; } -inline Duration get_priority(const rapidjson::Value& object) { +inline Priority get_priority(const rapidjson::Value& object) { Priority priority = 0; if (object.HasMember("priority")) { if (!object["priority"].IsUint()) { @@ -127,6 +127,17 @@ inline size_t get_max_tasks(const rapidjson::Value& object) { return max_tasks; } +inline Duration get_max_travel_time(const rapidjson::Value& object) { + Duration max_travel_time = std::numeric_limits::max(); + if (object.HasMember("max_travel_time")) { + if (!object["max_travel_time"].IsUint()) { + throw InputException("Invalid max_travel_time value."); + } + max_travel_time = object["max_travel_time"].GetUint(); + } + return max_travel_time; +} + inline void check_id(const rapidjson::Value& v, const std::string& type) { if (!v.IsObject()) { throw InputException("Invalid " + type + "."); @@ -391,6 +402,7 @@ inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, get_string(json_vehicle, "description"), get_double(json_vehicle, "speed_factor"), get_max_tasks(json_vehicle), + get_max_travel_time(json_vehicle), get_vehicle_steps(json_vehicle)); } From 7a9d873a9c3829720a91529064fc793cebaabc48 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 6 Sep 2022 16:47:59 +0200 Subject: [PATCH 03/40] Switch from Cost to Eval in debug assert after LS move. --- src/algorithms/local_search/local_search.cpp | 20 ++++++++++---------- src/structures/vroom/solution_indicators.h | 18 ++++++++++++------ src/structures/vroom/solution_state.cpp | 8 ++++---- src/structures/vroom/solution_state.h | 4 ++-- src/utils/helpers.h | 14 +++++++------- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index eb8a650f0..3a22d8d6f 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -284,8 +284,8 @@ void LocalSearch struct SolutionIndicators { Priority priority_sum; unsigned assigned; - Cost cost; + Eval eval; unsigned used_vehicles; SolutionIndicators() - : priority_sum(0), assigned(0), cost(0), used_vehicles(0) { + : priority_sum(0), assigned(0), eval(), used_vehicles(0) { } SolutionIndicators(const Input& input, const std::vector& sol) @@ -34,7 +34,7 @@ template struct SolutionIndicators { priority_sum += utils::priority_sum_for_route(input, r.route); assigned += r.route.size(); - cost += utils::route_cost_for_vehicle(input, v_rank, r.route); + eval += utils::route_eval_for_vehicle(input, v_rank, r.route); ++v_rank; if (!r.empty()) { @@ -53,11 +53,17 @@ template struct SolutionIndicators { return true; } if (lhs.assigned == rhs.assigned) { - if (lhs.cost < rhs.cost) { + if (lhs.eval.cost < rhs.eval.cost) { return true; } - if (lhs.cost == rhs.cost and lhs.used_vehicles < rhs.used_vehicles) { - return true; + if (lhs.eval.cost == rhs.eval.cost) { + if (lhs.eval.duration < rhs.eval.duration) { + return true; + } + if (lhs.eval.duration == rhs.eval.duration and + lhs.used_vehicles < rhs.used_vehicles) { + return true; + } } } } diff --git a/src/structures/vroom/solution_state.cpp b/src/structures/vroom/solution_state.cpp index e775e877b..8ab0610fc 100644 --- a/src/structures/vroom/solution_state.cpp +++ b/src/structures/vroom/solution_state.cpp @@ -41,7 +41,7 @@ SolutionState::SolutionState(const Input& input) weak_insertion_ranks_end(_nb_vehicles) #ifndef NDEBUG , - route_costs(_nb_vehicles) + route_evals(_nb_vehicles) #endif { } @@ -55,7 +55,7 @@ template void SolutionState::setup(const Route& r, Index v) { set_pd_gains(r.route, v); set_insertion_ranks(r, v); #ifndef NDEBUG - update_route_cost(r.route, v); + update_route_eval(r.route, v); #endif } @@ -616,9 +616,9 @@ void SolutionState::update_cheapest_job_rank_in_routes( } #ifndef NDEBUG -void SolutionState::update_route_cost(const std::vector& route, +void SolutionState::update_route_eval(const std::vector& route, Index v) { - route_costs[v] = route_cost_for_vehicle(_input, v, route); + route_evals[v] = route_eval_for_vehicle(_input, v, route); } #endif diff --git a/src/structures/vroom/solution_state.h b/src/structures/vroom/solution_state.h index 7d1750cee..3834f057f 100644 --- a/src/structures/vroom/solution_state.h +++ b/src/structures/vroom/solution_state.h @@ -113,7 +113,7 @@ class SolutionState { #ifndef NDEBUG // Only used for assertion checks in debug mode. - std::vector route_costs; + std::vector route_evals; #endif SolutionState(const Input& input); @@ -143,7 +143,7 @@ class SolutionState { void set_insertion_ranks(const TWRoute& r, Index v); #ifndef NDEBUG - void update_route_cost(const std::vector& route, Index v); + void update_route_eval(const std::vector& route, Index v); #endif }; diff --git a/src/utils/helpers.h b/src/utils/helpers.h index 51587e446..c73377d0b 100644 --- a/src/utils/helpers.h +++ b/src/utils/helpers.h @@ -296,30 +296,30 @@ inline Priority priority_sum_for_route(const Input& input, }); } -inline Cost route_cost_for_vehicle(const Input& input, +inline Eval route_eval_for_vehicle(const Input& input, Index vehicle_rank, const std::vector& route) { const auto& v = input.vehicles[vehicle_rank]; - Cost cost = 0; + Eval eval; if (route.size() > 0) { if (v.has_start()) { - cost += - v.cost(v.start.value().index(), input.jobs[route.front()].index()); + eval += + v.eval(v.start.value().index(), input.jobs[route.front()].index()); } Index previous = route.front(); for (auto it = ++route.cbegin(); it != route.cend(); ++it) { - cost += v.cost(input.jobs[previous].index(), input.jobs[*it].index()); + eval += v.eval(input.jobs[previous].index(), input.jobs[*it].index()); previous = *it; } if (v.has_end()) { - cost += v.cost(input.jobs[route.back()].index(), v.end.value().index()); + eval += v.eval(input.jobs[route.back()].index(), v.end.value().index()); } } - return cost; + return eval; } inline void check_precedence(const Input& input, From 86b098b4dedac0038f39a98b946625eec968bf80 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 6 Sep 2022 17:07:02 +0200 Subject: [PATCH 04/40] Crash on exceeded travel time in debug mode. --- src/algorithms/local_search/local_search.cpp | 3 +++ src/utils/helpers.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 3a22d8d6f..9208c9c9d 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -1652,7 +1652,10 @@ void LocalSearch Date: Wed, 7 Sep 2022 13:56:49 +0200 Subject: [PATCH 05/40] Switch to using Eval in heuristics::get_jobs_vehicles_costs. --- src/algorithms/heuristics/heuristics.cpp | 83 ++++++++++++------------ 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index c1e7913d0..16fa0fc7a 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -15,13 +15,13 @@ All rights reserved (see LICENSE). namespace vroom { namespace heuristics { -std::vector> get_jobs_vehicles_costs(const Input& input) { - // For a single job j, costs[j][v] is the cost of fetching job j in - // an empty route from vehicle at rank v. For a pickup job j, - // costs[j][v] is the cost of fetching job j **and** associated - // delivery in an empty route from vehicle at rank v. - std::vector> costs(input.jobs.size(), - std::vector( +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(); @@ -36,18 +36,19 @@ std::vector> get_jobs_vehicles_costs(const Input& input) { for (std::size_t v = 0; v < input.vehicles.size(); ++v) { const auto& vehicle = input.vehicles[v]; - Cost current_cost = is_pickup ? vehicle.cost(j_index, last_job_index) : 0; + Eval current_eval = + is_pickup ? vehicle.eval(j_index, last_job_index) : Eval(); if (vehicle.has_start()) { - current_cost += vehicle.cost(vehicle.start.value().index(), j_index); + current_eval += vehicle.eval(vehicle.start.value().index(), j_index); } if (vehicle.has_end()) { - current_cost += - vehicle.cost(last_job_index, vehicle.end.value().index()); + current_eval += + vehicle.eval(last_job_index, vehicle.end.value().index()); } - costs[j][v] = current_cost; + evals[j][v] = current_eval; if (is_pickup) { - // Assign same cost to delivery. - costs[j + 1][v] = current_cost; + // Assign same eval to delivery. + evals[j + 1][v] = current_eval; } } @@ -57,7 +58,7 @@ std::vector> get_jobs_vehicles_costs(const Input& input) { } } - return costs; + return evals; } template T basic(const Input& input, INIT init, double lambda) { @@ -90,18 +91,19 @@ template T basic(const Input& input, INIT init, double lambda) { v_lhs.tw.length > v_rhs.tw.length))); }); - auto costs = get_jobs_vehicles_costs(input); + auto evals = get_jobs_vehicles_evals(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 vehicle_ranks. - std::vector> regrets(nb_vehicles, - std::vector(input.jobs.size())); + std::vector> regrets(nb_vehicles, + std::vector( + input.jobs.size())); // Use own cost for last vehicle regret values. auto& last_regrets = regrets.back(); for (Index j = 0; j < input.jobs.size(); ++j) { - last_regrets[j] = costs[j][vehicles_ranks.back()]; + last_regrets[j] = evals[j][vehicles_ranks.back()].cost; } for (Index rev_v = 0; rev_v < nb_vehicles - 1; ++rev_v) { @@ -109,7 +111,7 @@ template T basic(const Input& input, INIT init, double lambda) { const auto v = nb_vehicles - 2 - rev_v; for (Index j = 0; j < input.jobs.size(); ++j) { regrets[v][j] = - std::min(regrets[v + 1][j], costs[j][vehicles_ranks[v + 1]]); + std::min(regrets[v + 1][j], (evals[j][vehicles_ranks[v + 1]]).cost); } } @@ -153,10 +155,10 @@ template T basic(const Input& input, INIT init, double lambda) { try_validity |= (current_deadline < earliest_deadline); } if (init == INIT::FURTHEST) { - try_validity |= (furthest_cost < costs[job_rank][v_rank]); + try_validity |= (furthest_cost < evals[job_rank][v_rank].cost); } if (init == INIT::NEAREST) { - try_validity |= (costs[job_rank][v_rank] < nearest_cost); + try_validity |= (evals[job_rank][v_rank].cost < nearest_cost); } if (!try_validity) { @@ -204,10 +206,10 @@ template T basic(const Input& input, INIT init, double lambda) { : input.jobs[job_rank].tws.back().end; break; case INIT::FURTHEST: - furthest_cost = costs[job_rank][v_rank]; + furthest_cost = evals[job_rank][v_rank].cost; break; case INIT::NEAREST: - nearest_cost = costs[job_rank][v_rank]; + nearest_cost = evals[job_rank][v_rank].cost; break; } } @@ -427,25 +429,26 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { std::vector vehicles_ranks(nb_vehicles); std::iota(vehicles_ranks.begin(), vehicles_ranks.end(), 0); - auto costs = get_jobs_vehicles_costs(input); + auto evals = get_jobs_vehicles_evals(input); while (!vehicles_ranks.empty() and !unassigned.empty()) { // 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. - std::vector jobs_min_costs(input.jobs.size(), - std::numeric_limits::max()); - std::vector jobs_second_min_costs(input.jobs.size(), - std::numeric_limits::max()); + std::vector jobs_min_costs(input.jobs.size(), + std::numeric_limits::max()); + std::vector + jobs_second_min_costs(input.jobs.size(), + std::numeric_limits::max()); for (const auto job_rank : unassigned) { for (const auto v_rank : vehicles_ranks) { - if (costs[job_rank][v_rank] <= jobs_min_costs[job_rank]) { + if (evals[job_rank][v_rank].cost <= jobs_min_costs[job_rank]) { jobs_second_min_costs[job_rank] = jobs_min_costs[job_rank]; - jobs_min_costs[job_rank] = costs[job_rank][v_rank]; + jobs_min_costs[job_rank] = evals[job_rank][v_rank].cost; } else { - if (costs[job_rank][v_rank] < jobs_second_min_costs[job_rank]) { - jobs_second_min_costs[job_rank] = costs[job_rank][v_rank]; + if (evals[job_rank][v_rank].cost < jobs_second_min_costs[job_rank]) { + jobs_second_min_costs[job_rank] = evals[job_rank][v_rank].cost; } } } @@ -456,7 +459,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { std::vector closest_jobs_count(nb_vehicles, 0); for (const auto job_rank : unassigned) { for (const auto v_rank : vehicles_ranks) { - if (costs[job_rank][v_rank] == jobs_min_costs[job_rank]) { + if (evals[job_rank][v_rank].cost == jobs_min_costs[job_rank]) { ++closest_jobs_count[v_rank]; } } @@ -484,7 +487,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { // vehicles. std::vector regrets(input.jobs.size(), input.get_cost_upper_bound()); for (const auto job_rank : unassigned) { - if (jobs_min_costs[job_rank] < costs[job_rank][v_rank]) { + if (jobs_min_costs[job_rank] < evals[job_rank][v_rank].cost) { regrets[job_rank] = jobs_min_costs[job_rank]; } else { regrets[job_rank] = jobs_second_min_costs[job_rank]; @@ -506,7 +509,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { Duration earliest_deadline = std::numeric_limits::max(); Index best_job_rank = 0; for (const auto job_rank : unassigned) { - if (jobs_min_costs[job_rank] < costs[job_rank][v_rank] or + 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) { @@ -532,10 +535,10 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { try_validity |= (current_deadline < earliest_deadline); } if (init == INIT::FURTHEST) { - try_validity |= (furthest_cost < costs[job_rank][v_rank]); + try_validity |= (furthest_cost < evals[job_rank][v_rank].cost); } if (init == INIT::NEAREST) { - try_validity |= (costs[job_rank][v_rank] < nearest_cost); + try_validity |= (evals[job_rank][v_rank].cost < nearest_cost); } if (!try_validity) { @@ -584,10 +587,10 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { : input.jobs[job_rank].tws.back().end; break; case INIT::FURTHEST: - furthest_cost = costs[job_rank][v_rank]; + furthest_cost = evals[job_rank][v_rank].cost; break; case INIT::NEAREST: - nearest_cost = costs[job_rank][v_rank]; + nearest_cost = evals[job_rank][v_rank].cost; break; } } From 2d670799f8cd005e93903d8372c93311eb8c1d0e Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 7 Sep 2022 14:20:11 +0200 Subject: [PATCH 06/40] Don't go over max_travel_time in basic heuristic. --- src/algorithms/heuristics/heuristics.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 16fa0fc7a..41272eb11 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -121,6 +121,8 @@ template T basic(const Input& input, INIT init, double lambda) { const auto& vehicle = input.vehicles[v_rank]; + Duration current_route_duration = 0; + if (init != INIT::NONE) { // Initialize current route with the "best" valid job. bool init_ok = false; @@ -166,6 +168,10 @@ template T basic(const Input& input, INIT init, double lambda) { } bool is_valid = + (evals[job_rank][v_rank].duration <= vehicle.max_travel_time); + + is_valid = + is_valid && current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -227,6 +233,7 @@ template T basic(const Input& input, INIT init, double lambda) { unassigned.erase(best_job_rank); unassigned.erase(best_job_rank + 1); } + current_route_duration += evals[best_job_rank][v_rank].duration; } } @@ -238,6 +245,7 @@ template T basic(const Input& input, INIT init, double lambda) { Index best_r = 0; Index best_pickup_r = 0; Index best_delivery_r = 0; + Duration best_duration_addition = 0; for (const auto job_rank : unassigned) { if (!input.vehicle_ok_with_job(v_rank, job_rank)) { @@ -262,6 +270,8 @@ template T basic(const Input& input, INIT init, double lambda) { lambda * static_cast(regrets[v][job_rank]); if (current_cost < best_cost and + (current_route_duration + current_add.duration <= + vehicle.max_travel_time) and current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -271,6 +281,7 @@ template T basic(const Input& input, INIT init, double lambda) { best_cost = current_cost; best_job_rank = job_rank; best_r = r; + best_duration_addition = current_add.duration; } } } @@ -351,7 +362,11 @@ template T basic(const Input& input, INIT init, double lambda) { modified_with_pd.push_back(job_rank + 1); // Update best cost depending on validity. - bool valid = + bool valid = (current_route_duration + current_add.duration <= + vehicle.max_travel_time); + + valid = + valid && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -377,6 +392,7 @@ template T basic(const Input& input, INIT init, double lambda) { best_job_rank = job_rank; best_pickup_r = pickup_r; best_delivery_r = delivery_r; + best_duration_addition = current_add.duration; } } } @@ -406,6 +422,8 @@ template T basic(const Input& input, INIT init, double lambda) { unassigned.erase(best_job_rank + 1); keep_going = true; } + + current_route_duration += best_duration_addition; } } } From 240d36c13f7c2f99ef9a726ca77f569a4fb568fe Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 7 Sep 2022 14:48:40 +0200 Subject: [PATCH 07/40] Don't go over max_travel_time in dynamic heuristic. --- src/algorithms/heuristics/heuristics.cpp | 26 ++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 41272eb11..01c4b530a 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -515,6 +515,8 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { const auto& vehicle = input.vehicles[v_rank]; auto& current_r = routes[v_rank]; + Duration current_route_duration = 0; + if (init != INIT::NONE) { // Initialize current route with the "best" valid job that is // closest for current vehicle than to any other remaining @@ -564,6 +566,10 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { } bool is_valid = + (evals[job_rank][v_rank].duration <= vehicle.max_travel_time); + + is_valid = + is_valid && current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -626,6 +632,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { unassigned.erase(best_job_rank); unassigned.erase(best_job_rank + 1); } + current_route_duration += evals[best_job_rank][v_rank].duration; } } @@ -637,6 +644,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { Index best_r = 0; Index best_pickup_r = 0; Index best_delivery_r = 0; + Duration best_duration_addition = 0; for (const auto job_rank : unassigned) { if (!input.vehicle_ok_with_job(v_rank, job_rank)) { @@ -661,6 +669,8 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { lambda * static_cast(regrets[job_rank]); if (current_cost < best_cost and + (current_route_duration + current_add.duration <= + vehicle.max_travel_time) and current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -670,6 +680,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { best_cost = current_cost; best_job_rank = job_rank; best_r = r; + best_duration_addition = current_add.duration; } } } @@ -750,7 +761,11 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { modified_with_pd.push_back(job_rank + 1); // Update best cost depending on validity. - bool is_valid = + bool valid = (current_route_duration + current_add.duration <= + vehicle.max_travel_time); + + valid = + valid && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -761,8 +776,8 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { pickup_r, delivery_r); - is_valid = - is_valid && + valid = + valid && current_r.is_valid_addition_for_tw(input, modified_with_pd.begin(), modified_with_pd.end(), @@ -771,11 +786,12 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { modified_with_pd.pop_back(); - if (is_valid) { + if (valid) { best_cost = current_cost; best_job_rank = job_rank; best_pickup_r = pickup_r; best_delivery_r = delivery_r; + best_duration_addition = current_add.duration; } } } @@ -805,6 +821,8 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { unassigned.erase(best_job_rank + 1); keep_going = true; } + + current_route_duration += best_duration_addition; } } } From a18a607c75b611e3e3774c79dfff785d355f8f41 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 7 Sep 2022 16:12:11 +0200 Subject: [PATCH 08/40] Make sure solution_state costs are consistent in try_job_additions and remove_from_route. --- src/algorithms/local_search/local_search.cpp | 27 +++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 9208c9c9d..103a14b32 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -268,7 +268,8 @@ void LocalSearch Date: Thu, 8 Sep 2022 11:34:44 +0200 Subject: [PATCH 09/40] Always run update_route_eval even outside debug mode. --- src/algorithms/local_search/local_search.cpp | 22 +++++++++----------- src/structures/vroom/solution_state.cpp | 12 ++--------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 103a14b32..be7e94487 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -269,7 +269,7 @@ void LocalSearch void SolutionState::setup(const Route& r, Index v) { @@ -54,9 +50,7 @@ template void SolutionState::setup(const Route& r, Index v) { set_pd_matching_ranks(r.route, v); set_pd_gains(r.route, v); set_insertion_ranks(r, v); -#ifndef NDEBUG update_route_eval(r.route, v); -#endif } template void SolutionState::setup(const Solution& sol) { @@ -615,12 +609,10 @@ void SolutionState::update_cheapest_job_rank_in_routes( } } -#ifndef NDEBUG void SolutionState::update_route_eval(const std::vector& route, Index v) { route_evals[v] = route_eval_for_vehicle(_input, v, route); } -#endif template void SolutionState::setup(const std::vector&); template void SolutionState::setup(const std::vector&); From 1986132eb45b009a166f44132374ced61ebacb6e Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 8 Sep 2022 14:06:27 +0200 Subject: [PATCH 10/40] Check max_travel_time validity in Relocate. --- src/algorithms/local_search/operator.h | 2 ++ src/problems/cvrp/operators/relocate.cpp | 35 +++++++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/algorithms/local_search/operator.h b/src/algorithms/local_search/operator.h index 2c74ce70f..561a156d5 100644 --- a/src/algorithms/local_search/operator.h +++ b/src/algorithms/local_search/operator.h @@ -35,6 +35,8 @@ class Operator { const Index t_rank; bool gain_computed; + Eval s_gain; + Eval t_gain; Eval stored_gain; virtual void compute_gain() = 0; diff --git a/src/problems/cvrp/operators/relocate.cpp b/src/problems/cvrp/operators/relocate.cpp index cb515433a..b4263221c 100644 --- a/src/problems/cvrp/operators/relocate.cpp +++ b/src/problems/cvrp/operators/relocate.cpp @@ -42,24 +42,39 @@ void Relocate::compute_gain() { const auto& v = _input.vehicles[t_vehicle]; // For source vehicle, we consider the cost of removing job at rank - // s_rank, already stored in - // _sol_state.node_gains[s_vehicle][s_rank]. + // s_rank, already stored. + s_gain = _sol_state.node_gains[s_vehicle][s_rank]; // For target vehicle, we consider the cost of adding source job at // rank t_rank. - Eval t_gain = - -utils::addition_cost(_input, s_route[s_rank], v, t_route, t_rank); + t_gain = -utils::addition_cost(_input, s_route[s_rank], v, t_route, t_rank); - stored_gain = _sol_state.node_gains[s_vehicle][s_rank] + t_gain; + stored_gain = s_gain + t_gain; gain_computed = true; } bool Relocate::is_valid() { - return target - .is_valid_addition_for_capacity(_input, - _input.jobs[s_route[s_rank]].pickup, - _input.jobs[s_route[s_rank]].delivery, - t_rank); + assert(gain_computed); + + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + bool valid = (s_travel_time <= s_v.max_travel_time + s_gain.duration); + + if (valid) { + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + + valid = (t_travel_time <= t_v.max_travel_time + t_gain.duration); + } + + valid = + valid && + target.is_valid_addition_for_capacity(_input, + _input.jobs[s_route[s_rank]].pickup, + _input.jobs[s_route[s_rank]].delivery, + t_rank); + + return valid; } void Relocate::apply() { From 5c514c624b552e389828296a2872f71a3395905a Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 8 Sep 2022 14:17:33 +0200 Subject: [PATCH 11/40] Check max_travel_time validity in OrOpt. --- src/problems/cvrp/operators/or_opt.cpp | 23 ++++++++++++++++------- src/problems/cvrp/operators/or_opt.h | 1 - 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/problems/cvrp/operators/or_opt.cpp b/src/problems/cvrp/operators/or_opt.cpp index d751c9ffb..cf5eaeeca 100644 --- a/src/problems/cvrp/operators/or_opt.cpp +++ b/src/problems/cvrp/operators/or_opt.cpp @@ -108,7 +108,7 @@ Eval OrOpt::gain_upper_bound() { } // Gain for source vehicle, including cost of moved edge. - _s_gain = + s_gain = _sol_state.edge_gains[s_vehicle][s_rank] + s_v.eval(s_index, after_s_index); // Gain for target vehicle, including cost of moved edge. @@ -120,14 +120,14 @@ Eval OrOpt::gain_upper_bound() { _gain_upper_bound_computed = true; - return _s_gain + std::max(_normal_t_gain, _reversed_t_gain); + return s_gain + std::max(_normal_t_gain, _reversed_t_gain); } void OrOpt::compute_gain() { assert(_gain_upper_bound_computed); assert(is_normal_valid or is_reverse_valid); - stored_gain = _s_gain; + stored_gain = s_gain; if (_reversed_t_gain > _normal_t_gain) { // Biggest potential gain is obtained when reversing edge. @@ -151,21 +151,29 @@ void OrOpt::compute_gain() { } bool OrOpt::is_valid() { + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + bool valid = (s_travel_time <= s_v.max_travel_time + s_gain.duration); + auto edge_pickup = _input.jobs[s_route[s_rank]].pickup + _input.jobs[s_route[s_rank + 1]].pickup; auto edge_delivery = _input.jobs[s_route[s_rank]].delivery + _input.jobs[s_route[s_rank + 1]].delivery; - bool valid = target.is_valid_addition_for_capacity(_input, - edge_pickup, - edge_delivery, - t_rank); + valid = target.is_valid_addition_for_capacity(_input, + edge_pickup, + edge_delivery, + t_rank); if (valid) { // Keep edge direction. auto s_start = s_route.begin() + s_rank; + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + is_normal_valid = + (t_travel_time <= t_v.max_travel_time + _normal_t_gain.duration) and target.is_valid_addition_for_capacity_inclusion(_input, edge_delivery, s_start, @@ -176,6 +184,7 @@ bool OrOpt::is_valid() { // Reverse edge direction. auto s_reverse_start = s_route.rbegin() + s_route.size() - 2 - s_rank; is_reverse_valid = + (t_travel_time <= t_v.max_travel_time + _reversed_t_gain.duration) and target.is_valid_addition_for_capacity_inclusion(_input, edge_delivery, s_reverse_start, diff --git a/src/problems/cvrp/operators/or_opt.h b/src/problems/cvrp/operators/or_opt.h index 71f5ad162..dcd4c9f9b 100644 --- a/src/problems/cvrp/operators/or_opt.h +++ b/src/problems/cvrp/operators/or_opt.h @@ -18,7 +18,6 @@ namespace cvrp { class OrOpt : public ls::Operator { private: bool _gain_upper_bound_computed; - Eval _s_gain; Eval _normal_t_gain; Eval _reversed_t_gain; From 1f6ff97549c1d317044b78629369321c2493d7fd Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 8 Sep 2022 14:34:51 +0200 Subject: [PATCH 12/40] Check max_travel_time validity in MixedExchange. --- .../cvrp/operators/mixed_exchange.cpp | 21 ++++++++++++++----- src/problems/cvrp/operators/mixed_exchange.h | 1 - 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/problems/cvrp/operators/mixed_exchange.cpp b/src/problems/cvrp/operators/mixed_exchange.cpp index f23041e6e..e5c0da986 100644 --- a/src/problems/cvrp/operators/mixed_exchange.cpp +++ b/src/problems/cvrp/operators/mixed_exchange.cpp @@ -141,12 +141,12 @@ Eval MixedExchange::gain_upper_bound() { next_cost = t_v.eval(s_index, n_index); } - _t_gain = _sol_state.edge_evals_around_edge[t_vehicle][t_rank] + - t_v.eval(t_index, t_after_index) - previous_cost - next_cost; + t_gain = _sol_state.edge_evals_around_edge[t_vehicle][t_rank] + + t_v.eval(t_index, t_after_index) - previous_cost - next_cost; _gain_upper_bound_computed = true; - return s_gain_upper_bound + _t_gain; + return s_gain_upper_bound + t_gain; } void MixedExchange::compute_gain() { @@ -170,13 +170,19 @@ void MixedExchange::compute_gain() { } } - stored_gain += _t_gain; + stored_gain += t_gain; gain_computed = true; } bool MixedExchange::is_valid() { - bool valid = + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + + bool valid = (t_travel_time <= t_v.max_travel_time + t_gain.duration); + + valid = + valid && target.is_valid_addition_for_capacity_margins(_input, _input.jobs[s_route[s_rank]] .pickup, @@ -201,7 +207,11 @@ bool MixedExchange::is_valid() { // Keep target edge direction when inserting in source route. auto t_start = t_route.begin() + t_rank; + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + s_is_normal_valid = + (s_travel_time <= s_v.max_travel_time + _normal_s_gain.duration) and source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_start, @@ -212,6 +222,7 @@ bool MixedExchange::is_valid() { // Reverse target edge direction when inserting in source route. auto t_reverse_start = t_route.rbegin() + t_route.size() - 2 - t_rank; s_is_reverse_valid = + (s_travel_time <= s_v.max_travel_time + _reversed_s_gain.duration) and source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_reverse_start, diff --git a/src/problems/cvrp/operators/mixed_exchange.h b/src/problems/cvrp/operators/mixed_exchange.h index 78551f339..72213462e 100644 --- a/src/problems/cvrp/operators/mixed_exchange.h +++ b/src/problems/cvrp/operators/mixed_exchange.h @@ -20,7 +20,6 @@ class MixedExchange : public ls::Operator { bool _gain_upper_bound_computed; Eval _normal_s_gain; Eval _reversed_s_gain; - Eval _t_gain; protected: bool reverse_t_edge; From 5766936249b4e872ccab49770efbc90ae39da8df Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 8 Sep 2022 14:46:22 +0200 Subject: [PATCH 13/40] Check max_travel_time validity in CrossExchange. --- src/problems/cvrp/operators/cross_exchange.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/problems/cvrp/operators/cross_exchange.cpp b/src/problems/cvrp/operators/cross_exchange.cpp index 6e8acd9af..9d45600a7 100644 --- a/src/problems/cvrp/operators/cross_exchange.cpp +++ b/src/problems/cvrp/operators/cross_exchange.cpp @@ -240,9 +240,13 @@ bool CrossExchange::is_valid() { s_rank + 2); if (valid) { + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + // Keep target edge direction when inserting in source route. auto t_start = t_route.begin() + t_rank; s_is_normal_valid = + (s_travel_time <= s_v.max_travel_time + _normal_s_gain.duration) and source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_start, @@ -254,6 +258,7 @@ bool CrossExchange::is_valid() { // Reverse target edge direction when inserting in source route. auto t_reverse_start = t_route.rbegin() + t_route.size() - 2 - t_rank; s_is_reverse_valid = + (s_travel_time <= s_v.max_travel_time + _reversed_s_gain.duration) and source.is_valid_addition_for_capacity_inclusion(_input, target_delivery, t_reverse_start, @@ -278,9 +283,13 @@ bool CrossExchange::is_valid() { t_rank + 2); if (valid) { + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + // Keep source edge direction when inserting in target route. auto s_start = s_route.begin() + s_rank; t_is_normal_valid = + (t_travel_time <= t_v.max_travel_time + _normal_t_gain.duration) and target.is_valid_addition_for_capacity_inclusion(_input, source_delivery, s_start, @@ -292,6 +301,7 @@ bool CrossExchange::is_valid() { // Reverse source edge direction when inserting in target route. auto s_reverse_start = s_route.rbegin() + s_route.size() - 2 - s_rank; t_is_reverse_valid = + (t_travel_time <= t_v.max_travel_time + _reversed_t_gain.duration) and target.is_valid_addition_for_capacity_inclusion(_input, source_delivery, s_reverse_start, From 43efafcb12ae4f7e1c7176a582c09019d5694f7d Mon Sep 17 00:00:00 2001 From: jcoupey Date: Fri, 9 Sep 2022 10:54:55 +0200 Subject: [PATCH 14/40] Split source and target gain in ReverseTwoOpt::compute_gain. --- .../cvrp/operators/reverse_two_opt.cpp | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/problems/cvrp/operators/reverse_two_opt.cpp b/src/problems/cvrp/operators/reverse_two_opt.cpp index b3c2eecf1..b1b5f47f1 100644 --- a/src/problems/cvrp/operators/reverse_two_opt.cpp +++ b/src/problems/cvrp/operators/reverse_two_opt.cpp @@ -47,7 +47,7 @@ void ReverseTwoOpt::compute_gain() { Index t_index = _input.jobs[t_route[t_rank]].index(); Index last_s = _input.jobs[s_route.back()].index(); Index first_t = _input.jobs[t_route.front()].index(); - stored_gain = Eval(); + bool last_in_source = (s_rank == s_route.size() - 1); bool last_in_target = (t_rank == t_route.size() - 1); @@ -56,25 +56,25 @@ void ReverseTwoOpt::compute_gain() { // t_rank, but reversed. // Add new source -> target edge. - stored_gain -= s_v.eval(s_index, t_index); + s_gain -= s_v.eval(s_index, t_index); // Cost of reversing target route portion. First remove forward cost // for beginning of target route as seen from target vehicle. Then // add backward cost for beginning of target route as seen from // source vehicle since it's the new source route end. - stored_gain += _sol_state.fwd_costs[t_vehicle][t_vehicle][t_rank]; - stored_gain -= _sol_state.bwd_costs[t_vehicle][s_vehicle][t_rank]; + t_gain += _sol_state.fwd_costs[t_vehicle][t_vehicle][t_rank]; + s_gain -= _sol_state.bwd_costs[t_vehicle][s_vehicle][t_rank]; if (!last_in_target) { // Spare next edge in target route. Index next_index = _input.jobs[t_route[t_rank + 1]].index(); - stored_gain += t_v.eval(t_index, next_index); + t_gain += t_v.eval(t_index, next_index); } if (!last_in_source) { // Spare next edge in source route. Index next_index = _input.jobs[s_route[s_rank + 1]].index(); - stored_gain += s_v.eval(s_index, next_index); + s_gain += s_v.eval(s_index, next_index); // Part of source route is moved to target route. Index next_s_index = _input.jobs[s_route[s_rank + 1]].index(); @@ -84,55 +84,56 @@ void ReverseTwoOpt::compute_gain() { // (subtracting intermediate cost to overall cost). Then add // backward cost for end of source route as seen from target // vehicle since it's the new target route start. - stored_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); - stored_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank + 1]; - stored_gain -= _sol_state.bwd_costs[s_vehicle][t_vehicle].back(); - stored_gain += _sol_state.bwd_costs[s_vehicle][t_vehicle][s_rank + 1]; + s_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); + s_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank + 1]; + t_gain -= _sol_state.bwd_costs[s_vehicle][t_vehicle].back(); + t_gain += _sol_state.bwd_costs[s_vehicle][t_vehicle][s_rank + 1]; if (last_in_target) { if (t_v.has_end()) { // Handle target route new end. auto end_t = t_v.end.value().index(); - stored_gain += t_v.eval(t_index, end_t); - stored_gain -= t_v.eval(next_s_index, end_t); + t_gain += t_v.eval(t_index, end_t); + t_gain -= t_v.eval(next_s_index, end_t); } } else { // Add new target -> source edge. Index next_t_index = _input.jobs[t_route[t_rank + 1]].index(); - stored_gain -= t_v.eval(next_s_index, next_t_index); + t_gain -= t_v.eval(next_s_index, next_t_index); } } if (s_v.has_end()) { // Update cost to source end because last job changed. auto end_s = s_v.end.value().index(); - stored_gain += s_v.eval(last_s, end_s); - stored_gain -= s_v.eval(first_t, end_s); + s_gain += s_v.eval(last_s, end_s); + s_gain -= s_v.eval(first_t, end_s); } if (t_v.has_start()) { // Spare cost from target start because first job changed. auto start_t = t_v.start.value().index(); - stored_gain += t_v.eval(start_t, first_t); + t_gain += t_v.eval(start_t, first_t); if (!last_in_source) { - stored_gain -= t_v.eval(start_t, last_s); + t_gain -= t_v.eval(start_t, last_s); } else { // No job from source route actually swapped to target route. if (!last_in_target) { // Going straight from start to next job in target route. Index next_index = _input.jobs[t_route[t_rank + 1]].index(); - stored_gain -= t_v.eval(start_t, next_index); + t_gain -= t_v.eval(start_t, next_index); } else { // Emptying the whole target route here, so also gaining cost // to end if it exists. if (t_v.has_end()) { auto end_t = t_v.end.value().index(); - stored_gain += t_v.eval(t_index, end_t); + t_gain += t_v.eval(t_index, end_t); } } } } + stored_gain = s_gain + t_gain; gain_computed = true; } From 26d025a9d68d28d30ad26384e65bad082bc60b75 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Fri, 9 Sep 2022 11:00:08 +0200 Subject: [PATCH 15/40] Check max_travel_time validity in ReverseTwoOpt. --- src/problems/cvrp/operators/reverse_two_opt.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/problems/cvrp/operators/reverse_two_opt.cpp b/src/problems/cvrp/operators/reverse_two_opt.cpp index b1b5f47f1..d19f90224 100644 --- a/src/problems/cvrp/operators/reverse_two_opt.cpp +++ b/src/problems/cvrp/operators/reverse_two_opt.cpp @@ -156,6 +156,14 @@ bool ReverseTwoOpt::is_valid() { 0, t_rank + 1); + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + valid = valid && (s_travel_time <= s_v.max_travel_time + s_gain.duration); + + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + valid = valid && (t_travel_time <= t_v.max_travel_time + t_gain.duration); + valid = valid && source.is_valid_addition_for_capacity_inclusion(_input, t_delivery, From edfbb0c4032009351a1b01e420e9d19476113eab Mon Sep 17 00:00:00 2001 From: jcoupey Date: Fri, 9 Sep 2022 17:17:31 +0200 Subject: [PATCH 16/40] Move default max_travel_time checks to dedicated Operator function members. --- src/algorithms/local_search/operator.cpp | 12 ++++ src/algorithms/local_search/operator.h | 4 ++ .../cvrp/operators/cross_exchange.cpp | 2 + .../cvrp/operators/mixed_exchange.cpp | 9 +-- src/problems/cvrp/operators/or_opt.cpp | 13 ++-- src/problems/cvrp/operators/relocate.cpp | 28 +++----- .../cvrp/operators/reverse_two_opt.cpp | 71 ++++++++----------- 7 files changed, 65 insertions(+), 74 deletions(-) diff --git a/src/algorithms/local_search/operator.cpp b/src/algorithms/local_search/operator.cpp index feb5229ed..8161c9dab 100644 --- a/src/algorithms/local_search/operator.cpp +++ b/src/algorithms/local_search/operator.cpp @@ -23,6 +23,18 @@ Eval Operator::gain() { return stored_gain; } +bool Operator::is_valid_for_source_max_travel_time() const { + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + return s_travel_time <= s_v.max_travel_time + s_gain.duration; +} + +bool Operator::is_valid_for_target_max_travel_time() const { + const auto& t_v = _input.vehicles[t_vehicle]; + const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; + return t_travel_time <= t_v.max_travel_time + t_gain.duration; +} + std::vector Operator::required_unassigned() const { return std::vector(); } diff --git a/src/algorithms/local_search/operator.h b/src/algorithms/local_search/operator.h index 561a156d5..7f776c736 100644 --- a/src/algorithms/local_search/operator.h +++ b/src/algorithms/local_search/operator.h @@ -41,6 +41,10 @@ class Operator { virtual void compute_gain() = 0; + bool is_valid_for_source_max_travel_time() const; + + bool is_valid_for_target_max_travel_time() const; + public: Operator(OperatorName name, const Input& input, diff --git a/src/problems/cvrp/operators/cross_exchange.cpp b/src/problems/cvrp/operators/cross_exchange.cpp index 9d45600a7..b3f647de4 100644 --- a/src/problems/cvrp/operators/cross_exchange.cpp +++ b/src/problems/cvrp/operators/cross_exchange.cpp @@ -228,6 +228,8 @@ void CrossExchange::compute_gain() { } bool CrossExchange::is_valid() { + assert(_gain_upper_bound_computed); + auto target_pickup = _input.jobs[t_route[t_rank]].pickup + _input.jobs[t_route[t_rank + 1]].pickup; auto target_delivery = _input.jobs[t_route[t_rank]].delivery + diff --git a/src/problems/cvrp/operators/mixed_exchange.cpp b/src/problems/cvrp/operators/mixed_exchange.cpp index e5c0da986..0a0dd02d3 100644 --- a/src/problems/cvrp/operators/mixed_exchange.cpp +++ b/src/problems/cvrp/operators/mixed_exchange.cpp @@ -176,13 +176,10 @@ void MixedExchange::compute_gain() { } bool MixedExchange::is_valid() { - const auto& t_v = _input.vehicles[t_vehicle]; - const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; - - bool valid = (t_travel_time <= t_v.max_travel_time + t_gain.duration); + assert(_gain_upper_bound_computed); - valid = - valid && + bool valid = + is_valid_for_target_max_travel_time() && target.is_valid_addition_for_capacity_margins(_input, _input.jobs[s_route[s_rank]] .pickup, diff --git a/src/problems/cvrp/operators/or_opt.cpp b/src/problems/cvrp/operators/or_opt.cpp index cf5eaeeca..9cb1a30d4 100644 --- a/src/problems/cvrp/operators/or_opt.cpp +++ b/src/problems/cvrp/operators/or_opt.cpp @@ -151,19 +151,18 @@ void OrOpt::compute_gain() { } bool OrOpt::is_valid() { - const auto& s_v = _input.vehicles[s_vehicle]; - const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; - bool valid = (s_travel_time <= s_v.max_travel_time + s_gain.duration); + assert(_gain_upper_bound_computed); auto edge_pickup = _input.jobs[s_route[s_rank]].pickup + _input.jobs[s_route[s_rank + 1]].pickup; auto edge_delivery = _input.jobs[s_route[s_rank]].delivery + _input.jobs[s_route[s_rank + 1]].delivery; - valid = target.is_valid_addition_for_capacity(_input, - edge_pickup, - edge_delivery, - t_rank); + bool valid = is_valid_for_source_max_travel_time() && + target.is_valid_addition_for_capacity(_input, + edge_pickup, + edge_delivery, + t_rank); if (valid) { // Keep edge direction. diff --git a/src/problems/cvrp/operators/relocate.cpp b/src/problems/cvrp/operators/relocate.cpp index b4263221c..7d8109315 100644 --- a/src/problems/cvrp/operators/relocate.cpp +++ b/src/problems/cvrp/operators/relocate.cpp @@ -55,26 +55,14 @@ void Relocate::compute_gain() { bool Relocate::is_valid() { assert(gain_computed); - - const auto& s_v = _input.vehicles[s_vehicle]; - const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; - bool valid = (s_travel_time <= s_v.max_travel_time + s_gain.duration); - - if (valid) { - const auto& t_v = _input.vehicles[t_vehicle]; - const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; - - valid = (t_travel_time <= t_v.max_travel_time + t_gain.duration); - } - - valid = - valid && - target.is_valid_addition_for_capacity(_input, - _input.jobs[s_route[s_rank]].pickup, - _input.jobs[s_route[s_rank]].delivery, - t_rank); - - return valid; + return is_valid_for_source_max_travel_time() && + is_valid_for_target_max_travel_time() && + target + .is_valid_addition_for_capacity(_input, + _input.jobs[s_route[s_rank]].pickup, + _input.jobs[s_route[s_rank]] + .delivery, + t_rank); } void Relocate::apply() { diff --git a/src/problems/cvrp/operators/reverse_two_opt.cpp b/src/problems/cvrp/operators/reverse_two_opt.cpp index d19f90224..9131a50a2 100644 --- a/src/problems/cvrp/operators/reverse_two_opt.cpp +++ b/src/problems/cvrp/operators/reverse_two_opt.cpp @@ -138,53 +138,42 @@ void ReverseTwoOpt::compute_gain() { } bool ReverseTwoOpt::is_valid() { + assert(gain_computed); + const auto& t_delivery = target.fwd_deliveries(t_rank); const auto& t_pickup = target.fwd_pickups(t_rank); - bool valid = source.is_valid_addition_for_capacity_margins(_input, - t_pickup, - t_delivery, - s_rank + 1, - s_route.size()); - const auto& s_delivery = source.bwd_deliveries(s_rank); const auto& s_pickup = source.bwd_pickups(s_rank); - valid = valid && target.is_valid_addition_for_capacity_margins(_input, - s_pickup, - s_delivery, - 0, - t_rank + 1); - - const auto& s_v = _input.vehicles[s_vehicle]; - const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; - valid = valid && (s_travel_time <= s_v.max_travel_time + s_gain.duration); - - const auto& t_v = _input.vehicles[t_vehicle]; - const auto t_travel_time = _sol_state.route_evals[t_vehicle].duration; - valid = valid && (t_travel_time <= t_v.max_travel_time + t_gain.duration); - - valid = - valid && source.is_valid_addition_for_capacity_inclusion(_input, - t_delivery, - t_route.rbegin() + - t_route.size() - - 1 - t_rank, - t_route.rend(), - s_rank + 1, - s_route.size()); - - valid = - valid && target.is_valid_addition_for_capacity_inclusion(_input, - s_delivery, - s_route.rbegin(), - s_route.rbegin() + - s_route.size() - - 1 - s_rank, - 0, - t_rank + 1); - - return valid; + return is_valid_for_source_max_travel_time() && + is_valid_for_target_max_travel_time() && + source.is_valid_addition_for_capacity_margins(_input, + t_pickup, + t_delivery, + s_rank + 1, + s_route.size()) && + target.is_valid_addition_for_capacity_margins(_input, + s_pickup, + s_delivery, + 0, + t_rank + 1) && + source.is_valid_addition_for_capacity_inclusion(_input, + t_delivery, + t_route.rbegin() + + t_route.size() - 1 - + t_rank, + t_route.rend(), + s_rank + 1, + s_route.size()) && + target.is_valid_addition_for_capacity_inclusion(_input, + s_delivery, + s_route.rbegin(), + s_route.rbegin() + + s_route.size() - 1 - + s_rank, + 0, + t_rank + 1); } void ReverseTwoOpt::apply() { From 0cfe7708d8663d3b7ccd7eb7d1ead09b54ebf200 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Mon, 12 Sep 2022 15:52:52 +0200 Subject: [PATCH 17/40] Check max_travel_time validity in IntraOrOpt. --- src/problems/cvrp/operators/intra_or_opt.cpp | 57 ++++++++++++-------- src/problems/cvrp/operators/intra_or_opt.h | 1 - 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/problems/cvrp/operators/intra_or_opt.cpp b/src/problems/cvrp/operators/intra_or_opt.cpp index c22fbf507..b4a7f09c8 100644 --- a/src/problems/cvrp/operators/intra_or_opt.cpp +++ b/src/problems/cvrp/operators/intra_or_opt.cpp @@ -127,7 +127,7 @@ Eval IntraOrOpt::gain_upper_bound() { } // Gain for source vehicle. - _s_gain = _sol_state.edge_gains[s_vehicle][s_rank]; + s_gain = _sol_state.edge_gains[s_vehicle][s_rank]; // Gain for target vehicle. _normal_t_gain = old_edge_cost - previous_cost - next_cost; @@ -145,14 +145,14 @@ Eval IntraOrOpt::gain_upper_bound() { _gain_upper_bound_computed = true; - return _s_gain + t_gain_upper_bound; + return s_gain + t_gain_upper_bound; } void IntraOrOpt::compute_gain() { assert(_gain_upper_bound_computed); assert(is_normal_valid or is_reverse_valid); - stored_gain = _s_gain; + stored_gain = s_gain; if (_reversed_t_gain > _normal_t_gain) { // Biggest potential gain is obtained when reversing edge. @@ -176,28 +176,39 @@ void IntraOrOpt::compute_gain() { } bool IntraOrOpt::is_valid() { - is_normal_valid = source.is_valid_addition_for_capacity_inclusion( - _input, - source.delivery_in_range(_first_rank, _last_rank), - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); + assert(_gain_upper_bound_computed); + + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + const auto normal_duration = s_gain.duration + _normal_t_gain.duration; + + is_normal_valid = (s_travel_time <= s_v.max_travel_time + normal_duration) and + source.is_valid_addition_for_capacity_inclusion( + _input, + source.delivery_in_range(_first_rank, _last_rank), + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); if (check_reverse) { - std::swap(_moved_jobs[_s_edge_first], _moved_jobs[_s_edge_last]); - - is_reverse_valid = source.is_valid_addition_for_capacity_inclusion( - _input, - source.delivery_in_range(_first_rank, _last_rank), - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); - - // Reset to initial situation before potential application or TW - // checks. - std::swap(_moved_jobs[_s_edge_first], _moved_jobs[_s_edge_last]); + const auto reversed_duration = s_gain.duration + _reversed_t_gain.duration; + + if (s_travel_time <= s_v.max_travel_time + reversed_duration) { + std::swap(_moved_jobs[_s_edge_first], _moved_jobs[_s_edge_last]); + + is_reverse_valid = source.is_valid_addition_for_capacity_inclusion( + _input, + source.delivery_in_range(_first_rank, _last_rank), + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); + + // Reset to initial situation before potential application or TW + // checks. + std::swap(_moved_jobs[_s_edge_first], _moved_jobs[_s_edge_last]); + } } return is_normal_valid or is_reverse_valid; diff --git a/src/problems/cvrp/operators/intra_or_opt.h b/src/problems/cvrp/operators/intra_or_opt.h index 2b8a95def..2b652b99b 100644 --- a/src/problems/cvrp/operators/intra_or_opt.h +++ b/src/problems/cvrp/operators/intra_or_opt.h @@ -18,7 +18,6 @@ namespace cvrp { class IntraOrOpt : public ls::Operator { private: bool _gain_upper_bound_computed; - Eval _s_gain; Eval _normal_t_gain; Eval _reversed_t_gain; From 8defa506f125c4272f270059316ce6872ade0f80 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Mon, 12 Sep 2022 16:01:23 +0200 Subject: [PATCH 18/40] Split source and target gain in TwoOpt::compute_gain. --- src/problems/cvrp/operators/two_opt.cpp | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/problems/cvrp/operators/two_opt.cpp b/src/problems/cvrp/operators/two_opt.cpp index c5d99b4d8..9d6bc5757 100644 --- a/src/problems/cvrp/operators/two_opt.cpp +++ b/src/problems/cvrp/operators/two_opt.cpp @@ -47,7 +47,7 @@ void TwoOpt::compute_gain() { Index t_index = _input.jobs[t_route[t_rank]].index(); Index last_s = _input.jobs[s_route.back()].index(); Index last_t = _input.jobs[t_route.back()].index(); - stored_gain = Eval(); + Index new_last_s = last_t; Index new_last_t = last_s; @@ -59,31 +59,31 @@ void TwoOpt::compute_gain() { // the route. Otherwise remember that last job does not change. if (s_rank < s_route.size() - 1) { Index next_index = _input.jobs[s_route[s_rank + 1]].index(); - stored_gain += s_v.eval(s_index, next_index); - stored_gain -= t_v.eval(t_index, next_index); + s_gain += s_v.eval(s_index, next_index); + t_gain -= t_v.eval(t_index, next_index); // Account for the change in cost across vehicles for the end of // source route. Cost of remaining route retrieved by subtracting // intermediate cost to overall cost. - stored_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); - stored_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank + 1]; - stored_gain -= _sol_state.fwd_costs[s_vehicle][t_vehicle].back(); - stored_gain += _sol_state.fwd_costs[s_vehicle][t_vehicle][s_rank + 1]; + s_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); + s_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank + 1]; + t_gain -= _sol_state.fwd_costs[s_vehicle][t_vehicle].back(); + t_gain += _sol_state.fwd_costs[s_vehicle][t_vehicle][s_rank + 1]; } else { new_last_t = t_index; } if (t_rank < t_route.size() - 1) { Index next_index = _input.jobs[t_route[t_rank + 1]].index(); - stored_gain += t_v.eval(t_index, next_index); - stored_gain -= s_v.eval(s_index, next_index); + t_gain += t_v.eval(t_index, next_index); + s_gain -= s_v.eval(s_index, next_index); // Account for the change in cost across vehicles for the end of // target route. Cost of remaining route retrieved by subtracting // intermediate cost to overall cost. - stored_gain += _sol_state.fwd_costs[t_vehicle][t_vehicle].back(); - stored_gain -= _sol_state.fwd_costs[t_vehicle][t_vehicle][t_rank + 1]; - stored_gain -= _sol_state.fwd_costs[t_vehicle][s_vehicle].back(); - stored_gain += _sol_state.fwd_costs[t_vehicle][s_vehicle][t_rank + 1]; + t_gain += _sol_state.fwd_costs[t_vehicle][t_vehicle].back(); + t_gain -= _sol_state.fwd_costs[t_vehicle][t_vehicle][t_rank + 1]; + s_gain -= _sol_state.fwd_costs[t_vehicle][s_vehicle].back(); + s_gain += _sol_state.fwd_costs[t_vehicle][s_vehicle][t_rank + 1]; } else { new_last_s = s_index; } @@ -92,15 +92,16 @@ void TwoOpt::compute_gain() { // different or none. if (s_v.has_end()) { auto end_s = s_v.end.value().index(); - stored_gain += s_v.eval(last_s, end_s); - stored_gain -= s_v.eval(new_last_s, end_s); + s_gain += s_v.eval(last_s, end_s); + s_gain -= s_v.eval(new_last_s, end_s); } if (t_v.has_end()) { auto end_t = t_v.end.value().index(); - stored_gain += t_v.eval(last_t, end_t); - stored_gain -= t_v.eval(new_last_t, end_t); + t_gain += t_v.eval(last_t, end_t); + t_gain -= t_v.eval(new_last_t, end_t); } + stored_gain = s_gain + t_gain; gain_computed = true; } From 3ffef0c637aa4cfa135391f0d3e9e7f4df6a8263 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Mon, 12 Sep 2022 16:07:00 +0200 Subject: [PATCH 19/40] Check max_travel_time validity in TwoOpt. --- src/problems/cvrp/operators/two_opt.cpp | 58 ++++++++++++------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/src/problems/cvrp/operators/two_opt.cpp b/src/problems/cvrp/operators/two_opt.cpp index 9d6bc5757..762a4f28e 100644 --- a/src/problems/cvrp/operators/two_opt.cpp +++ b/src/problems/cvrp/operators/two_opt.cpp @@ -106,44 +106,40 @@ void TwoOpt::compute_gain() { } bool TwoOpt::is_valid() { + assert(gain_computed); + const auto& t_delivery = target.bwd_deliveries(t_rank); const auto& t_pickup = target.bwd_pickups(t_rank); - bool valid = source.is_valid_addition_for_capacity_margins(_input, - t_pickup, - t_delivery, - s_rank + 1, - s_route.size()); - const auto& s_delivery = source.bwd_deliveries(s_rank); const auto& s_pickup = source.bwd_pickups(s_rank); - valid = - valid && target.is_valid_addition_for_capacity_margins(_input, - s_pickup, - s_delivery, + return is_valid_for_source_max_travel_time() && + is_valid_for_target_max_travel_time() && + source.is_valid_addition_for_capacity_margins(_input, + t_pickup, + t_delivery, + s_rank + 1, + s_route.size()) && + target.is_valid_addition_for_capacity_margins(_input, + s_pickup, + s_delivery, + t_rank + 1, + t_route.size()) && + source.is_valid_addition_for_capacity_inclusion(_input, + t_delivery, + t_route.begin() + t_rank + 1, - t_route.size()); - - valid = - valid && source.is_valid_addition_for_capacity_inclusion(_input, - t_delivery, - t_route.begin() + - t_rank + 1, - t_route.end(), - s_rank + 1, - s_route.size()); - - valid = - valid && target.is_valid_addition_for_capacity_inclusion(_input, - s_delivery, - s_route.begin() + - s_rank + 1, - s_route.end(), - t_rank + 1, - t_route.size()); - - return valid; + t_route.end(), + s_rank + 1, + s_route.size()) && + target.is_valid_addition_for_capacity_inclusion(_input, + s_delivery, + s_route.begin() + + s_rank + 1, + s_route.end(), + t_rank + 1, + t_route.size()); } void TwoOpt::apply() { From f2eca9e1313fb8a7ea889e3f158fe728bee81246 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Mon, 12 Sep 2022 17:03:53 +0200 Subject: [PATCH 20/40] Some renaming in swap_star_utils. --- src/algorithms/local_search/swap_star_utils.h | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/algorithms/local_search/swap_star_utils.h b/src/algorithms/local_search/swap_star_utils.h index c1769a484..8dd747a9f 100644 --- a/src/algorithms/local_search/swap_star_utils.h +++ b/src/algorithms/local_search/swap_star_utils.h @@ -205,7 +205,7 @@ SwapChoice compute_best_swap_star_choice(const Input& input, const auto source_job_rank = source.route[s_rank]; if (input.jobs[source_job_rank].type == JOB_TYPE::SINGLE and - input.vehicle_ok_with_job(target.vehicle_rank, source_job_rank)) { + input.vehicle_ok_with_job(t_vehicle, source_job_rank)) { top_insertions_in_target[s_rank] = find_top_3_insertions(input, source_job_rank, target); } @@ -216,7 +216,7 @@ SwapChoice compute_best_swap_star_choice(const Input& input, const auto target_job_rank = target.route[t_rank]; if (input.jobs[target_job_rank].type == JOB_TYPE::SINGLE and - input.vehicle_ok_with_job(source.vehicle_rank, target_job_rank)) { + input.vehicle_ok_with_job(s_vehicle, target_job_rank)) { top_insertions_in_source[t_rank] = find_top_3_insertions(input, target_job_rank, source); } @@ -226,8 +226,8 @@ SwapChoice compute_best_swap_star_choice(const Input& input, auto best_choice = empty_choice; Eval best_gain = best_known_gain; - const auto& v_source = input.vehicles[source.vehicle_rank]; - const auto& v_target = input.vehicles[target.vehicle_rank]; + const auto& s_v = input.vehicles[s_vehicle]; + const auto& t_v = input.vehicles[t_vehicle]; const auto& s_delivery_margin = source.delivery_margin(); const auto& s_pickup_margin = source.pickup_margin(); @@ -242,12 +242,11 @@ SwapChoice compute_best_swap_star_choice(const Input& input, // except in the case of a single-step route with a start and end, // where the start->end cost is not accounted for. const auto source_start_end_cost = - (source.size() == 1 and v_source.has_start() and v_source.has_end()) - ? v_source.eval(v_source.start.value().index(), - v_source.end.value().index()) + (source.size() == 1 and s_v.has_start() and s_v.has_end()) + ? s_v.eval(s_v.start.value().index(), s_v.end.value().index()) : Eval(); const auto source_delta = - sol_state.node_gains[source.vehicle_rank][s_rank] - source_start_end_cost; + sol_state.node_gains[s_vehicle][s_rank] - source_start_end_cost; for (const auto& t_element : top_insertions_in_source) { const auto t_rank = t_element.first; @@ -255,25 +254,23 @@ SwapChoice compute_best_swap_star_choice(const Input& input, // Same as above. const auto target_start_end_cost = - (target.size() == 1 and v_target.has_start() and v_target.has_end()) - ? v_target.eval(v_target.start.value().index(), - v_target.end.value().index()) + (target.size() == 1 and t_v.has_start() and t_v.has_end()) + ? t_v.eval(t_v.start.value().index(), t_v.end.value().index()) : Eval(); const auto target_delta = - sol_state.node_gains[target.vehicle_rank][t_rank] - - target_start_end_cost; + sol_state.node_gains[t_vehicle][t_rank] - target_start_end_cost; const auto target_in_place_delta = utils::in_place_delta_cost(input, source.route[s_rank], - v_target, + t_v, target.route, t_rank); const auto source_in_place_delta = utils::in_place_delta_cost(input, target.route[t_rank], - v_source, + s_v, source.route, s_rank); From eb7c235b39dc9c387c06a601ec6f838d22b40740 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 08:27:28 +0200 Subject: [PATCH 21/40] Check max_travel_time validity in swap_star_utils. --- src/algorithms/local_search/swap_star_utils.h | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/src/algorithms/local_search/swap_star_utils.h b/src/algorithms/local_search/swap_star_utils.h index 8dd747a9f..463efd835 100644 --- a/src/algorithms/local_search/swap_star_utils.h +++ b/src/algorithms/local_search/swap_star_utils.h @@ -229,6 +229,9 @@ SwapChoice compute_best_swap_star_choice(const Input& input, const auto& s_v = input.vehicles[s_vehicle]; const auto& t_v = input.vehicles[t_vehicle]; + const auto s_travel_time = sol_state.route_evals[s_vehicle].duration; + const auto t_travel_time = sol_state.route_evals[t_vehicle].duration; + const auto& s_delivery_margin = source.delivery_margin(); const auto& s_pickup_margin = source.pickup_margin(); const auto& t_delivery_margin = target.delivery_margin(); @@ -279,39 +282,43 @@ SwapChoice compute_best_swap_star_choice(const Input& input, // Options for in-place insertion in source route include // in-place insertion in target route and other relevant // positions from target_insertions. - const Eval in_place_source_insertion_gain = - target_delta - source_in_place_delta; - const Eval in_place_target_insertion_gain = - source_delta - target_in_place_delta; - - Eval current_gain = - in_place_target_insertion_gain + in_place_source_insertion_gain; - if (current_gain > best_gain) { - SwapChoice sc({current_gain, s_rank, t_rank, s_rank, t_rank}); - if (valid_choice_for_insertion_ranks(sol_state, - s_vehicle, - source, - t_vehicle, - target, - sc)) { - swap_choice_options.push_back(std::move(sc)); + const Eval in_place_s_gain = source_delta - source_in_place_delta; + const Eval in_place_t_gain = target_delta - target_in_place_delta; + + Eval current_gain = in_place_s_gain + in_place_t_gain; + + if (s_travel_time <= s_v.max_travel_time + in_place_s_gain.duration) { + // Only bother further checking in-place insertion in source + // route if max travel time constraint is OK. + if (current_gain > best_gain and + t_travel_time <= t_v.max_travel_time + in_place_t_gain.duration) { + SwapChoice sc({current_gain, s_rank, t_rank, s_rank, t_rank}); + if (valid_choice_for_insertion_ranks(sol_state, + s_vehicle, + source, + t_vehicle, + target, + sc)) { + swap_choice_options.push_back(std::move(sc)); + } } - } - for (const auto& ti : target_insertions) { - if ((ti.rank != t_rank) and (ti.rank != t_rank + 1) and - (ti.cost != NO_EVAL)) { - const Eval t_gain = source_delta - ti.cost; - current_gain = in_place_source_insertion_gain + t_gain; - if (current_gain > best_gain) { - SwapChoice sc({current_gain, s_rank, t_rank, s_rank, ti.rank}); - if (valid_choice_for_insertion_ranks(sol_state, - s_vehicle, - source, - t_vehicle, - target, - sc)) { - swap_choice_options.push_back(std::move(sc)); + for (const auto& ti : target_insertions) { + if ((ti.rank != t_rank) and (ti.rank != t_rank + 1) and + (ti.cost != NO_EVAL)) { + const Eval t_gain = target_delta - ti.cost; + current_gain = in_place_s_gain + t_gain; + if (current_gain > best_gain and + t_travel_time <= t_v.max_travel_time + t_gain.duration) { + SwapChoice sc({current_gain, s_rank, t_rank, s_rank, ti.rank}); + if (valid_choice_for_insertion_ranks(sol_state, + s_vehicle, + source, + t_vehicle, + target, + sc)) { + swap_choice_options.push_back(std::move(sc)); + } } } } @@ -324,10 +331,17 @@ SwapChoice compute_best_swap_star_choice(const Input& input, for (const auto& si : source_insertions) { if ((si.rank != s_rank) and (si.rank != s_rank + 1) and (si.cost != NO_EVAL)) { - const Eval s_gain = target_delta - si.cost; + const Eval s_gain = source_delta - si.cost; + + if (s_travel_time > s_v.max_travel_time + s_gain.duration) { + // Don't bother further checking if max travel time + // constraint is violated for source route. + continue; + } - current_gain = s_gain + in_place_target_insertion_gain; - if (current_gain > best_gain) { + current_gain = s_gain + in_place_t_gain; + if (current_gain > best_gain and + t_travel_time <= t_v.max_travel_time + in_place_t_gain.duration) { SwapChoice sc({current_gain, s_rank, t_rank, si.rank, t_rank}); if (valid_choice_for_insertion_ranks(sol_state, s_vehicle, @@ -342,9 +356,10 @@ SwapChoice compute_best_swap_star_choice(const Input& input, for (const auto& ti : target_insertions) { if ((ti.rank != t_rank) and (ti.rank != t_rank + 1) and (ti.cost != NO_EVAL)) { - const Eval t_gain = source_delta - ti.cost; + const Eval t_gain = target_delta - ti.cost; current_gain = s_gain + t_gain; - if (current_gain > best_gain) { + if (current_gain > best_gain and + t_travel_time <= t_v.max_travel_time + t_gain.duration) { SwapChoice sc({current_gain, s_rank, t_rank, si.rank, ti.rank}); if (valid_choice_for_insertion_ranks(sol_state, s_vehicle, From 966239f87c397decc821114b5458878ec7f1bf45 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 09:00:37 +0200 Subject: [PATCH 22/40] Split source and target gain in RouteExchange::compute_gain. --- .../cvrp/operators/route_exchange.cpp | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/problems/cvrp/operators/route_exchange.cpp b/src/problems/cvrp/operators/route_exchange.cpp index 49e607482..7d0e4eb97 100644 --- a/src/problems/cvrp/operators/route_exchange.cpp +++ b/src/problems/cvrp/operators/route_exchange.cpp @@ -39,58 +39,55 @@ void RouteExchange::compute_gain() { const auto& s_v = _input.vehicles[s_vehicle]; const auto& t_v = _input.vehicles[t_vehicle]; - Eval new_cost; - Eval previous_cost; - if (s_route.size() > 0) { // Handle changes at route start. auto first_s_index = _input.jobs[s_route.front()].index(); if (s_v.has_start()) { - previous_cost += s_v.eval(s_v.start.value().index(), first_s_index); + s_gain += s_v.eval(s_v.start.value().index(), first_s_index); } if (t_v.has_start()) { - new_cost += t_v.eval(t_v.start.value().index(), first_s_index); + t_gain -= t_v.eval(t_v.start.value().index(), first_s_index); } // Handle changes at route end. auto last_s_index = _input.jobs[s_route.back()].index(); if (s_v.has_end()) { - previous_cost += s_v.eval(last_s_index, s_v.end.value().index()); + s_gain += s_v.eval(last_s_index, s_v.end.value().index()); } if (t_v.has_end()) { - new_cost += t_v.eval(last_s_index, t_v.end.value().index()); + t_gain -= t_v.eval(last_s_index, t_v.end.value().index()); } // Handle inner cost change for route. - previous_cost += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); - new_cost += _sol_state.fwd_costs[s_vehicle][t_vehicle].back(); + s_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle].back(); + t_gain -= _sol_state.fwd_costs[s_vehicle][t_vehicle].back(); } if (t_route.size() > 0) { // Handle changes at route start. auto first_t_index = _input.jobs[t_route.front()].index(); if (t_v.has_start()) { - previous_cost += t_v.eval(t_v.start.value().index(), first_t_index); + t_gain += t_v.eval(t_v.start.value().index(), first_t_index); } if (s_v.has_start()) { - new_cost += s_v.eval(s_v.start.value().index(), first_t_index); + s_gain -= s_v.eval(s_v.start.value().index(), first_t_index); } // Handle changes at route end. auto last_t_index = _input.jobs[t_route.back()].index(); if (t_v.has_end()) { - previous_cost += t_v.eval(last_t_index, t_v.end.value().index()); + t_gain += t_v.eval(last_t_index, t_v.end.value().index()); } if (s_v.has_end()) { - new_cost += s_v.eval(last_t_index, s_v.end.value().index()); + s_gain -= s_v.eval(last_t_index, s_v.end.value().index()); } // Handle inner cost change for route. - previous_cost += _sol_state.fwd_costs[t_vehicle][t_vehicle].back(); - new_cost += _sol_state.fwd_costs[t_vehicle][s_vehicle].back(); + t_gain += _sol_state.fwd_costs[t_vehicle][t_vehicle].back(); + s_gain -= _sol_state.fwd_costs[t_vehicle][s_vehicle].back(); } - stored_gain = previous_cost - new_cost; + stored_gain = s_gain + t_gain; gain_computed = true; } From ab43e54fc422ad91200d78f08fc9fe10edd8546d Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 09:01:02 +0200 Subject: [PATCH 23/40] Check max_travel_time validity in RouteExchange. --- src/problems/cvrp/operators/route_exchange.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/problems/cvrp/operators/route_exchange.cpp b/src/problems/cvrp/operators/route_exchange.cpp index 7d0e4eb97..3c3a20e39 100644 --- a/src/problems/cvrp/operators/route_exchange.cpp +++ b/src/problems/cvrp/operators/route_exchange.cpp @@ -92,7 +92,11 @@ void RouteExchange::compute_gain() { } bool RouteExchange::is_valid() { - return (source.max_load() <= _input.vehicles[t_vehicle].capacity) && + assert(gain_computed); + + return is_valid_for_source_max_travel_time() && + is_valid_for_target_max_travel_time() && + (source.max_load() <= _input.vehicles[t_vehicle].capacity) && (target.max_load() <= _input.vehicles[s_vehicle].capacity); } From c80636fee203187c7b4a1565ef1da6a21e461c40 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 09:45:20 +0200 Subject: [PATCH 24/40] Check max_travel_time validity in UnassignedExchange. --- .../cvrp/operators/unassigned_exchange.cpp | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/problems/cvrp/operators/unassigned_exchange.cpp b/src/problems/cvrp/operators/unassigned_exchange.cpp index 9b1efadc3..a5750dc40 100644 --- a/src/problems/cvrp/operators/unassigned_exchange.cpp +++ b/src/problems/cvrp/operators/unassigned_exchange.cpp @@ -59,9 +59,6 @@ void UnassignedExchange::compute_gain() { const Index u_index = _input.jobs[_u].index(); - Eval s_gain; - Eval t_gain; - if (t_rank == s_rank) { // Removed job is replaced by the unassigned one so there is no // new edge in place of removal. @@ -69,34 +66,28 @@ void UnassignedExchange::compute_gain() { // No old edge to remove when adding unassigned job in place of // removed job. - Eval previous_cost; - Eval next_cost; - if (t_rank == 0) { if (v.has_start()) { - previous_cost = v.eval(v.start.value().index(), u_index); + s_gain -= v.eval(v.start.value().index(), u_index); } } else { - previous_cost = v.eval(_input.jobs[s_route[t_rank - 1]].index(), u_index); + s_gain -= v.eval(_input.jobs[s_route[t_rank - 1]].index(), u_index); } if (t_rank == s_route.size() - 1) { if (v.has_end()) { - next_cost = v.eval(u_index, v.end.value().index()); + s_gain -= v.eval(u_index, v.end.value().index()); } } else { - next_cost = v.eval(u_index, _input.jobs[s_route[s_rank + 1]].index()); + s_gain -= v.eval(u_index, _input.jobs[s_route[s_rank + 1]].index()); } - - t_gain = -previous_cost - next_cost; } else { // No common edge so both gains can be computed independently. - s_gain = _sol_state.node_gains[s_vehicle][s_rank]; - - t_gain = -utils::addition_cost(_input, _u, v, s_route, t_rank); + s_gain = _sol_state.node_gains[s_vehicle][s_rank] - + utils::addition_cost(_input, _u, v, s_route, t_rank); } - stored_gain = s_gain + t_gain; + stored_gain = s_gain; gain_computed = true; } @@ -125,6 +116,18 @@ bool UnassignedExchange::is_valid() { _first_rank, _last_rank); + if (valid) { + // Check validity with regard to max_travel_time, requires valid + // gain value. + if (!gain_computed) { + // May happen that we don't check gain before validity if priority + // is improved. + this->compute_gain(); + } + + valid = is_valid_for_source_max_travel_time(); + } + return valid; } From 8a8a870e703369dc5b15d4e7c376a378b07f5406 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 09:58:40 +0200 Subject: [PATCH 25/40] Check max_travel_time validity in IntraTwoOpt. --- src/problems/cvrp/operators/intra_two_opt.cpp | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/problems/cvrp/operators/intra_two_opt.cpp b/src/problems/cvrp/operators/intra_two_opt.cpp index 7e67d65dd..8463db87e 100644 --- a/src/problems/cvrp/operators/intra_two_opt.cpp +++ b/src/problems/cvrp/operators/intra_two_opt.cpp @@ -41,41 +41,41 @@ void IntraTwoOpt::compute_gain() { Index s_index = _input.jobs[s_route[s_rank]].index(); Index t_index = _input.jobs[t_route[t_rank]].index(); - stored_gain = Eval(); // Cost of reversing vehicle route between s_rank and t_rank // included. - stored_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle][t_rank]; - stored_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank]; - stored_gain += _sol_state.bwd_costs[s_vehicle][s_vehicle][s_rank]; - stored_gain -= _sol_state.bwd_costs[s_vehicle][s_vehicle][t_rank]; + s_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle][t_rank]; + s_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank]; + s_gain += _sol_state.bwd_costs[s_vehicle][s_vehicle][s_rank]; + s_gain -= _sol_state.bwd_costs[s_vehicle][s_vehicle][t_rank]; // Cost of going to t_rank first instead of s_rank. if (s_rank > 0) { Index previous_index = _input.jobs[s_route[s_rank - 1]].index(); - stored_gain += s_v.eval(previous_index, s_index); - stored_gain -= s_v.eval(previous_index, t_index); + s_gain += s_v.eval(previous_index, s_index); + s_gain -= s_v.eval(previous_index, t_index); } else { if (s_v.has_start()) { Index start_index = s_v.start.value().index(); - stored_gain += s_v.eval(start_index, s_index); - stored_gain -= s_v.eval(start_index, t_index); + s_gain += s_v.eval(start_index, s_index); + s_gain -= s_v.eval(start_index, t_index); } } // Cost of going from s_rank after instead of t_rank. if (t_rank < s_route.size() - 1) { Index next_index = _input.jobs[s_route[t_rank + 1]].index(); - stored_gain += s_v.eval(t_index, next_index); - stored_gain -= s_v.eval(s_index, next_index); + s_gain += s_v.eval(t_index, next_index); + s_gain -= s_v.eval(s_index, next_index); } else { if (s_v.has_end()) { Index end_index = s_v.end.value().index(); - stored_gain += s_v.eval(t_index, end_index); - stored_gain -= s_v.eval(s_index, end_index); + s_gain += s_v.eval(t_index, end_index); + s_gain -= s_v.eval(s_index, end_index); } } + stored_gain = s_gain; gain_computed = true; } @@ -95,8 +95,12 @@ bool IntraTwoOpt::reversal_ok_for_shipments() const { } bool IntraTwoOpt::is_valid() { + assert(gain_computed); + bool valid = !_input.has_shipments() or reversal_ok_for_shipments(); + valid = valid && is_valid_for_source_max_travel_time(); + if (valid) { auto rev_t = s_route.rbegin() + (s_route.size() - t_rank - 1); auto rev_s_next = s_route.rbegin() + (s_route.size() - s_rank); From e0e40d5c2acbde984210bc7b55f63e5e539a7dda Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 10:02:42 +0200 Subject: [PATCH 26/40] Check max_travel_time validity in IntraRelocate. --- .../cvrp/operators/intra_relocate.cpp | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/problems/cvrp/operators/intra_relocate.cpp b/src/problems/cvrp/operators/intra_relocate.cpp index 0e05f6d74..7ca53bc93 100644 --- a/src/problems/cvrp/operators/intra_relocate.cpp +++ b/src/problems/cvrp/operators/intra_relocate.cpp @@ -61,23 +61,25 @@ void IntraRelocate::compute_gain() { if (s_rank < t_rank) { ++new_rank; } - Eval t_gain = - -utils::addition_cost(_input, s_route[s_rank], v_target, t_route, new_rank); + s_gain = + _sol_state.node_gains[s_vehicle][s_rank] - + utils::addition_cost(_input, s_route[s_rank], v_target, t_route, new_rank); - stored_gain = _sol_state.node_gains[s_vehicle][s_rank] + t_gain; + stored_gain = s_gain; gain_computed = true; } bool IntraRelocate::is_valid() { - return source - .is_valid_addition_for_capacity_inclusion(_input, - source - .delivery_in_range(_first_rank, - _last_rank), - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); + assert(gain_computed); + + return is_valid_for_source_max_travel_time() && + source.is_valid_addition_for_capacity_inclusion( + _input, + source.delivery_in_range(_first_rank, _last_rank), + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); } void IntraRelocate::apply() { From c80d00896b1e2a1309b47f780f9c6bd9f2d03d8f Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 10:38:56 +0200 Subject: [PATCH 27/40] Rely on stored_gain for max_travel_time check in Intra operators. --- src/algorithms/local_search/operator.cpp | 9 ++++++ src/algorithms/local_search/operator.h | 3 ++ .../cvrp/operators/intra_relocate.cpp | 7 ++--- src/problems/cvrp/operators/intra_two_opt.cpp | 29 +++++++++---------- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/algorithms/local_search/operator.cpp b/src/algorithms/local_search/operator.cpp index 8161c9dab..0dcdfcc1f 100644 --- a/src/algorithms/local_search/operator.cpp +++ b/src/algorithms/local_search/operator.cpp @@ -35,6 +35,15 @@ bool Operator::is_valid_for_target_max_travel_time() const { return t_travel_time <= t_v.max_travel_time + t_gain.duration; } +bool Operator::is_valid_for_max_travel_time() const { + assert(s_vehicle == t_vehicle); + assert(gain_computed); + + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + return s_travel_time <= s_v.max_travel_time + stored_gain.duration; +} + std::vector Operator::required_unassigned() const { return std::vector(); } diff --git a/src/algorithms/local_search/operator.h b/src/algorithms/local_search/operator.h index 7f776c736..d910344e0 100644 --- a/src/algorithms/local_search/operator.h +++ b/src/algorithms/local_search/operator.h @@ -45,6 +45,9 @@ class Operator { bool is_valid_for_target_max_travel_time() const; + // Used for internal operators only. + bool is_valid_for_max_travel_time() const; + public: Operator(OperatorName name, const Input& input, diff --git a/src/problems/cvrp/operators/intra_relocate.cpp b/src/problems/cvrp/operators/intra_relocate.cpp index 7ca53bc93..906f5a425 100644 --- a/src/problems/cvrp/operators/intra_relocate.cpp +++ b/src/problems/cvrp/operators/intra_relocate.cpp @@ -61,18 +61,15 @@ void IntraRelocate::compute_gain() { if (s_rank < t_rank) { ++new_rank; } - s_gain = + stored_gain = _sol_state.node_gains[s_vehicle][s_rank] - utils::addition_cost(_input, s_route[s_rank], v_target, t_route, new_rank); - stored_gain = s_gain; gain_computed = true; } bool IntraRelocate::is_valid() { - assert(gain_computed); - - return is_valid_for_source_max_travel_time() && + return is_valid_for_max_travel_time() && source.is_valid_addition_for_capacity_inclusion( _input, source.delivery_in_range(_first_rank, _last_rank), diff --git a/src/problems/cvrp/operators/intra_two_opt.cpp b/src/problems/cvrp/operators/intra_two_opt.cpp index 8463db87e..b3e34a634 100644 --- a/src/problems/cvrp/operators/intra_two_opt.cpp +++ b/src/problems/cvrp/operators/intra_two_opt.cpp @@ -44,38 +44,37 @@ void IntraTwoOpt::compute_gain() { // Cost of reversing vehicle route between s_rank and t_rank // included. - s_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle][t_rank]; - s_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank]; - s_gain += _sol_state.bwd_costs[s_vehicle][s_vehicle][s_rank]; - s_gain -= _sol_state.bwd_costs[s_vehicle][s_vehicle][t_rank]; + stored_gain += _sol_state.fwd_costs[s_vehicle][s_vehicle][t_rank]; + stored_gain -= _sol_state.fwd_costs[s_vehicle][s_vehicle][s_rank]; + stored_gain += _sol_state.bwd_costs[s_vehicle][s_vehicle][s_rank]; + stored_gain -= _sol_state.bwd_costs[s_vehicle][s_vehicle][t_rank]; // Cost of going to t_rank first instead of s_rank. if (s_rank > 0) { Index previous_index = _input.jobs[s_route[s_rank - 1]].index(); - s_gain += s_v.eval(previous_index, s_index); - s_gain -= s_v.eval(previous_index, t_index); + stored_gain += s_v.eval(previous_index, s_index); + stored_gain -= s_v.eval(previous_index, t_index); } else { if (s_v.has_start()) { Index start_index = s_v.start.value().index(); - s_gain += s_v.eval(start_index, s_index); - s_gain -= s_v.eval(start_index, t_index); + stored_gain += s_v.eval(start_index, s_index); + stored_gain -= s_v.eval(start_index, t_index); } } // Cost of going from s_rank after instead of t_rank. if (t_rank < s_route.size() - 1) { Index next_index = _input.jobs[s_route[t_rank + 1]].index(); - s_gain += s_v.eval(t_index, next_index); - s_gain -= s_v.eval(s_index, next_index); + stored_gain += s_v.eval(t_index, next_index); + stored_gain -= s_v.eval(s_index, next_index); } else { if (s_v.has_end()) { Index end_index = s_v.end.value().index(); - s_gain += s_v.eval(t_index, end_index); - s_gain -= s_v.eval(s_index, end_index); + stored_gain += s_v.eval(t_index, end_index); + stored_gain -= s_v.eval(s_index, end_index); } } - stored_gain = s_gain; gain_computed = true; } @@ -95,11 +94,9 @@ bool IntraTwoOpt::reversal_ok_for_shipments() const { } bool IntraTwoOpt::is_valid() { - assert(gain_computed); - bool valid = !_input.has_shipments() or reversal_ok_for_shipments(); - valid = valid && is_valid_for_source_max_travel_time(); + valid = valid && is_valid_for_max_travel_time(); if (valid) { auto rev_t = s_route.rbegin() + (s_route.size() - t_rank - 1); From f34c4cdf0258a118c6c03577c6dd62e5b25575ac Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 15:35:36 +0200 Subject: [PATCH 28/40] Check max_travel_time validity in IntraExchange. --- src/problems/cvrp/operators/intra_exchange.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/problems/cvrp/operators/intra_exchange.cpp b/src/problems/cvrp/operators/intra_exchange.cpp index 8bd7b22ca..fbd78573e 100644 --- a/src/problems/cvrp/operators/intra_exchange.cpp +++ b/src/problems/cvrp/operators/intra_exchange.cpp @@ -99,15 +99,14 @@ void IntraExchange::compute_gain() { } bool IntraExchange::is_valid() { - return source - .is_valid_addition_for_capacity_inclusion(_input, - source - .delivery_in_range(_first_rank, - _last_rank), - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); + return is_valid_for_max_travel_time() && + source.is_valid_addition_for_capacity_inclusion( + _input, + source.delivery_in_range(_first_rank, _last_rank), + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); } void IntraExchange::apply() { From a26d924145351dfa9f94cedee5a2a70f4dda4cfb Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 15:50:59 +0200 Subject: [PATCH 29/40] Check max_travel_time validity in IntraMixedExchange. --- .../cvrp/operators/intra_mixed_exchange.cpp | 47 ++++++++++++------- .../cvrp/operators/intra_mixed_exchange.h | 1 - 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/problems/cvrp/operators/intra_mixed_exchange.cpp b/src/problems/cvrp/operators/intra_mixed_exchange.cpp index 090949662..09ec8a394 100644 --- a/src/problems/cvrp/operators/intra_mixed_exchange.cpp +++ b/src/problems/cvrp/operators/intra_mixed_exchange.cpp @@ -163,12 +163,12 @@ Eval IntraMixedExchange::gain_upper_bound() { next_cost = v.eval(s_index, n_index); } - _t_gain = _sol_state.edge_evals_around_edge[t_vehicle][t_rank] - - previous_cost - next_cost; + t_gain = _sol_state.edge_evals_around_edge[t_vehicle][t_rank] - + previous_cost - next_cost; _gain_upper_bound_computed = true; - return s_gain_upper_bound + _t_gain; + return s_gain_upper_bound + t_gain; } void IntraMixedExchange::compute_gain() { @@ -192,15 +192,22 @@ void IntraMixedExchange::compute_gain() { } } - stored_gain += _t_gain; + stored_gain += t_gain; gain_computed = true; } bool IntraMixedExchange::is_valid() { - auto delivery = source.delivery_in_range(_first_rank, _last_rank); + assert(_gain_upper_bound_computed); + + const auto delivery = source.delivery_in_range(_first_rank, _last_rank); + + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + const auto normal_duration = _normal_s_gain.duration + t_gain.duration; s_is_normal_valid = + (s_travel_time <= s_v.max_travel_time + normal_duration) and source.is_valid_addition_for_capacity_inclusion(_input, delivery, _moved_jobs.begin(), @@ -209,19 +216,23 @@ bool IntraMixedExchange::is_valid() { _last_rank); if (check_t_reverse) { - std::swap(_moved_jobs[_t_edge_first], _moved_jobs[_t_edge_last]); - - s_is_reverse_valid = - source.is_valid_addition_for_capacity_inclusion(_input, - delivery, - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); - - // Reset to initial situation before potential application or TW - // checks. - std::swap(_moved_jobs[_t_edge_first], _moved_jobs[_t_edge_last]); + const auto reversed_duration = _reversed_s_gain.duration + t_gain.duration; + + if (s_travel_time <= s_v.max_travel_time + reversed_duration) { + std::swap(_moved_jobs[_t_edge_first], _moved_jobs[_t_edge_last]); + + s_is_reverse_valid = + source.is_valid_addition_for_capacity_inclusion(_input, + delivery, + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); + + // Reset to initial situation before potential application or TW + // checks. + std::swap(_moved_jobs[_t_edge_first], _moved_jobs[_t_edge_last]); + } } return s_is_normal_valid or s_is_reverse_valid; diff --git a/src/problems/cvrp/operators/intra_mixed_exchange.h b/src/problems/cvrp/operators/intra_mixed_exchange.h index 476cc1e6c..70db1a04b 100644 --- a/src/problems/cvrp/operators/intra_mixed_exchange.h +++ b/src/problems/cvrp/operators/intra_mixed_exchange.h @@ -20,7 +20,6 @@ class IntraMixedExchange : public ls::Operator { bool _gain_upper_bound_computed; Eval _normal_s_gain; Eval _reversed_s_gain; - Eval _t_gain; protected: bool reverse_t_edge; From a86be3a94edc83ada81373af95e6e244f6578868 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 16:10:30 +0200 Subject: [PATCH 30/40] Check max_travel_time validity in IntraCrossExchange. --- .../cvrp/operators/intra_cross_exchange.cpp | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/src/problems/cvrp/operators/intra_cross_exchange.cpp b/src/problems/cvrp/operators/intra_cross_exchange.cpp index d134224cc..90b4432a2 100644 --- a/src/problems/cvrp/operators/intra_cross_exchange.cpp +++ b/src/problems/cvrp/operators/intra_cross_exchange.cpp @@ -215,9 +215,17 @@ void IntraCrossExchange::compute_gain() { } bool IntraCrossExchange::is_valid() { - auto delivery = source.delivery_in_range(_first_rank, _last_rank); + assert(_gain_upper_bound_computed); + + const auto delivery = source.delivery_in_range(_first_rank, _last_rank); + + const auto& s_v = _input.vehicles[s_vehicle]; + const auto s_travel_time = _sol_state.route_evals[s_vehicle].duration; + const auto s_normal_t_normal_duration = + _normal_s_gain.duration + _normal_t_gain.duration; s_normal_t_normal_is_valid = + (s_travel_time <= s_v.max_travel_time + s_normal_t_normal_duration) and source.is_valid_addition_for_capacity_inclusion(_input, delivery, _moved_jobs.begin(), @@ -225,23 +233,32 @@ bool IntraCrossExchange::is_valid() { _first_rank, _last_rank); - std::swap(_moved_jobs[0], _moved_jobs[1]); + const auto s_normal_t_reversed_duration = + _normal_s_gain.duration + _reversed_t_gain.duration; - if (check_t_reverse) { - s_normal_t_reverse_is_valid = - source.is_valid_addition_for_capacity_inclusion(_input, - delivery, - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); - } + if (s_travel_time <= s_v.max_travel_time + s_normal_t_reversed_duration) { + std::swap(_moved_jobs[0], _moved_jobs[1]); - std::swap(_moved_jobs[_moved_jobs.size() - 2], - _moved_jobs[_moved_jobs.size() - 1]); + if (check_t_reverse) { + s_normal_t_reverse_is_valid = + source.is_valid_addition_for_capacity_inclusion(_input, + delivery, + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); + } + + std::swap(_moved_jobs[_moved_jobs.size() - 2], + _moved_jobs[_moved_jobs.size() - 1]); + } if (check_s_reverse and check_t_reverse) { + const auto s_reversed_t_reversed_duration = + _reversed_s_gain.duration + _reversed_t_gain.duration; s_reverse_t_reverse_is_valid = + (s_travel_time <= + s_v.max_travel_time + s_reversed_t_reversed_duration) and source.is_valid_addition_for_capacity_inclusion(_input, delivery, _moved_jobs.begin(), @@ -250,22 +267,27 @@ bool IntraCrossExchange::is_valid() { _last_rank); } - std::swap(_moved_jobs[0], _moved_jobs[1]); + const auto s_reversed_t_normal_duration = + _reversed_s_gain.duration + _normal_t_gain.duration; - if (check_s_reverse) { - s_reverse_t_normal_is_valid = - source.is_valid_addition_for_capacity_inclusion(_input, - delivery, - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); - } + if (s_travel_time <= s_v.max_travel_time + s_reversed_t_normal_duration) { + std::swap(_moved_jobs[0], _moved_jobs[1]); - // Reset to initial situation before potential application and TW - // checks. - std::swap(_moved_jobs[_moved_jobs.size() - 2], - _moved_jobs[_moved_jobs.size() - 1]); + if (check_s_reverse) { + s_reverse_t_normal_is_valid = + source.is_valid_addition_for_capacity_inclusion(_input, + delivery, + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); + } + + // Reset to initial situation before potential application and TW + // checks. + std::swap(_moved_jobs[_moved_jobs.size() - 2], + _moved_jobs[_moved_jobs.size() - 1]); + } return s_normal_t_normal_is_valid or s_normal_t_reverse_is_valid or s_reverse_t_reverse_is_valid or s_reverse_t_normal_is_valid; From 405566443ff33c0e777210863a198230463d3bf2 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 17:06:14 +0200 Subject: [PATCH 31/40] Split source and target gain in PDShift and perform source max_travel_time check upstream in LS. --- src/algorithms/local_search/local_search.cpp | 10 ++++++++++ src/problems/cvrp/operators/pd_shift.cpp | 20 +++++++++++--------- src/problems/cvrp/operators/pd_shift.h | 1 - src/problems/vrptw/operators/pd_shift.cpp | 16 ++++++++-------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index be7e94487..613d29c67 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -1496,6 +1496,16 @@ void LocalSearch v_s.max_travel_time + s_removal_duration_gain) { + // Removing shipment from source route actually breaks + // max_travel_time constraint in source. + continue; + } + #ifdef LOG_LS_OPERATORS ++tried_moves[OperatorName::PDShift]; #endif diff --git a/src/problems/cvrp/operators/pd_shift.cpp b/src/problems/cvrp/operators/pd_shift.cpp index 002272e22..4e4cad2eb 100644 --- a/src/problems/cvrp/operators/pd_shift.cpp +++ b/src/problems/cvrp/operators/pd_shift.cpp @@ -35,7 +35,6 @@ PDShift::PDShift(const Input& input, 0), _s_p_rank(s_p_rank), _s_d_rank(s_d_rank), - _remove_gain(_sol_state.pd_gains[s_vehicle][_s_p_rank]), _valid(false) { assert(s_vehicle != t_vehicle); assert(s_route.size() >= 2); @@ -43,21 +42,24 @@ PDShift::PDShift(const Input& input, assert(s_d_rank < s_route.size()); assert(s_route.route[s_p_rank] + 1 == s_route.route[s_d_rank]); + s_gain = _sol_state.pd_gains[s_vehicle][_s_p_rank]; + assert(_sol_state.route_evals[s_vehicle].duration <= + _input.vehicles[s_vehicle].max_travel_time + s_gain.duration); stored_gain = gain_threshold; } void PDShift::compute_gain() { - ls::RouteInsertion rs = - ls::compute_best_insertion_pd(_input, - _sol_state, - s_route[_s_p_rank], - t_vehicle, - target, - _remove_gain - stored_gain); + ls::RouteInsertion rs = ls::compute_best_insertion_pd(_input, + _sol_state, + s_route[_s_p_rank], + t_vehicle, + target, + s_gain - stored_gain); if (rs.eval != NO_EVAL) { _valid = true; - stored_gain = _remove_gain - rs.eval; + t_gain = -rs.eval; + stored_gain = s_gain + t_gain; _best_t_p_rank = rs.pickup_rank; _best_t_d_rank = rs.delivery_rank; } diff --git a/src/problems/cvrp/operators/pd_shift.h b/src/problems/cvrp/operators/pd_shift.h index 4513fc73b..8c2e0abbf 100644 --- a/src/problems/cvrp/operators/pd_shift.h +++ b/src/problems/cvrp/operators/pd_shift.h @@ -19,7 +19,6 @@ class PDShift : public ls::Operator { protected: const Index _s_p_rank; const Index _s_d_rank; - const Eval _remove_gain; Index _best_t_p_rank; Index _best_t_d_rank; bool _valid; diff --git a/src/problems/vrptw/operators/pd_shift.cpp b/src/problems/vrptw/operators/pd_shift.cpp index 46d2b19c6..2ff0834da 100644 --- a/src/problems/vrptw/operators/pd_shift.cpp +++ b/src/problems/vrptw/operators/pd_shift.cpp @@ -50,17 +50,17 @@ void PDShift::compute_gain() { return; } - ls::RouteInsertion rs = - ls::compute_best_insertion_pd(_input, - _sol_state, - s_route[_s_p_rank], - t_vehicle, - _tw_t_route, - _remove_gain - stored_gain); + ls::RouteInsertion rs = ls::compute_best_insertion_pd(_input, + _sol_state, + s_route[_s_p_rank], + t_vehicle, + _tw_t_route, + s_gain - stored_gain); if (rs.eval != NO_EVAL) { _valid = true; - stored_gain = _remove_gain - rs.eval; + t_gain = -rs.eval; + stored_gain = s_gain + t_gain; _best_t_p_rank = rs.pickup_rank; _best_t_d_rank = rs.delivery_rank; } From 5ef8550081eb4359a17f8ed0964d9aa207ecadc4 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 13 Sep 2022 17:21:25 +0200 Subject: [PATCH 32/40] Check max_travel_time validity in PDShift/compute_best_insertion_pd. --- src/algorithms/local_search/insertion_search.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/algorithms/local_search/insertion_search.h b/src/algorithms/local_search/insertion_search.h index ba064f33f..14254478e 100644 --- a/src/algorithms/local_search/insertion_search.h +++ b/src/algorithms/local_search/insertion_search.h @@ -81,6 +81,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input, RouteInsertion result = empty_insert; const auto& current_job = input.jobs[j]; const auto& v_target = input.vehicles[v]; + const auto target_travel_time = sol_state.route_evals[v].duration; if (!input.vehicle_ok_with_job(v, j)) { return result; @@ -119,7 +120,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input, Eval p_add = utils::addition_cost(input, j, v_target, route.route, pickup_r); if (p_add > result.eval) { - // Even without delivery insertion more expensive then current best + // Even without delivery insertion more expensive than current best. continue; } @@ -158,7 +159,8 @@ RouteInsertion compute_best_insertion_pd(const Input& input, pd_eval = p_add + d_adds[delivery_r]; } - if (pd_eval < result.eval) { + if (pd_eval < result.eval && + target_travel_time + pd_eval.duration <= v_target.max_travel_time) { modified_with_pd.push_back(j + 1); // Update best cost depending on validity. @@ -184,6 +186,7 @@ RouteInsertion compute_best_insertion_pd(const Input& input, } } } + assert(result.eval <= cost_threshold); if (result.eval == cost_threshold) { result.eval = NO_EVAL; From b5abde61821181e0eca3304589390e7c6bebc2b7 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 14 Sep 2022 08:00:20 +0200 Subject: [PATCH 33/40] Fix wrong ordering in IntraCrossExchange check. --- src/problems/cvrp/operators/intra_cross_exchange.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/problems/cvrp/operators/intra_cross_exchange.cpp b/src/problems/cvrp/operators/intra_cross_exchange.cpp index 90b4432a2..805d0584d 100644 --- a/src/problems/cvrp/operators/intra_cross_exchange.cpp +++ b/src/problems/cvrp/operators/intra_cross_exchange.cpp @@ -233,10 +233,10 @@ bool IntraCrossExchange::is_valid() { _first_rank, _last_rank); - const auto s_normal_t_reversed_duration = - _normal_s_gain.duration + _reversed_t_gain.duration; + const auto s_normal_t_reverse_duration = + _reversed_s_gain.duration + _normal_t_gain.duration; - if (s_travel_time <= s_v.max_travel_time + s_normal_t_reversed_duration) { + if (s_travel_time <= s_v.max_travel_time + s_normal_t_reverse_duration) { std::swap(_moved_jobs[0], _moved_jobs[1]); if (check_t_reverse) { @@ -267,10 +267,10 @@ bool IntraCrossExchange::is_valid() { _last_rank); } - const auto s_reversed_t_normal_duration = - _reversed_s_gain.duration + _normal_t_gain.duration; + const auto s_reverse_t_normal_duration = + _normal_s_gain.duration + _reversed_t_gain.duration; - if (s_travel_time <= s_v.max_travel_time + s_reversed_t_normal_duration) { + if (s_travel_time <= s_v.max_travel_time + s_reverse_t_normal_duration) { std::swap(_moved_jobs[0], _moved_jobs[1]); if (check_s_reverse) { From 06cfa0bf3e3fa18665f3ca71aafcdef92adef55b Mon Sep 17 00:00:00 2001 From: jcoupey Date: Wed, 14 Sep 2022 17:41:43 +0200 Subject: [PATCH 34/40] Fix missing swaps in IntraCrossExchange::is_valid. --- .../cvrp/operators/intra_cross_exchange.cpp | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/problems/cvrp/operators/intra_cross_exchange.cpp b/src/problems/cvrp/operators/intra_cross_exchange.cpp index 805d0584d..dcb816284 100644 --- a/src/problems/cvrp/operators/intra_cross_exchange.cpp +++ b/src/problems/cvrp/operators/intra_cross_exchange.cpp @@ -233,26 +233,25 @@ bool IntraCrossExchange::is_valid() { _first_rank, _last_rank); - const auto s_normal_t_reverse_duration = - _reversed_s_gain.duration + _normal_t_gain.duration; - - if (s_travel_time <= s_v.max_travel_time + s_normal_t_reverse_duration) { - std::swap(_moved_jobs[0], _moved_jobs[1]); - - if (check_t_reverse) { - s_normal_t_reverse_is_valid = - source.is_valid_addition_for_capacity_inclusion(_input, - delivery, - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); - } + std::swap(_moved_jobs[0], _moved_jobs[1]); + + if (check_t_reverse) { + const auto s_normal_t_reverse_duration = + _reversed_s_gain.duration + _normal_t_gain.duration; - std::swap(_moved_jobs[_moved_jobs.size() - 2], - _moved_jobs[_moved_jobs.size() - 1]); + s_normal_t_reverse_is_valid = + (s_travel_time <= s_v.max_travel_time + s_normal_t_reverse_duration) && + source.is_valid_addition_for_capacity_inclusion(_input, + delivery, + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); } + std::swap(_moved_jobs[_moved_jobs.size() - 2], + _moved_jobs[_moved_jobs.size() - 1]); + if (check_s_reverse and check_t_reverse) { const auto s_reversed_t_reversed_duration = _reversed_s_gain.duration + _reversed_t_gain.duration; @@ -267,28 +266,27 @@ bool IntraCrossExchange::is_valid() { _last_rank); } - const auto s_reverse_t_normal_duration = - _normal_s_gain.duration + _reversed_t_gain.duration; + std::swap(_moved_jobs[0], _moved_jobs[1]); - if (s_travel_time <= s_v.max_travel_time + s_reverse_t_normal_duration) { - std::swap(_moved_jobs[0], _moved_jobs[1]); - - if (check_s_reverse) { - s_reverse_t_normal_is_valid = - source.is_valid_addition_for_capacity_inclusion(_input, - delivery, - _moved_jobs.begin(), - _moved_jobs.end(), - _first_rank, - _last_rank); - } + if (check_s_reverse) { + const auto s_reverse_t_normal_duration = + _normal_s_gain.duration + _reversed_t_gain.duration; - // Reset to initial situation before potential application and TW - // checks. - std::swap(_moved_jobs[_moved_jobs.size() - 2], - _moved_jobs[_moved_jobs.size() - 1]); + s_reverse_t_normal_is_valid = + (s_travel_time <= s_v.max_travel_time + s_reverse_t_normal_duration) && + source.is_valid_addition_for_capacity_inclusion(_input, + delivery, + _moved_jobs.begin(), + _moved_jobs.end(), + _first_rank, + _last_rank); } + // Reset to initial situation before potential application and TW + // checks. + std::swap(_moved_jobs[_moved_jobs.size() - 2], + _moved_jobs[_moved_jobs.size() - 1]); + return s_normal_t_normal_is_valid or s_normal_t_reverse_is_valid or s_reverse_t_reverse_is_valid or s_reverse_t_normal_is_valid; } From 822461f9c67a781455365182d2916ece997d8a4d Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 15 Sep 2022 10:34:51 +0200 Subject: [PATCH 35/40] Check max_travel_time validity in try_job_addition/compute_best_insertion. --- src/algorithms/local_search/insertion_search.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/algorithms/local_search/insertion_search.h b/src/algorithms/local_search/insertion_search.h index 14254478e..a1c6387c6 100644 --- a/src/algorithms/local_search/insertion_search.h +++ b/src/algorithms/local_search/insertion_search.h @@ -28,17 +28,18 @@ compute_best_insertion_single(const Input& input, const auto& v_target = input.vehicles[v]; if (input.vehicle_ok_with_job(v, j)) { - for (Index rank = sol_state.insertion_ranks_begin[v][j]; rank < sol_state.insertion_ranks_end[v][j]; ++rank) { Eval current_eval = utils::addition_cost(input, j, v_target, route.route, rank); - if (current_eval.cost < result.eval.cost and + if (current_eval.cost < result.eval.cost && + (sol_state.route_evals[v].duration + current_eval.duration <= + v_target.max_travel_time) && route.is_valid_addition_for_capacity(input, current_job.pickup, current_job.delivery, - rank) and + rank) && route.is_valid_addition_for_tw(input, j, rank)) { result = {current_eval, rank, 0, 0}; } From 138658142d579e9bf0000d5c908c8b98197441b7 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 15 Sep 2022 11:32:04 +0200 Subject: [PATCH 36/40] Document the new max_travel_time key. --- CHANGELOG.md | 1 + docs/API.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bca26fc0a..a22994426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Support for `max_travel_time` at vehicle level (#273) - Advertise `libvroom` in README and wiki (#42) ### Changed diff --git a/docs/API.md b/docs/API.md index 32bfe5f08..00ee4175b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -115,6 +115,7 @@ A `vehicle` object has the following properties: | [`breaks`] | an array of `break` objects | | [`speed_factor`] | a double value in the range `(0, 5]` used to scale **all** vehicle travel times (defaults to 1.), the respected precision is limited to two digits after the decimal point | | [`max_tasks`] | an integer defining the maximum number of tasks in a route for this vehicle | +| [`max_travel_time`] | an integer defining the maximum travel time for this vehicle | | [`steps`] | an array of `vehicle_step` objects describing a custom route for this vehicle | A `break` object has the following properties: From c2bf6f47bc1eae1d9ae5a7706b382f008eb8b48c Mon Sep 17 00:00:00 2001 From: jcoupey Date: Thu, 15 Sep 2022 12:42:57 +0200 Subject: [PATCH 37/40] Check max_travel_time validity in remove_from_routes. --- src/algorithms/local_search/local_search.cpp | 23 +++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp index 613d29c67..86b2034e6 100644 --- a/src/algorithms/local_search/local_search.cpp +++ b/src/algorithms/local_search/local_search.cpp @@ -2121,6 +2121,9 @@ void LocalSearch best_gain) { // Only check validity if required. - valid_removal = _sol[v].is_valid_removal(_input, r, 1); + valid_removal = + (current_travel_time <= max_travel_time + removal_gain.duration) && + _sol[v].is_valid_removal(_input, r, 1); } } else { assert(current_job.type == JOB_TYPE::PICKUP); - auto delivery_r = _sol_state.matching_delivery_rank[v][r]; - current_gain = _sol_state.pd_gains[v][r] - - relocate_cost_lower_bound(v, r, delivery_r); + const auto delivery_r = _sol_state.matching_delivery_rank[v][r]; + const auto& removal_gain = _sol_state.pd_gains[v][r]; + current_gain = + removal_gain - relocate_cost_lower_bound(v, r, delivery_r); - if (current_gain > best_gain) { + if (current_gain > best_gain && + (current_travel_time <= max_travel_time + removal_gain.duration)) { // Only check validity if required. if (delivery_r == r + 1) { valid_removal = _sol[v].is_valid_removal(_input, r, 2); From a0e6331deacb69a1c465e4da4958fe4c2e62aaf8 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 20 Sep 2022 11:40:07 +0200 Subject: [PATCH 38/40] Simplify some boolean values. --- src/algorithms/heuristics/heuristics.cpp | 31 ++++++------------- src/problems/cvrp/operators/intra_two_opt.cpp | 5 ++- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/algorithms/heuristics/heuristics.cpp b/src/algorithms/heuristics/heuristics.cpp index 01c4b530a..22f112d23 100644 --- a/src/algorithms/heuristics/heuristics.cpp +++ b/src/algorithms/heuristics/heuristics.cpp @@ -168,10 +168,7 @@ template T basic(const Input& input, INIT init, double lambda) { } bool is_valid = - (evals[job_rank][v_rank].duration <= vehicle.max_travel_time); - - is_valid = - is_valid && + (evals[job_rank][v_rank].duration <= vehicle.max_travel_time) && current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -362,11 +359,9 @@ template T basic(const Input& input, INIT init, double lambda) { modified_with_pd.push_back(job_rank + 1); // Update best cost depending on validity. - bool valid = (current_route_duration + current_add.duration <= - vehicle.max_travel_time); - - valid = - valid && + bool valid = + (current_route_duration + current_add.duration <= + vehicle.max_travel_time) && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -566,10 +561,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { } bool is_valid = - (evals[job_rank][v_rank].duration <= vehicle.max_travel_time); - - is_valid = - is_valid && + (evals[job_rank][v_rank].duration <= vehicle.max_travel_time) && current_r .is_valid_addition_for_capacity(input, input.jobs[job_rank].pickup, @@ -761,11 +753,9 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { modified_with_pd.push_back(job_rank + 1); // Update best cost depending on validity. - bool valid = (current_route_duration + current_add.duration <= - vehicle.max_travel_time); - - valid = - valid && + bool valid = + (current_route_duration + current_add.duration <= + vehicle.max_travel_time) && current_r .is_valid_addition_for_capacity_inclusion(input, modified_delivery, @@ -774,10 +764,7 @@ T dynamic_vehicle_choice(const Input& input, INIT init, double lambda) { modified_with_pd .end(), pickup_r, - delivery_r); - - valid = - valid && + delivery_r) && current_r.is_valid_addition_for_tw(input, modified_with_pd.begin(), modified_with_pd.end(), diff --git a/src/problems/cvrp/operators/intra_two_opt.cpp b/src/problems/cvrp/operators/intra_two_opt.cpp index b3e34a634..ac3d2254e 100644 --- a/src/problems/cvrp/operators/intra_two_opt.cpp +++ b/src/problems/cvrp/operators/intra_two_opt.cpp @@ -94,9 +94,8 @@ bool IntraTwoOpt::reversal_ok_for_shipments() const { } bool IntraTwoOpt::is_valid() { - bool valid = !_input.has_shipments() or reversal_ok_for_shipments(); - - valid = valid && is_valid_for_max_travel_time(); + bool valid = (!_input.has_shipments() or reversal_ok_for_shipments()) && + is_valid_for_max_travel_time(); if (valid) { auto rev_t = s_route.rbegin() + (s_route.size() - t_rank - 1); From fe978c59d7758defb7dea9d929689de634cbf038 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 20 Sep 2022 11:40:20 +0200 Subject: [PATCH 39/40] Make Vehicle::max_travel_time const. --- src/structures/vroom/vehicle.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/vroom/vehicle.h b/src/structures/vroom/vehicle.h index 63c8337b4..f85fae8b3 100644 --- a/src/structures/vroom/vehicle.h +++ b/src/structures/vroom/vehicle.h @@ -36,7 +36,7 @@ struct Vehicle { const std::string description; CostWrapper cost_wrapper; size_t max_tasks; - Duration max_travel_time; + const Duration max_travel_time; std::vector steps; std::unordered_map break_id_to_rank; From fca6dc1a812e552a10670dcca81d61b2c05a1098 Mon Sep 17 00:00:00 2001 From: jcoupey Date: Tue, 20 Sep 2022 11:43:57 +0200 Subject: [PATCH 40/40] Make route_evals stuff available outside debug mode. --- src/structures/vroom/solution_state.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/structures/vroom/solution_state.h b/src/structures/vroom/solution_state.h index 3834f057f..b3130b691 100644 --- a/src/structures/vroom/solution_state.h +++ b/src/structures/vroom/solution_state.h @@ -111,10 +111,8 @@ class SolutionState { std::vector> weak_insertion_ranks_begin; std::vector> weak_insertion_ranks_end; -#ifndef NDEBUG // Only used for assertion checks in debug mode. std::vector route_evals; -#endif SolutionState(const Input& input); @@ -142,9 +140,7 @@ class SolutionState { void set_insertion_ranks(const RawRoute& r, Index v); void set_insertion_ranks(const TWRoute& r, Index v); -#ifndef NDEBUG void update_route_eval(const std::vector& route, Index v); -#endif }; } // namespace utils